Unstick Node if User Double Clicks Again D3

Chapter iv. Events, Interactivity, and Animation

When rendered past a browser, SVG elements can receive user events and can be manipulated as a whole (for example, to change their position or appearance). This means that they conduct substantially like widgets in a GUI toolkit. That'south an exciting suggestion: to regard SVG as a widget set for graphics. This chapter discusses the options available to create those essential features of a user interface: interactivity and animation.

Events

An important aspect of the DOM is its event model: essentially whatsoever DOM element can receive events and invoke an appropriate handler. The number of unlike outcome types is very large; well-nigh of import for our purposes are user-generated events (mouse clicks or movements, too every bit keystrokes; see Table 4-i).1

Table four-1. Some important user-generated event types
Part Description

click

Any mouse button is pressed and released on an element.

mousemove

The mouse is moved while over an element.

mousedown, mouseup

A mouse push is pressed or released over an chemical element.

mouseenter, mouseleave

The mouse arrow is moved onto or off of an element.

mouseover, mouseout

The mouse pointer is moved onto or off of an element, or any of its children.

keydown, keyup

Whatever key is pressed or released.

D3 treats effect handling equally function of the Selection brainchild (see Table iv-2). If sel is a Selection case, then you utilise the following member role to register a callback as event handler for the specified consequence type:

            sel            .            on            (            type            ,            callback            )          

The blazon argument must be a string indicating the issue blazon (such as "click"). Any DOM event type is permitted. If a handler was already registered for the event type via on(), it is removed before the new handler is registered. To explicitly remove the handler for a given event type, provide null every bit the second argument. To register multiple handlers for the aforementioned effect type, the type proper noun may be followed by a period and an arbitrary tag (so that the handler for "click.foo" does non overwrite the one for "click.bar").

The callback is a function invoked when an event of the specified type is received by any chemical element of the selection. The callback will exist invoked in the same way equally any other accessor is invoked in the context of a selection, being passed the data signal d bound to the current element, the element'southward alphabetize i in the current selection, and the nodes in the current choice, while this contains the current chemical element itself.ii The actual issue instance is non passed to the callback as an statement, but it is available in the variable:

            d3            .            event          

When an consequence occurs, this variable contains the raw DOM event case (not a D3 wrapper!). The information provided by the event object itself depends on the event type. For mouse events, naturally the location of the mouse arrow when the event occurred is of particular interest. The issue object contains the mouse coordinates with respect to three different coordinate systems,3 but none directly provides the information that would exist nearly useful, namely the position with respect to the containing parent chemical element! Thankfully, they tin exist obtained using:

            (((            "d3.mouse() office"            )))            d3            .            mouse            (            node            )          

This role returns the mouse coordinates as a two-element assortment [ten, y]. The argument should be the enclosing container element (as a DOM Node, non equally Selection). When working with SVG, you can supply any chemical element (equally a Node), and the function will calculate the coordinates relative to the nearest antecedent SVG element.

Table four-2. Some important methods, variables, and functions related to event handling (sel is a Choice object)
Function Description

sel.on( types, callback )

Adds or removes a callback for each chemical element in the selection. The types argument must be a string consisting of i or more result type names, separated by whitespace. An event type may be followed past a menstruum and an capricious tag to allow multiple handlers to be registered for a single result type.

  • If a callback is specified, it is registered equally the consequence handler; any existing event handler is removed first.

  • If the callback argument is null, any existing handler is removed.

  • If the callback statement is missing, the currently assigned handler is returned.

d3.event

Contains the current event, if any, equally a DOM Upshot object.

d3.mouse( parent )

Returns a 2-chemical element array containing the mouse coordinates relative to the specified parent.

sel.dispatch( blazon )

Dispatches a custom event of the specified blazon to all elements in the electric current selection.

Exploring Graphs with the Mouse

For someone working analytically with information, these features offer some exciting opportunities because they make it easy to explore graphs interactively: signal the mouse at a spot on the graph and get additional information almost the data point located there. Hither is a elementary example. If you call the part in Case iv-1, while supplying a CSS selector cord (come across "CSS Selectors") that identifies an <svg> element, the electric current mouse arrow location (in pixel coordinates) will be shown in the graph itself. Moreover, the location of the textual brandish is non fixed but will move together with the mouse pointer.

1

Create the <text> element to display the coordinates. It is of import to do this outside of the event callback; otherwise, a new <text> element will be created every fourth dimension the user moves the mouse!

2

Change the shape of the mouse cursor while over the <svg> chemical element. This is non required, of class—but it is a plumbing equipment issue (and as well demonstrates how the mouse cursor tin can exist changed through attributes; see Appendix B).

3

Obtain the mouse coordinates, relative to the upper-left corner of the <svg> element, using the d3.mouse() convenience function.

4

Update the text chemical element created before. In this example, both the displayed text content of the element and its position are updated: slightly to the right of the mouse position.

Displaying the mouse coordinates is, of class, neither new nor particularly exciting. But what is exciting is to see just how like shooting fish in a barrel it is to implement such behavior in D3!

Example Study: Simultaneous Highlighting

The next case is more interesting. It addresses a mutual problem when working with multivariate information sets: how to link 2 different views or projections of the information visually. One style is to select a region of data points in one view with the mouse and simultaneously highlight the corresponding points in all other views. In Figure four-ane, points are highlighted in both panels according to their distance (in pixel coordinates) from the mouse pointer in the lefthand console. Because this instance is more than involved, we will first discuss a simplified version (run into Instance 4-2).

dfti 0401

Figure iv-1. Data points belonging to the same tape are simultaneously highlighted in both panels, based on the distance of the points in the lefthand panel to the mouse pointer.
Example 4-2. Commands for Figure 4-1
                role                                                makeBrush                (                )                                                {                                                d3                .                csv                (                                                "dense.csv"                                                )                .                then                (                                                office                (                                                data                                                )                                                {                                                1                                                var                                                svg1                                                =                                                d3                .                select                (                                                "#brush1"                                                )                ;                                                2                                                var                                                svg2                                                =                                                d3                .                select                (                                                "#brush2"                                                )                ;                                                var                                                sc1                =                d3                .                scaleLinear                (                )                .                domain                (                [                0                ,                10                ,                50                ]                )                                                3                                                .                range                (                [                "lime"                ,                "yellowish"                ,                "red"                ]                )                ;                                                var                                                sc2                =                d3                .                scaleLinear                (                )                .                domain                (                [                0                ,                x                ,                50                ]                )                                                .                range                (                [                "lime"                ,                "yellow"                ,                "blue"                ]                )                ;                                                var                                                cs1                                                =                                                drawCircles                (                svg1                ,                information                ,                d                =>                d                [                "A"                ]                ,                d                =>                d                [                "B"                ]                ,                sc1                )                ;                                                4                                                var                                                cs2                                                =                                                drawCircles                (                svg2                ,                data                ,                d                =>                d                [                "A"                ]                ,                d                =>                d                [                "C"                ]                ,                sc2                )                ;                                                svg1                .                call                (                                                installHandlers                ,                                                data                ,                                                cs1                ,                                                cs2                ,                                                sc1                ,                                                sc2                                                )                ;                                                5                                                }                                                )                ;                                                }                                                role                                                drawCircles                (                                                svg                ,                                                data                ,                                                accX                ,                                                accY                ,                                                sc                                                )                                                {                                                var                                                colour                                                =                                                sc                (                Infinity                )                ;                                                6                                                return                                                svg                .                selectAll                (                                                "circle"                                                )                .                data                (                                                data                                                )                .                enter                (                )                                                .                suspend                (                                                "circle"                                                )                                                .                attr                (                                                "r"                ,                                                5                                                )                .                attr                (                                                "cx"                ,                                                accX                                                )                .                attr                (                                                "cy"                ,                                                accY                                                )                                                .                attr                (                                                "fill up"                ,                                                colour                                                )                .                attr                (                                                "fill-opacity"                ,                                                0.4                                                )                ;                                                }                                                function                                                installHandlers                (                                                svg                ,                                                information                ,                                                cs1                ,                                                cs2                ,                                                sc1                ,                                                sc2                                                )                                                {                                                svg                .                attr                (                                                "cursor"                ,                                                "crosshair"                                                )                                                .                on                (                                                "mousemove"                ,                                                function                (                )                                                {                                                var                                                pt                                                =                                                d3                .                mouse                (                                                svg                .                node                (                )                                                )                ;                                                cs1                .                attr                (                                                "make full"                ,                                                function                (                                                d                ,                                                i                                                )                                                {                                                7                                                var                                                dx                                                =                                                pt                [                0                ]                                                -                                                d3                .                select                (                                                this                                                )                .                attr                (                                                "cx"                                                )                ;                                                var                                                dy                                                =                                                pt                [                1                ]                                                -                                                d3                .                select                (                                                this                                                )                .                attr                (                                                "cy"                                                )                ;                                                var                                                r                                                =                                                Math                .                hypot                (                                                dx                ,                                                dy                                                )                ;                                                information                [                i                ]                [                "r"                ]                                                =                                                r                ;                                                8                                                return                                                sc1                (                r                )                ;                                                }                                                )                ;                                                9                                                cs2                .                attr                (                                                "fill"                ,                                                (                d                ,                i                )                                                =>                                                sc2                (                                                data                [                i                ]                [                "r"                ]                                                )                                                )                ;                                                }                                                )                                                10                                                .                on                (                                                "mouseleave"                ,                                                function                (                )                                                {                                                cs1                .                attr                (                                                "fill"                ,                                                sc1                (                Infinity                )                                                )                ;                                                11                                                cs2                .                attr                (                                                "fill"                ,                                                sc2                (                Infinity                )                                                )                ;                                                }                                                )                ;                                                }              
1

Load the data ready and specify the callback to invoke when the information is bachelor (see Affiliate six for more than information virtually fetching data). The file contains 3 columns, labeled A, B, and C.

2

Select the two panels of the graph.

3

D3 can smoothly interpolate between colors. Here nosotros create two colour gradients (ane for each panel). (Run across Chapter 7 to learn more nearly interpolation and scale objects.)

4

Create the circles representing data points. The newly created circles are returned as Selection objects. Following a general D3 convention, columns are specified in the function call by providing accessor functions.

5

Phone call the function installHandlers() to register the consequence handlers. This line of lawmaking uses the phone call() facility to invoke the installHandlers() function, while supplying the svg1 selection and the remaining parameters as arguments. (We encountered this already in Example 2-half dozen; too see the word regarding components in Chapter 5.)

6

Initially, the circles are drawn with the "maximum" colour. To notice this color, evaluate the color calibration at positive infinity.

7

For each point in the panel on the left, calculate its distance to the mouse pointer…

8

… and store it, as an additional column, in the data set. (This will exist our mechanism of communication between the two panels of the figure.)

9

Return the advisable color from the color gradient.

10

Use the boosted column in the data set to colour the points in the console on the right.

11

Restore the points to their original colors when the mouse leaves the lefthand console.

This version of the program works well and solves the original problem. The improved version of the installHandlers() function shown in Example 4-3 allows us to discuss some additional techniques when writing this kind of user interface code.

Example 4-3. An improved version of the installHandlers() function in Example 4-2
                function                                                installHandlers2                (                                                svg                ,                                                data                ,                                                cs1                ,                                                cs2                ,                                                sc1                ,                                                sc2                                                )                                                {                                                var                                                cursor                                                =                                                svg                .                append                (                                                "circle"                                                )                .                attr                (                                                "r"                ,                                                50                                                )                                                1                                                .                attr                (                                                "fill"                ,                                                "none"                                                )                .                attr                (                                                "stroke"                ,                                                "black"                                                )                                                .                attr                (                                                "stroke-width"                ,                                                x                                                )                .                attr                (                                                "stroke-opacity"                ,                                                0.ane                                                )                                                .                attr                (                                                "visibility"                ,                                                "hidden"                                                )                ;                                                2                                                var                                                hotzone                                                =                                                svg                .                append                (                                                "rect"                                                )                .                attr                (                                                "cursor"                ,                                                "none"                                                )                                                3                                                .                attr                (                                                "ten"                ,                                                50                                                )                .                attr                (                                                "y"                ,                                                fifty                                                )                                                .                attr                (                                                "width"                ,                                                200                                                )                .                attr                (                                                "height"                ,                                                200                                                )                                                .                attr                (                                                "visibility"                ,                                                "subconscious"                                                )                                                4                                                .                attr                (                                                "arrow-events"                ,                                                "all"                                                )                                                .                on                (                                                "mouseenter"                ,                                                function                (                )                                                {                                                5                                                cursor                .                attr                (                                                "visibility"                ,                                                "visible"                                                )                ;                                                }                                                )                                                .                on                (                                                "mousemove"                ,                                                office                (                )                                                {                                                6                                                var                                                pt                                                =                                                d3                .                mouse                (                                                svg                .                node                (                )                                                )                ;                                                cursor                .                attr                (                                                "cx"                ,                                                pt                [                0                ]                                                )                .                attr                (                                                "cy"                ,                                                pt                [                1                ]                                                )                ;                                                cs1                .                attr                (                                                "fill up"                ,                                                function                (                                                d                ,                                                i                                                )                                                {                                                var                                                dx                                                =                                                pt                [                0                ]                                                -                                                d3                .                select                (                                                this                                                )                .                attr                (                                                "cx"                                                )                ;                                                var                                                dy                                                =                                                pt                [                1                ]                                                -                                                d3                .                select                (                                                this                                                )                .                attr                (                                                "cy"                                                )                ;                                                var                                                r                                                =                                                Math                .                hypot                (                                                dx                ,                                                dy                                                )                ;                                                data                [                i                ]                [                "r"                ]                                                =                                                r                ;                                                return                                                sc1                (                r                )                ;                                                }                                                )                ;                                                cs2                .                attr                (                                                "fill"                ,                                                (                d                ,                i                )                                                =>                                                sc2                (                                                data                [                i                ]                [                "r"                ]                                                )                                                )                ;                                                }                                                )                                                .                on                (                                                "mouseleave"                ,                                                office                (                )                                                {                                                cursor                .                attr                (                                                "visibility"                ,                                                "hidden"                                                )                ;                                                cs1                .                attr                (                                                "fill"                ,                                                sc1                (                Infinity                )                                                )                ;                                                cs2                .                attr                (                                                "fill"                ,                                                sc2                (                Infinity                )                                                )                ;                                                }                                                )                                                }              
1

In this version, the actual mouse pointer itself is subconscious and replaced with a large, partially opaque circle. Points within the circle will exist highlighted.

2

Initially, the circle is subconscious. It volition merely be shown once the mouse pointer enters the "hot zone."

3

The "hot zone" is defined as a rectangle inside the lefthand console. The result handlers are registered on this rectangle, meaning that they will simply exist invoked when the mouse arrow is within of it.

4

The rectangle is hidden from view. By default, DOM elements that have their visibility attribute set to hidden practice not receive mouse pointer events. To overcome this, the pointer-events attribute must be set explicitly. (Some other way to make an element invisible is to prepare its make full-opacity to 0. In this case, it volition not be necessary to alter the pointer-events attribute.)

5

When the mouse enters the "hot zone," the opaque circle that acts every bit the pointer is displayed.

6

The mousemove and mouseleave event handlers are equivalent to the ones in Instance 4-2, except for the additional commands to update the circle interim every bit a cursor.

The use of an active "hot zone" in this example is of course optional, but information technology demonstrates an interesting technique. At the same time, the discussion of the arrow-events attribute suggests that this kind of user interface programming may involve unexpected challenges. We will come dorsum to this point after the next instance.

The D3 Drag-and-Drop Behavior Component

Several common user interface patterns consist of a combination of events and responses: in the drag-and-drop pattern, for instance, the user beginning selects an item, then moves information technology, and finally releases information technology again. D3 includes a number of predefined behavior components that simplify the evolution of such user interface code by bundling and organizing the required actions. In addition, these components also unify some details of the user interface.

Consider a state of affairs like the i in Figure iv-2, showing the following SVG snippet:

              <svg              id=              "dragdrop"              width=              "600"              height=              "200"              >              <circumvolve              cx=              "100"              cy=              "100"              r=              "20"              fill up=              "red"              />              <circle              cx=              "300"              cy=              "100"              r=              "xx"              fill=              "green"              />              <circle              cx=              "500"              cy=              "100"              r=              "20"              fill=              "blue"              />              </svg>            

dfti 0402

Figure 4-2. The initial configuration for the elevate-and-driblet behavior

Now let'due south enable the user to modify the position of the circles with the mouse. It is not difficult to add the familiar drag-and-drop blueprint past registering callbacks for mousedown, mousemove, and mouseup events, but Instance 4-4 uses the D3 drag behavior component instead. As explained in Example two-6, a component is a function object that takes a Pick instance as argument and adds DOM elements to that Selection (also run into Chapter five). A behavior component is a component that installs required issue callbacks in the DOM tree. At the aforementioned time, information technology is besides an object that has member functions itself. The listing uses the elevate component's on( type, callback ) member office to specify the callbacks for the unlike event types.

1

Create a elevate function object using the manufactory role d3.elevate(), then invoke the on() fellow member function on the returned function object to register the required callbacks.

2

The start handler stores the current colour of the selected circle; then changes the selected circle'due south color and assigns the selected circle itself (as a Selection) to widget.

3

The elevate handler retrieves the current mouse coordinates and moves the selected circle to this location.

4

The finish handler restores the circumvolve's color and clears the agile widget.

5

Finally, invoke the elevate component functioning while supplying a selection containing the circles to install the configured event handlers on the option.

A more than idiomatic way to express this would employ the call() part rather than invoking the component operation explicitly:

              d3              .              select              (              "#dragdrop"              ).              selectAll              (              "circle"              )              .              call              (              d3              .              drag              ()              .              on              (              "offset"              ,              role              ()              {              ...              }              )              .              on              (              "drag"              ,              office              ()              {              ...              }              )              .              on              (              "end"              ,              part              ()              {              ...              }              )              );            

The effect names in Example 4-iv may come as a surprise: these are not standard DOM events, but D3 pseudoevents. The D3 drag behavior combines both mouse and touch-screen upshot treatment. Internally, the first pseudoevent corresponds to either a mousedown or a touchstart upshot, and similar for drag and end. Furthermore, the drag behavior prevents the browser's default activity for certain event types.four D3 includes additional behaviors to aid with zooming and when selecting parts of a graph with a mouse.

Notes on User Interface Programming

I hope the examples so far have convinced you that creating interactive graphs using D3 demand non be difficult—in fact, I believe D3 makes them feasible fifty-fifty for advertising hoc, i-off tasks and explorations. At the same time, equally the discussion after the previous two examples shows, graphical user interface programming is still a relatively complex problem. Many components, each with its ain rules, participate and tin collaborate in unexpected means. Browsers may differ in their implementation. Hither are some reminders and potential surprises (too encounter Appendix C for groundwork data on DOM event handling):

  • Repeated calls to on() for the same issue type on the same Choice instance clobber each other. Add together a unique tag to the event type (separated past a period) to register multiple event handlers.

  • If you want to admission this in a callback or accessor role, you must utilize the function keyword, you lot cannot utilise an arrow function. This is a limitation of the JavaScript language (meet Appendix C). Examples can be plant in the installHandlers() function in Examples 4-2 and four-3, and several times in Example iv-four.

  • Browser default behavior may interfere with your code; you may need to preclude it explicitly.

  • Generally, just visible, painted elements can receive mouse pointer events. Elements with their visibility aspect set to hidden, or with both fill and stroke fix to none, do not receive pointer events by default. Use the pointer-events attribute for fine-grained control over the conditions under which elements will receive events. (See MDN Arrow-Events.)

  • In a like spirit, a <g> element has no visual representation, and hence does not generate pointer events. Nevertheless, information technology may be advisable to annals an event handler on a <chiliad> element considering events generated by any of its (visible) children will be delegated to information technology. (Utilise an invisible rectangle or other shape to define active "hot zones," equally in Instance four-three.)

Smooth Transitions

An obvious way to answer to events is to use some change to the effigy's appearance or configuration (for example, to testify a earlier-and-afterward event). In this case, it is oft useful to permit the change take identify gradually, rather than instantaneously, to depict attention to the change that is taking place and to allow users to discern boosted item. For case, users may now exist able to recognize which data points are most affected by the change, and how (run into Effigy three-3 and Example iii-one for an instance).

Conveniently, the D3 Transition facility does all the work for y'all. It replicates near of the Selection API, and you lot tin can change the appearance of selected elements using attr() or way() as before (see Affiliate 3). Simply now the new settings practise not take upshot immediately; instead, they are applied gradually over a configurable time span (meet Case 2-8 for an early on example).

Under the covers, D3 creates and schedules the required intermediate configurations to give the appearance that the graph is irresolute smoothly over the desired duration. To do and then, D3 invokes an interpolator that creates the intermediate configurations betwixt the starting and terminate points. The D3 interpolation facility is fairly smart and able to interpolate automatically betwixt most types (such as numbers, dates, colors, strings with embedded numbers, and more—see Chapter 7 for a detailed description).

Creating and Configuring Transitions

The workflow to create a transition is uncomplicated (too encounter Table iv-3):

  1. Before creating a transition, make sure any data has been spring and all elements that are supposed to be office of the transition have been created (using append() or insert())—fifty-fifty if they are initially gear up to exist invisible! (The Transition API allows you to change and remove elements, but it does non provide for the creation of elements as part of the transition.)

  2. Now select the elements you wish to alter using the familiar Selection API.

  3. Invoke transition() on this selection to create a transition. Optionally, call duration(), filibuster(), or ease() for more command over its behavior.

  4. Ready the desired end country using attr() or mode() as usual. D3 will create the intermediate configurations between the current values and the indicated stop states, and utilize them over the duration of the transition.

Ofttimes, these commands will exist function of an event handler, so that the transition starts when an appropriate event occurs.

Table 4-3. Functions to create and terminate a transition (sel is a Selection object; trans is aTransition object)
Function Clarification

sel.transition( tag )

Returns a new transition on the receiving selection. The optional argument may be a string (to place and distinguish this transition on the selection) or a Transition instance (to synchronize transitions).

sel.interrupt( tag )

Stops the agile transition and cancels any pending transitions on the selected elements for the given identifier. (Interrupts are non forwarded to children of the selected elements.)

trans.transition()

Returns a new transition on the aforementioned selected elements every bit the receiving transition, scheduled to start when the current transition ends. The new transition inherits the electric current transition's configuration.

trans.selection()

Returns the option for a transition.

In add-on to the desired stop point, a Transition also allows you to configure several aspects of its behavior (run across Table 4-iv). All of these have reasonable defaults, making explicit configuration optional:

  • A filibuster that must pass before the change begins to take upshot.

  • A elapsing over which the setting will change gradually.

  • An easing that controls how the rate of change will differ over the transition duration (to "ease into" and "out of" the animation). By default, the easing follows a piecewise cubic polynomial with "wearisome-in, slow-out" behavior.

  • An interpolator to calculate the intermediate values (this is rarely necessary, because the default interpolators handle most common configurations automatically).

  • An event handler to execute custom code when the transition starts, ends, or is interrupted.

Tabular array 4-4. Functions to configure a transition or to retrieve the current setting if chosen without argument (trans is a Transition object)
Office Description

trans.delay( value )

Sets the delay (in milliseconds) earlier the transition begins for each chemical element in the selection; the default is 0. The filibuster can be given every bit a constant or equally a part. If it is a function, the function will be invoked once for each element, earlier the transition begins, and should render the desired delay. The function will exist passed the information bound to the element d and its index in the selection i.

trans.duration( value )

Sets the elapsing (in milliseconds) of the transition for each chemical element in the selection; the default is 250 milliseconds. The duration can be given as a constant or every bit a part. If information technology is a role, the part volition exist invoked once for each element, before the transition begins, and should return the desired duration. The office will be passed the data bound to the element d and its index in the option i.

trans.ease( fct )

Sets the easing role for all selected elements. The easing must be a function, taking a unmarried parameter between 0 and 1, and returning a single value, too between 0 and 1. The default easing is d3.easeCubic (a piecewise defined cubic polynomial with "dull-in, slow-out" behavior).

trans.on( type, handler )

Adds an event handler on the transition. The blazon must be start, finish, or interrupt. The upshot handler will be invoked at the appropriate point in the transition'southward lifecycle. This function behaves similarly to the on() part on a Pick object run across Tabular array 4-2).

Using Transitions

The Transition API replicates large parts of the Selection API. In particular, all functions from Tabular array 3-2 (that is, select(), selectAll(), and filter()) are bachelor. From Table three-4, attr(), way(), text(), and each() carry over, as well as all functions from Tabular array 3-5 except suspend(), insert(), and sort(). (As was pointed out before, all elements participating in a transition must exist earlier the transition is created. For the aforementioned reason, none of the functions for bounden data from Table 3-3 exist for transitions.)

Basic transitions are straightforward to use, as nosotros have already seen in an example in an earlier chapter (Example three-1). The application in Example 4-5 is still elementary, but the outcome is more sophisticated: a bar chart is updated with new information, only the outcome is staggered (using delay()) then that the confined don't all change at the same time.

dfti 0403

Figure 4-3. When this bar chart is updated with a new data gear up, the updates are applied consecutively, left to right.
Example 4-5. Using transitions (see Figure 4-3)
                function                                                makeStagger                (                )                                                {                                                var                                                ds1                                                =                                                [                                                ii                ,                                                1                ,                                                3                ,                                                5                ,                                                7                ,                                                8                ,                                                9                ,                                                9                ,                                                ix                ,                                                8                ,                                                7                ,                                                v                ,                                                3                ,                                                1                ,                                                2                                                ]                ;                                                1                                                var                                                ds2                                                =                                                [                                                8                ,                                                9                ,                                                viii                ,                                                7                ,                                                v                ,                                                3                ,                                                2                ,                                                1                ,                                                2                ,                                                three                ,                                                5                ,                                                7                ,                                                8                ,                                                nine                ,                                                8                                                ]                ;                                                var                                                n                                                =                                                ds1                .                length                ,                                                mx                                                =                                                d3                .                max                (                                                d3                .                merge                (                                                [                ds1                ,                                                ds2                ]                                                )                                                )                ;                                                2                                                var                                                svg                                                =                                                d3                .                select                (                                                "#stagger"                                                )                ;                                                var                                                scX                                                =                                                d3                .                scaleLinear                (                )                .                domain                (                                                [                0                ,                due north                ]                                                )                .                range                (                                                [                50                ,                540                ]                                                )                ;                                                3                                                var                                                scY                                                =                                                d3                .                scaleLinear                (                )                .                domain                (                                                [                0                ,                mx                ]                                                )                .                range                (                                                [                250                ,                fifty                ]                                                )                ;                                                svg                .                selectAll                (                                                "line"                                                )                .                data                (                                                ds1                                                )                .                enter                (                )                .                append                (                                                "line"                                                )                                                4                                                .                attr                (                                                "stroke"                ,                                                "reddish"                                                )                .                attr                (                                                "stroke-width"                ,                                                xx                                                )                                                .                attr                (                                                "x1"                ,                                                (                d                ,                i                )                =>                scX                (                i                )                                                )                .                attr                (                                                "y1"                ,                                                scY                (                0                )                                                )                                                .                attr                (                                                "x2"                ,                                                (                d                ,                i                )                =>                scX                (                i                )                                                )                .                attr                (                                                "y2"                ,                                                d                =>                scY                (                d                )                                                )                ;                                                svg                .                on                (                                                "click"                ,                                                function                (                )                                                {                                                5                                                [                                                ds1                ,                                                ds2                                                ]                                                =                                                [                                                ds2                ,                                                ds1                                                ]                ;                                                6                                                svg                .                selectAll                (                                                "line"                                                )                .                data                (                                                ds1                                                )                                                7                                                .                transition                (                )                .                elapsing                (                                                one thousand                                                )                .                delay                (                                                (                d                ,                i                )                =>                200                *                i                                                )                                                8                                                .                attr                (                                                "y2"                ,                                                d                =>                scY                (                d                )                                                )                ;                                                9                                                }                                                )                ;                                                }              
1

Define ii data sets. To proceed things uncomplicated, only the y values are included; we will exist using the assortment index of each detail for its horizontal position.

2

Find the number of data points, and the overall maximal value beyond both data sets.

3

Two scale objects that map the values in the data set to vertical, and their index positions in the array to horizontal pixel coordinates.

4

Create the bar nautical chart. Each "bar" is realized as a thick line (rather than a <rect> element).

5

Register an result handler for "click" events.

6

Interchange the information sets.

7

Bind the (updated) data prepare ds1 to the option…

8

… and create a transition case. Each bar will take one second to attain its new size, but will start only afterward a delay. The delay is dependent on the horizontal position of each bar, growing left to right. This has the upshot that the "update" seems to sweep across the chart.

9

Finally, set the new vertical length of each line. This is the stop point for the transition.

Hints and Techniques

Not all transitions are as straightforward every bit the ones nosotros have seen so far. Hither are some additional hints and techniques.

Strings

The D3 default interpolators volition interpolate numbers that are embedded in strings, only leave the residual of the string lone considering in that location is no by and large useful way to interpolate between strings. The best way to achieve a smooth transition between strings is to cross-fade between 2 strings in the same location. Assume that 2 suitable <text> elements exist:

                <text                id=                "t1"                x=                "100"                y=                "100"                fill up-opacity=                "1"                >Hello</text>                <text                id=                "t2"                10=                "100"                y=                "100"                make full-opacity=                "0"                >Earth</text>              

Then you can cross-fade between them past changing their opacity (possibly changing the duration of the transition):

                d3                .                select                (                "#t1"                ).                transition                ().                attr                (                "make full-opacity"                ,                0                );                d3                .                select                (                "#t2"                ).                transition                ().                attr                (                "make full-opacity"                ,                1                );              

An alternative that may brand sense in certain cases is to write a custom interpolator to generate intermediate string values.

Chained transitions

Transitions can be chained so that i transition begins when the first one ends. The subsequent transitions inherit the earlier transition'due south elapsing and delay (unless they are overridden explicitly). The following code will turn the selected elements first to blood-red, then to blue:

                d3                .                selectAll                (                "circumvolve"                )                .                transition                ().                duration                (                2000                ).                attr                (                "fill up"                ,                "red"                )                .                transition                ().                attr                (                "fill"                ,                "blue"                );              

Explicit starting configuration

Unless you plan to utilise a custom interpolator (come across next), it is important that the starting configuration is set up explicitly. For example, don't rely on the default value (black) for the fill attribute: unless the fill attribute is set explicitly, the default interpolator will not know what to exercise.

Custom interpolators

Using the methods in Table 4-5, information technology is possible to specify a custom interpolator to be used during the transition. The methods to fix a custom interpolator take a factory part as argument. When the transition starts, the factory function is invoked for each element in the choice, being passed the data d leap to the element and the element's alphabetize i, with this existence prepare to the electric current DOM Node. The manufacturing plant must return an interpolator part. The interpolator function must accept a single numeric argument between 0 and 1 and must return an advisable intermediate value between the starting and the cease configuration. The interpolator volition be called afterward any easing has been applied. The post-obit code uses a simple custom colour interpolator without easing (run across Chapter 8 to learn about more flexible ways to operate on colors in D3):

                d3                .                select                (                "#custom"                ).                selectAll                (                "circle"                )                .                attr                (                "make full"                ,                "white"                )                .                transition                ().                elapsing                (                2000                ).                ease                (                t                =>                t                )                .                attrTween                (                "make full"                ,                function                ()                {                render                t                =>                "hsl("                +                360                *                t                +                ", 100%, 50%)"                }                );              

The next example is more interesting. Information technology creates a rectangle centered at the position (100, 100) in the graph and so rotates the rectangle smoothly around its middle. (D3 default interpolators understand some SVG transformations, but this example demonstrates how to write your own interpolator in instance you demand to.)

                d3                .                select                (                "#custom"                ).                suspend                (                "rect"                )                .                attr                (                "x"                ,                eighty                ).                attr                (                "y"                ,                eighty                )                .                attr                (                "width"                ,                40                ).                attr                (                "height"                ,                40                )                .                transition                ().                elapsing                (                2000                ).                ease                (                t                =>                t                )                .                attrTween                (                "transform"                ,                function                ()                {                render                t                =>                "rotate("                +                360                *                t                +                ",100,100)"                }                );              

Transition events

Transitions emit custom events when they commencement, end, and are interrupted. Using the on() method, you can register an effect handler on a transition, which volition be chosen when the appropriate lifecycle event is emitted. (See the D3 Reference Documentation for details.)

Easings

Using the ease() method, y'all can specify an easing. The purpose of an easing is to "stretch" or "compress" the time seen past the interpolator, assuasive the animation to "ease into and out of" the move. This often enhances the visual affect of an blitheness dramatically. As a matter of fact, "tiresome-in, slow-out" has been recognized by animators at Disney as ane of the "Twelve Principles of Animation" (see "Principles of Animation"). But at other times, when they don't friction match well with the manner the user expects an object to behave, easings tin be downright confusing. The rest is definitely subtle.

An easing takes a parameter in the interval [0, 1] and maps it to the same interval, starting for t = 0 at 0 and ending for t = 1 at 1. The mapping is typically nonlinear (otherwise, the easing is merely the identity). The default easing is d3.easeCubic, which implements a version of "boring-in, ho-hum-out" behavior.

Technically, an easing is simply a mapping that is practical to the time parameter t earlier it is passed to the interpolator. This makes the distinction between the easing and the interpolator somewhat arbitrary. What if a custom interpolator itself mangles the time parameter in some nonlinear way? From a practical point of view, it seems best to treat easings as a convenience characteristic that adds "tiresome-in, slow-out" beliefs to standard interpolators. (D3 includes a confusingly large range of unlike easings, some of which considerably mistiness the distinction between an easing and what should be considered a custom interpolator.)

Don't overuse transitions

Transitions can be overused. A full general problem with transitions is that they usually tin can't exist interrupted past the user: the resulting forced wait can quickly pb to frustration. When transitions are employed to permit the user to track the effects of a change, they assist agreement (see Figure three-3 for a simple example). But when they are used merely "for upshot," they easily get tiring once the initial cuteness wears off. (Effigy iv-three can serve every bit a cautionary example in this spirit!)

Tabular array iv-v. Methods to specify custom interpolators (trans is a Transition object)
Function Description

trans.attrTween( name, factory )

Sets a custom interpolator for the named attribute. The second argument must exist a manufactory method that returns an interpolator.

trans.styleTween( name, factory )

Sets a custom interpolator for the named style. The 2d argument must be a factory method that returns an interpolator.

trans.tween( tag, factory )

Sets a custom interpolator to exist invoked during transitions. The first argument is an arbitrary tag to identify this interpolator, the 2d argument must exist a factory method that returns an interpolator. The consequence of the interpolator is not restricted; it volition exist invoked purely for its side effects.

Animation with Timer Events

Transitions are a convenience technique to transform a configuration smoothly into another, only they are non intended as framework for general animations. To create those, it is mostly necessary to work on a lower level. D3 includes a special timer that will invoke a given callback once per animation frame, that is, every time the browser is about to repaint the screen. The time interval is not configurable because it is determined by the browser's refresh rate (about threescore times per 2d or every 17 milliseconds, for most browsers). It is also not exact; the callback volition be passed a high-precision timestamp that can be used to determine how much time has passed since the last invocation (run into Table 4-6).

Table 4-6. Functions and methods for creating and using timers (t is a Timer object)
Function Clarification

d3.timer( callback, after, start )

Returns a new timer instance. The timer will invoke the callback perpetually once per animation frame. When invoked, the callback will be passed the apparent elapsed fourth dimension since the timer started running. (Apparent elapsed fourth dimension does not progress while the window or tab is in the background.) The numeric get-go argument may contain a timestamp, as returned by d3.now(), at which the timer is scheduled to begin (it defaults to now). The numeric after argument may contain a delay, in milliseconds, which will be added to the offset time (it defaults to 0).

d3.timeout( callback, later on, start )

Similar d3.timer(), except that the callback will be invoked exactly in one case.

d3.interval( callback, interval, kickoff )

Similar to d3.timer(), except that the callback will only be invoked every interval milliseconds.

t.stop()

Stops this timer. Has no result if the timer is already stopped.

d3.now()

Returns the current time, in milliseconds.

Example: Existent-Fourth dimension Animations

Example iv-half dozen creates a smooth animation past updating a graph for every browser repaint. The graph (meet the left side of Effigy 4-iv) draws a line (a Lissajous bend 5) that slowly fades every bit time goes on. In contrast to most other examples, this code does not use binding—more often than not because at that place is no information gear up to bind! Instead, at each time pace, the next position of the curve is calculated and a new <line> element is added to the graph from the previous position to the new ane. The opacity of all elements is reduced by a constant factor, and elements whose opacity has fallen then low as to be essentially invisible are removed from the graph. The current value of the opacity is stored in each DOM Node itself as a new, "artificial" property. This is optional; you lot could instead store the value in a separate data structure keyed past each node (for example, using d3.local(), which is intended for this purpose), or query the current value using attr(), update, and reset information technology.

Case 4-6. Real-time animation (see the left side of Figure 4-4)
                function                makeLissajous                ()                {                var                svg                =                d3                .                select                (                "#lissajous"                );                var                a                =                iii.2                ,                b                =                5.nine                ;                // Lissajous frequencies                var                phi                ,                omega                =                2                *                Math                .                PI                /                10000                ;                // 10 seconds per flow                var                crrX                =                150                +                100                ,                crrY                =                150                +                0                ;                var                prvX                =                crrX                ,                prvY                =                crrY                ;                var                timer                =                d3                .                timer                (                function                (                t                )                {                phi                =                omega                *                t                ;                crrX                =                150                +                100                *                Math                .                cos                (                a                *                phi                );                crrY                =                150                +                100                *                Math                .                sin                (                b                *                phi                );                svg                .                selectAll                (                "line"                )                .                each                (                function                ()                {                this                .                bogus_opacity                *=                .                99                }                )                .                attr                (                "stroke-opacity"                ,                function                ()                {                return                this                .                bogus_opacity                }                )                .                filter                (                function                ()                {                return                this                .                bogus_opacity                <                0.05                }                )                .                remove                ();                svg                .                suspend                (                "line"                )                .                each                (                office                ()                {                this                .                bogus_opacity                =                1.0                }                )                .                attr                (                "x1"                ,                prvX                ).                attr                (                "y1"                ,                prvY                )                .                attr                (                "x2"                ,                crrX                ).                attr                (                "y2"                ,                crrY                )                .                attr                (                "stroke"                ,                "green"                ).                attr                (                "stroke-width"                ,                2                );                prvX                =                crrX                ;                prvY                =                crrY                ;                if                (                t                >                120                e3                )                {                timer                .                terminate                ();                }                // after 120 seconds                }                );                }              

dfti 0404

Figure 4-4. Animations: a Lissajous figure (left, see Instance 4-half dozen), and a voter model (right, meet Example 4-seven)

Example: Smoothing Periodic Updates with Transitions

In the previous example, each new data point to be displayed was calculated in real time. That's not e'er possible. Imagine that you demand to access a remote server for data. Yous might desire to poll it periodically, but certainly not for each repaint. In any case, a remote fetch is always an asynchronous call and needs to be handled accordingly.

In such a situation, transitions can aid to create a better user experience past smoothing out the time periods between updates from the data source. In Example 4-vii, the remote server has been replaced past a local role to keep the example simple, but most of the concepts carry over. The example implements a simple voter model:six at each time step, each graph chemical element randomly selects one of its 8 neighbors and adopts its color. The update function is called only every few seconds; D3 transitions are used to update the graph smoothly in the meantime (see the right side of Figure 4-4).

Instance 4-7. Using transitions to smooth out periodic updates (see the right side of Figure 4-4)
                role                                                makeVoters                (                )                                                {                                                var                                                due north                                                =                                                50                ,                                                westward                =                300                /                n                ,                                                dt                                                =                                                3000                ,                                                svg                                                =                                                d3                .                select                (                                                "#voters"                                                )                ;                                                var                                                data                                                =                                                d3                .                range                (                n                *                due north                )                                                1                                                .                map                (                                                d                                                =>                                                {                                                return                                                {                                                x                :                                                d                %                n                ,                                                y                :                                                d                /                northward                |                0                ,                                                val                :                                                Math                .                random                (                )                                                }                                                }                                                )                ;                                                var                                                sc                                                =                                                d3                .                scaleQuantize                (                )                                                2                                                .                range                (                                                [                                                "white"                ,                                                "red"                ,                                                "black"                                                ]                                                )                ;                                                svg                .                selectAll                (                                                "rect"                                                )                .                data                (                                                data                                                )                .                enter                (                )                .                suspend                (                                                "rect"                                                )                                                3                                                .                attr                (                                                "x"                ,                                                d                =>                w                *                d                .                x                                                )                .                attr                (                                                "y"                ,                                                d                =>                w                *                d                .                y                                                )                                                .                attr                (                                                "width"                ,                                                due west                -                1                                                )                .                attr                (                                                "summit"                ,                                                w                -                1                                                )                                                .                attr                (                                                "fill"                ,                                                d                                                =>                                                sc                (                d                .                val                )                                                )                ;                                                part                                                update                (                )                                                {                                                4                                                var                                                nbs                                                =                                                [                                                [                0                ,                i                ]                ,                                                [                0                ,                -                1                ]                ,                                                [                                                ane                ,                0                ]                ,                                                [                -                one                ,                                                0                ]                ,                                                [                1                ,                1                ]                ,                                                [                1                ,                -                i                ]                ,                                                [                -                i                ,                1                ]                ,                                                [                -                one                ,                -                1                ]                                                ]                ;                                                return                                                d3                .                shuffle                (                                                d3                .                range                (                                                due north                *                north                                                )                                                )                .                map                (                                                i                                                =>                                                {                                                var                                                nb                                                =                                                nbs                [                                                nbs                .                length                *                Math                .                random                (                )                                                |                                                0                                                ]                ;                                                var                                                x                                                =                                                (                information                [                i                ]                .                x                                                +                                                nb                [                0                ]                                                +                                                north                )                %                n                ;                                                var                                                y                                                =                                                (                data                [                i                ]                .                y                                                +                                                nb                [                1                ]                                                +                                                n                )                %                n                ;                                                data                [                i                ]                .                val                                                =                                                data                [                                                y                *                n                                                +                                                x                                                ]                .                val                ;                                                }                                                )                ;                                                }                                                d3                .                interval                (                                                part                (                )                                                {                                                5                                                update                (                )                ;                                                svg                .                selectAll                (                                                "rect"                                                )                .                data                (                                                data                                                )                                                .                transition                (                )                .                duration                (                dt                )                .                delay                (                (                d                ,                i                )                =>                i                *                0.25                *                dt                /                (                north                *                north                )                )                                                .                attr                (                                                "make full"                ,                                                d                                                =>                                                sc                (                d                .                val                )                                                )                                                }                ,                                                dt                                                )                ;                                                }              
1

Creates an array of n ii objects. Each object has a random value betwixt 0 and 1, and also knowledge of its 10 and y coordinates in a square. (The odd d/n|0 expression is a autograph way to truncate the quotient on the left to an integer: the chip-wise OR operator forces its operands into an integer representation, truncating the decimals in the procedure. This is a semi-common JavaScript idiom that is worth knowing.)

2

The object returned by d3.scaleQuantize() is an case of a binning scale, which splits its input domain into equally sized bins. Here, the default input domain [0,1] is split up into iii equally sized bins, i for each colour. (See Chapter 7 for more detail about scale objects.)

3

Binds the data fix so creates a rectangle for each record in the data prepare. Each data record contains information nearly the position of the rectangle, and the scale object is used to map each record's value property to a colour.

4

The actual update office that computes a new configuration when called. It visits each element of the array in random club. For each element, it randomly selects one of its eight neighbors and assigns the neighbor'southward value to the current element. (The purpose of the arithmetic is to catechumen between the chemical element'south array index and its (x, y) coordinates in the matrix representation, while taking into account periodic boundary conditions: if you leave the matrix on the left, you loop back in on the correct and vice versa; same for top and bottom.)

5

The d3.interval() function returns a timer that invokes the specified callback at a configurable frequency. Hither, it calls the update() role every dt milliseconds and then updates the graph elements with the new information. The updates are smoothed by a transition, which is delayed according to the position of the chemical element in the assortment. The delay is short compared with the duration of the transition. The effect is that the update sweeps from top to bottom across the figure.

1 See the MDN Event Reference for more information.

2 If you lot want to access this in a callback, you must use the part keyword to ascertain the callback; you cannot use an pointer part.

3 They are: screen, relative to the border of the physical screen; client, relative to the edge of the browser window; and page, relative to the border of the document itself. Due to the placement of the window on the screen, and to the scrolling of the page within the browser, these three volition generally differ.

4 If you endeavour to implement the current example without using the D3 drag facility, you lot may occasionally observe spurious user interface behavior. This is likely the browser's default activeness interfering with the intended behavior. The remedy is to phone call d3.event.preventDefault() in the mousemove handler. See Appendix C for more information.

5 Run into http://mathworld.wolfram.com/LissajousCurve.html.

6 See http://mathworld.wolfram.com/VoterModel.html.

lopezsamphy.blogspot.com

Source: https://www.oreilly.com/library/view/d3-for-the/9781492046783/ch04.html

0 Response to "Unstick Node if User Double Clicks Again D3"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel