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
Part | Description |
---|---|
| Any mouse button is pressed and released on an element. |
| The mouse is moved while over an element. |
| A mouse push is pressed or released over an chemical element. |
| The mouse arrow is moved onto or off of an element. |
| The mouse pointer is moved onto or off of an element, or any of its children. |
| 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.
Function | Description |
---|---|
| Adds or removes a callback for each chemical element in the selection. The
|
| Contains the current event, if any, equally a DOM |
| Returns a 2-chemical element array containing the mouse coordinates relative to the specified parent. |
| 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.
-
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! -
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). -
Obtain the mouse coordinates, relative to the upper-left corner of the
<svg>
element, using thed3.mouse()
convenience function. -
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).
Example 4-2. Commands for Figure 4-1
role
makeBrush
(
)
{
d3
.
csv
(
"dense.csv"
)
.
then
(
office
(
data
)
{
var
svg1
=
d3
.
select
(
"#brush1"
)
;
var
svg2
=
d3
.
select
(
"#brush2"
)
;
var
sc1
=
d3
.
scaleLinear
(
)
.
domain
(
[
0
,
10
,
50
]
)
.
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
)
;
var
cs2
=
drawCircles
(
svg2
,
data
,
d
=>
d
[
"A"
]
,
d
=>
d
[
"C"
]
,
sc2
)
;
svg1
.
call
(
installHandlers
,
data
,
cs1
,
cs2
,
sc1
,
sc2
)
;
}
)
;
}
role
drawCircles
(
svg
,
data
,
accX
,
accY
,
sc
)
{
var
colour
=
sc
(
Infinity
)
;
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
)
{
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
;
return
sc1
(
r
)
;
}
)
;
cs2
.
attr
(
"fill"
,
(
d
,
i
)
=>
sc2
(
data
[
i
]
[
"r"
]
)
)
;
}
)
.
on
(
"mouseleave"
,
function
(
)
{
cs1
.
attr
(
"fill"
,
sc1
(
Infinity
)
)
;
cs2
.
attr
(
"fill"
,
sc2
(
Infinity
)
)
;
}
)
;
}
-
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
, andC
. -
Select the two panels of the graph.
-
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.)
-
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. -
Phone call the function
installHandlers()
to register the consequence handlers. This line of lawmaking uses thephone call()
facility to invoke theinstallHandlers()
function, while supplying thesvg1
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.) -
Initially, the circles are drawn with the "maximum" colour. To notice this color, evaluate the color calibration at positive infinity.
-
For each point in the panel on the left, calculate its distance to the mouse pointer…
-
… 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.)
-
Return the advisable color from the color gradient.
-
Use the boosted column in the data set to colour the points in the console on the right.
-
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
)
.
attr
(
"fill"
,
"none"
)
.
attr
(
"stroke"
,
"black"
)
.
attr
(
"stroke-width"
,
x
)
.
attr
(
"stroke-opacity"
,
0.ane
)
.
attr
(
"visibility"
,
"hidden"
)
;
var
hotzone
=
svg
.
append
(
"rect"
)
.
attr
(
"cursor"
,
"none"
)
.
attr
(
"ten"
,
50
)
.
attr
(
"y"
,
fifty
)
.
attr
(
"width"
,
200
)
.
attr
(
"height"
,
200
)
.
attr
(
"visibility"
,
"subconscious"
)
.
attr
(
"arrow-events"
,
"all"
)
.
on
(
"mouseenter"
,
function
(
)
{
cursor
.
attr
(
"visibility"
,
"visible"
)
;
}
)
.
on
(
"mousemove"
,
office
(
)
{
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
)
)
;
}
)
}
-
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.
-
Initially, the circle is subconscious. It volition merely be shown once the mouse pointer enters the "hot zone."
-
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.
-
The rectangle is hidden from view. By default, DOM elements that have their
visibility
attribute set tohidden
practice not receive mouse pointer events. To overcome this, thepointer-events
attribute must be set explicitly. (Some other way to make an element invisible is to prepare itsmake full-opacity
to0
. In this case, it volition not be necessary to alter thepointer-events
attribute.) -
When the mouse enters the "hot zone," the opaque circle that acts every bit the pointer is displayed.
-
The
mousemove
andmouseleave
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>
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.
-
Create a
elevate
function object using the manufactory roled3.elevate()
, then invoke theon()
fellow member function on the returned function object to register the required callbacks. -
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 aSelection
) towidget
. -
The
elevate
handler retrieves the current mouse coordinates and moves the selected circle to this location. -
The
finish
handler restores the circumvolve's color and clears the agilewidget
. -
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 sameChoice
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 thefunction
keyword, you lot cannot utilise an arrow function. This is a limitation of the JavaScript language (meet Appendix C). Examples can be plant in theinstallHandlers()
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 tohidden
, or with bothfill
andstroke
fix tonone
, do not receive pointer events by default. Use thepointer-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):
-
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()
orinsert()
)—fifty-fifty if they are initially gear up to exist invisible! (TheTransition
API allows you to change and remove elements, but it does non provide for the creation of elements as part of the transition.) -
Now select the elements you wish to alter using the familiar
Selection
API. -
Invoke
transition()
on this selection to create a transition. Optionally, callduration()
,filibuster()
, orease()
for more command over its behavior. -
Ready the desired end country using
attr()
ormode()
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.
Function | Clarification |
---|---|
| 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 |
| 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.) |
| 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. |
| 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.
Office | Description |
---|---|
| 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 |
| 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 |
| 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 |
| Adds an event handler on the transition. The blazon must be |
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.
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
]
;
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
]
)
)
;
var
svg
=
d3
.
select
(
"#stagger"
)
;
var
scX
=
d3
.
scaleLinear
(
)
.
domain
(
[
0
,
due north
]
)
.
range
(
[
50
,
540
]
)
;
var
scY
=
d3
.
scaleLinear
(
)
.
domain
(
[
0
,
mx
]
)
.
range
(
[
250
,
fifty
]
)
;
svg
.
selectAll
(
"line"
)
.
data
(
ds1
)
.
enter
(
)
.
append
(
"line"
)
.
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
(
)
{
[
ds1
,
ds2
]
=
[
ds2
,
ds1
]
;
svg
.
selectAll
(
"line"
)
.
data
(
ds1
)
.
transition
(
)
.
elapsing
(
one thousand
)
.
delay
(
(
d
,
i
)
=>
200
*
i
)
.
attr
(
"y2"
,
d
=>
scY
(
d
)
)
;
}
)
;
}
-
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.
-
Find the number of data points, and the overall maximal value beyond both data sets.
-
Two scale objects that map the values in the data set to vertical, and their index positions in the array to horizontal pixel coordinates.
-
Create the bar nautical chart. Each "bar" is realized as a thick line (rather than a
<rect>
element). -
Register an result handler for
"click"
events. -
Interchange the information sets.
-
Bind the (updated) data prepare
ds1
to the option… -
… 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.
-
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!)
Function | Description |
---|---|
| Sets a custom interpolator for the named attribute. The second argument must exist a manufactory method that returns an interpolator. |
| Sets a custom interpolator for the named style. The 2d argument must be a factory method that returns an interpolator. |
| 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).
Function | Clarification |
---|---|
| 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 |
| Similar |
| Similar to |
| Stops this timer. Has no result if the timer is already stopped. |
| 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
}
);
}
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
)
.
map
(
d
=>
{
return
{
x
:
d
%
n
,
y
:
d
/
northward
|
0
,
val
:
Math
.
random
(
)
}
}
)
;
var
sc
=
d3
.
scaleQuantize
(
)
.
range
(
[
"white"
,
"red"
,
"black"
]
)
;
svg
.
selectAll
(
"rect"
)
.
data
(
data
)
.
enter
(
)
.
suspend
(
"rect"
)
.
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
(
)
{
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
(
)
{
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
)
;
}
-
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.) -
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.) -
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.
-
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.)
-
The
d3.interval()
function returns a timer that invokes the specified callback at a configurable frequency. Hither, it calls theupdate()
role everydt
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.
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