Chapter 08Events
In this section we discuss adding and handling events using D3.js.
When displaying our visualizations in a webpage, it may help our readers if our visualizations are interactive. Buttons that alter the graphic and tooltips that display additional information are good ways to help convey the information in our visualizations.
To add in these interactive features to our visualizations we will need to use events
. When adding an event to any element or node we need to set two things: the event type and the listener function.
The event type is what scenario the element is checking for, such as when the mouse comes onto or goes off of the element (onmouseenter
and onmouseout
respectively). The listener is the function that is called whenever the event is triggered. For example, our listener function could change the color of an element whenever the mouse goes over the element.
A complete list of event types, properties, and methods.
Adding an event using HTML
One way to add events to our elements is to set the event in our html
file. To do this we name the event type and set its listener function in the .html
file that our page loads.
For example to set a <circle>
event listener:
<circle id="id" onmouseenter="foo(this, event)"></circle>
In the above example we set the onmouseenter
event to call foo
when it is triggered. We also pass this
and event
into foo
. this
is a reference to the node that called the event, in this case the <circle>
node. event
is a reference to the event itself that was called; it contains various properties and methods of the event.
In Figure 1, we set onmouseenter
and onmouseout
and set their respective listener functions. The circle will change colors when the mouse comes over it, and again when the mouse leaves it.
<script> function mouseEnter(node){ d3.select(node) .attr("fill", "green"); } function mouseOut(node){ d3.select(node) .attr("fill", "red"); } </script> <svg id="demoEvent" width=200 height=200> <circle id="circleColorChangeH" cx=100 cy=100 r=50 onmouseenter="mouseEnter(this)" onmouseout="mouseOut(this)" ></circle> </svg>
Adding an event using javascript
Adding our events into the HTML code works when we already have the elements in the .html
file, but if we are dynamically adding elements and events to our page this method will not work. Instead, the typical way to add event listeners with JavaScript is to use node.
. To use node.
we pass in the type
(such as mouseenter
or mousemove
) and the listener
(the function called when the event is triggered).
document.getElementById(id) .addEventListener("mouseenter", function(event){ //do stuff });
It is important to note that in the last example we added onmouseenter
in our .html
but when we are adding event in JavaScript we drop the on-
and use mouseenter
. This pattern holds true in all event types that start with on-
.
<script> var selection = d3.select("#demoJSEvent"); document.getElementById("circleColorChangeJ") .addEventListener("mouseenter", function(event){ selection .select("#circleColorChangeJ") .attr("fill", "red"); }); document.getElementById("circleColorChangeJ") .addEventListener("mouseout", function(event){ selection .select("#circleColorChangeJ") .attr("fill", "green"); }); </script> <svg id="demoJSEvent" width=200 height=200> <circle id="circleColorChangeJ" cx=100 cy=100 r=50></circle> </svg>
Adding an event using D3.js
Now we know how to add events to elements in JavaScript; however, the addEventListener
method only works on a single element at a time. We could make a for
loop to iterate over multiple elements, but this would be cumbersome, especially when working with selections from D3.js.
Luckily D3.js allows us to add an event to every node in a selection at once with selection.
. We use selection.on
in a similar way as addEventListener
.
First we pass in the the type of event we want to add; however, now we can add multiple events at the same time by separating the event types with a space such as "mouseenter mouseout"
. Next we pass in the listener
function for the event(s). This listener
will replace any current listener
applied to that event on that node. If we want to get rid of a listener
, we can choose to not pass one in. Finally, we can choose to add optional parameters to our event listener as outlined in this event.addEventListener documentation.
When creating the listener
we can use either this
or nodes[i]
to refer to the node that triggered the event.
For example to have an element change to a random color everytime the mouse comes onto and leaves the element we could use:
d3.select(id) .on("mouseenter mouseout", function(){ d3.select(this) .attr("fill", randomColor); });
-
selection.on(typenames[,listener[, options]]) - Sets (or removes) a
listener
on thetypenames
event(s).
In Figure 3 we use selection.on
to add our event listeners to a circle. The circle turns green when the mouse goes over it, and red when the mouse leaves.
<script> d3.select("#demoSelectOn") .select("#circleColorChangeD") .on("mouseenter", (d,i,n) => n[i].setAttribute("fill", "green")) .on("mouseout", (d,i,n) => n[i].setAttribute("fill", "red")); </script> <svg id="demoSelectOn" width=200px height=200px> <circle id="circleColorChangeD" cx=100 cy=100 r=50></circle> </svg>
d3.event
Using selection.on
has additional advantages when it comes to handling events. Any event that is registered by selection.on
will be accessible inside the event handler with the static field d3.event
. d3.event
always points to the current event being invoked, so it is useful inside event functions to access various fields or methods on the event (such as event.timeStamp
or event.
).
-
d3.event - Returns the current event being invoked. This contains various fields such as event.pathX/Y and event.timeStamp and methods like event.preventDefault(). Only works when the event handler that is triggered was registered by
selection.on
In Figure 4 we show an example using d3.
.
<script> d3.select("#circleTime") .on("click", function(){ d3.select("#textTime") .text(Math.round(d3.event.timeStamp) + "ms"); }); </script> <svg id="demoTime" width=200 height=200> <circle id="circleTime" cx="100" cy="100" r="50"></circle> <text id= "textTime" x="100" y="190" text-anchor="middle" font-size="16px"></text> </svg>
d3.mouse
One situation we may run into is needing to know where an event is triggered. d3.event
contains the fields event.pathX
and event.pathY
which tell us the position of the mouse when the event was triggered, but this position is in relation to the entire HTML document. So while it may help us in some situations, it won’t help us figure out where the mouse is within our visualizations. To aid in this scenario D3.js provides us with d3.
.
When we call d3.
we pass in the node that we want to have our event position related to. d3.mouse
will return back an array of the x
and y
positions of where the current event was triggered. In many cases, we will pass in the svg
node that our visualizations lie in.
-
d3.mouse(container) - Returns the location of the current event relative to the specified
container
. A container is an HTML node. Uses the same event that is ind3.event
, so the event must have been registered byselection.on
.
In Figure 5 we use d3.mouse
and pass in the svg node. We use the position it returns to update a text element to show the position.
<script> d3.select("#demoMouseMove") .on("mousemove", function() { let pos = d3.mouse(this); d3.select(this) .append("circle") .attr("fill", "red") .attr("r", 1.5) .attr("cx", pos[0]) .attr("cy", pos[1]); }); </script> <svg id="demoMouseMove" width=200px height=200px> <path d="M0,0 L200,0 L200,200 L0,200 Z" stroke="black" stroke-width="5px" fill="none"></path>> </svg>
d3.clientPoint
There are some situations where we cannot use selection.on
to add an event. However, we may still need to know the position of the mouse. Since we cannot use d3.mouse
or d3.event
, D3.js provides us with an alternate method to find the position of an event, d3.
. Just like with d3.mouse
we pass in the container
(a node), but now we also have to pass in the event to find its location.
-
d3.clientPoint(container, event) - Returns the location of the event passed in relative to the specified
container
. This method accepts any event, not justd3.event
, so it is useful for event handlers registered outside ofselection.on
.
In Figure 6 we use d3.clientPoint
to find the position of the mouse event. The event was created in the HTML and it passes the event to the findPosition
function that finds the location.
<script> var clientNode = d3.select("#demoClient").node(); function createDot(event){ let pos = d3.clientPoint(clientNode, event); d3.select(clientNode) .append("circle") .attr("fill", "red") .attr("r", 1.5) .attr("cx", pos[0]) .attr("cy", pos[1]); } </script> <svg id="demoClient" width=200px height=200px onmousemove="createDot(event)"> <path d="M0,0 L200,0 L200,200 L0,200 Z" stroke="black" stroke-width="5px" fill="none"></path> </svg>