Interacting with your Visualization

Exclusive offer: get 50% off this eBook here
Data Visualization with D3.js Cookbook

Data Visualization with D3.js Cookbook — Save 50%

Over 70 recipes to create dynamic data-driven visualization with D3.js with this book and ebook

$26.99    $13.50
by Nick Qi Zhu | October 2013 | Cookbooks Open Source Web Development

In this article by Nick Qi Zhu, author of the book Data Visualization with D3.js Cookbook we will focus on D3 human visualization interaction support, or in other words how to add computational steering capability to your visualization.

In this article we will cover:

  • Interacting with the mouse
  • Interacting with a multi-touch device
  • Implementing zoom and pan behavior
  • Implementing the drag behavior

(For more resources related to this topic, see here.)

The ultimate goal of visualization design is to optimize applications so that they help us perform cognitive work more efficiently.

Ware C. (2012)

The goal of data visualization is to help the audience gain information from a large quantity of raw data quickly and efficiently through metaphor, mental model alignment, and cognitive magnification. So far in this article we have introduced various techniques to leverage D3 library implementing many types of visualization. However, we haven't touched a crucial aspect of visualization: human interaction. Various researches have concluded the unique value of human interaction in information visualization.

Visualization combined with computational steering allows faster analyses of more sophisticated scenarios...This case study adequately demonstrate that the interaction of a complex model with steering and interactive visualization can extend the applicability of the modelling beyond research

Barrass I. & Leng J (2011)

In this article we will focus on D3 human visualization interaction support, or as mentioned earlier learn how to add computational steering capability to your visualization.

Interacting with mouse events

The mouse is the most common and popular human-computer interaction control found on most desktop and laptop computers. Even today, with multi-touch devices rising to dominance, touch events are typically still emulated into mouse events; therefore making application designed to interact via mouse usable through touches. In this recipe we will learn how to handle standard mouse events in D3.

Getting ready

Open your local copy of the following file in your web browser:

https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter10/mouse.html

How to do it...

In the following code example we will explore techniques of registering and handling mouse events in D3. Although, in this particular example we are only handling click and mousemove, the techniques utilized here can be applied easily to all other standard mouse events supported by modern browsers:

<script type="text/javascript"> var r = 400; var svg = d3.select("body") .append("svg"); var positionLabel = svg.append("text") .attr("x", 10) .attr("y", 30); svg.on("mousemove", function () { //<-A printPosition(); }); function printPosition() { //<-B var position = d3.mouse(svg.node()); //<-C positionLabel.text(position); } svg.on("click", function () { //<-D for (var i = 1; i < 5; ++i) { var position = d3.mouse(svg.node()); var circle = svg.append("circle") .attr("cx", position[0]) .attr("cy", position[1]) .attr("r", 0) .style("stroke-width", 5 / (i)) .transition() .delay(Math.pow(i, 2.5) * 50) .duration(2000) .ease('quad-in') .attr("r", r) .style("stroke-opacity", 0) .each("end", function () { d3.select(this).remove(); }); } }); </script>

This recipe generates the following interactive visualization:

Mouse Interaction

How it works...

In D3, to register an event listener, we need to invoke the on function on a particular selection. The given event listener will be attached to all selected elements for the specified event (line A). The following code in this recipe attaches a mousemove event listener which displays the current mouse position (line B):

svg.on("mousemove", function () { //<-A printPosition(); }); function printPosition() { //<-B var position = d3.mouse(svg.node()); //<-C positionLabel.text(position); }

On line C we used d3.mouse function to obtain the current mouse position relative to the given container element. This function returns a two-element array [x, y]. After this we also registered an event listener for mouse click event on line D using the same on function:

svg.on("click", function () { //<-D for (var i = 1; i < 5; ++i) { var position = d3.mouse(svg.node()); var circle = svg.append("circle") .attr("cx", position[0]) .attr("cy", position[1]) .attr("r", 0) .style("stroke-width", 5 / (i)) // <-E .transition() .delay(Math.pow(i, 2.5) * 50) // <-F .duration(2000) .ease('quad-in') .attr("r", r) .style("stroke-opacity", 0) .each("end", function () { d3.select(this).remove(); // <-G }); } });

Once again, we retrieved the current mouse position using d3.mouse function and then generated five concentric expanding circles to simulate the ripple effect. The ripple effect was simulated using geometrically increasing delay (line F) with decreasing stroke-width (line E). Finally when the transition effect is over, the circles were removed using transition end listener (line G).

There's more...

Although, we have only demonstrated listening on the click and mousemove events in this recipe, you can listen on any event that your browser supports through the on function. The following is a list of mouse events that are useful to know when building your interactive visualization:

  • click: Dispatched when user clicks a mouse button
  • dbclick: Dispatched when a mouse button is clicked twice
  • mousedown: Dispatched when a mouse button is pressed
  • mouseenter: Dispatched when mouse is moved onto the boundaries of an element or one of its descendent elements
  • mouseleave: Dispatched when mouse is moved off of the boundaries of an element and all of its descendent elements
  • mousemove: Dispatched when mouse is moved over an element
  • mouseout: Dispatched when mouse is moved off of the boundaries of an element
  • mouseover: Dispatched when mouse is moved onto the boundaries of an element
  • mouseup: Dispatched when a mouse button is released over an element

Interacting with a multi-touch device

Today, with the proliferation of multi-touch devices, any visualization targeting mass consumption needs to worry about its interactability not only through the traditional pointing device, but through multi-touches and gestures as well. In this recipe we will explore touch support offered by D3 to see how it can be leveraged to generate some pretty interesting interaction with multi-touch capable devices.

Getting ready

Open your local copy of the following file in your web browser:

https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter10/touch.html.

How to do it...

In this recipe we will generate a progress-circle around the user's touch and once the progress is completed then a subsequent ripple effect will be triggered around the circle. However, if the user prematurely ends his/her touch, then we shall stop the progress-circle without generating the ripples:

<script type="text/javascript"> var initR = 100, r = 400, thickness = 20; var svg = d3.select("body") .append("svg"); d3.select("body") .on("touchstart", touch) .on("touchend", touch); function touch() { d3.event.preventDefault(); var arc = d3.svg.arc() .outerRadius(initR) .innerRadius(initR - thickness); var g = svg.selectAll("g.touch") .data(d3.touches(svg.node()), function (d) { return d.identifier; }); g.enter() .append("g") .attr("class", "touch") .attr("transform", function (d) { return "translate(" + d[0] + "," + d[1] + ")"; }) .append("path") .attr("class", "arc") .transition().duration(2000) .attrTween("d", function (d) { var interpolate = d3.interpolate( {startAngle: 0, endAngle: 0}, {startAngle: 0, endAngle: 2 * Math.PI} ); return function (t) { return arc(interpolate(t)); }; }) .each("end", function (d) { if (complete(g)) ripples(d); g.remove(); }); g.exit().remove().each(function () { this.__stopped__ = true; }); } function complete(g) { return g.node().__stopped__ != true; } function ripples(position) { for (var i = 1; i < 5; ++i) { var circle = svg.append("circle") .attr("cx", position[0]) .attr("cy", position[1]) .attr("r", initR - (thickness / 2)) .style("stroke-width", thickness / (i)) .transition().delay(Math.pow(i, 2.5) * 50) .duration(2000).ease('quad-in') .attr("r", r) .style("stroke-opacity", 0) .each("end", function () { d3.select(this).remove(); }); } } </script>

This recipe generates the following interactive visualization on a touch enabled device:

Touch Interaction

How it works...

Event listener for touch events are registered through D3 selection's on function similar to what we have done with mouse events in the previous recipe:

d3.select("body") .on("touchstart", touch) .on("touchend", touch);

One crucial difference here is that we have registered our touch event listener on the body element instead of the svg element since with many OS and browsers there are default touch behaviors defined and we would like to override it with our custom implementation. This is done through the following function call:

d3.event.preventDefault();

Once the touch event is triggered we retrieve multiple touch point data using the d3.touches function as illustrated by the following code snippet:

var g = svg.selectAll("g.touch") .data(d3.touches(svg.node()), function (d) { return d.identifier; });

Instead of returning a two-element array as what d3.mouse function does, d3.touches returns an array of two-element arrays since there could be multiple touch points for each touch event. Each touch position array has data structure that looks like the following:

Touch Position Array

Other than the [x, y] position of the touch point each position array also carries an identifier to help you differentiate each touch point. We used this identifier here in this recipe to establish object constancy. Once the touch data is bound to the selection the progress circle was generated for each touch around the user's finger:

g.enter() .append("g") .attr("class", "touch") .attr("transform", function (d) { return "translate(" + d[0] + "," + d[1] + ")"; }) .append("path") .attr("class", "arc") .transition().duration(2000).ease('linear') .attrTween("d", function (d) { // <-A var interpolate = d3.interpolate( {startAngle: 0, endAngle: 0}, {startAngle: 0, endAngle: 2 * Math.PI} ); return function (t) { return arc(interpolate(t)); }; }) .each("end", function (d) { // <-B if (complete(g)) ripples(d); g.remove(); });

This is done through a standard arc transition with attribute tweening (line A). Once the transition is over if the progress-circle has not yet been canceled by the user then a ripple effect similar to what we have done in the previous recipe was generated on line B. Since we have registered the same event listener touch function on both touchstart and touchend events, we can use the following lines to remove progress-circle and also set a flag to indicate that this progress circle has been stopped prematurely:

g.exit().remove().each(function () { this.__stopped__ = true; });

We need to set this stateful flag since there is no way to cancel a transition once it is started; hence, even after removing the progress-circle element from the DOM tree the transition will still complete and trigger line B.

There's more...

We have demonstrated touch interaction through the touchstart and touchend events; however, you can use the same pattern to handle any other touch events supported by your browser. The following list contains the proposed touch event types recommended by W3C:

  • touchstart: Dispatched when the user places a touch point on the touch surface
  • touchend: Dispatched when the user removes a touch point from the touch surface
  • touchmove: Dispatched when the user moves a touch point along the touch surface
  • touchcancel: Dispatched when a touch point has been disrupted in an implementation-specific manner
Data Visualization with D3.js Cookbook Over 70 recipes to create dynamic data-driven visualization with D3.js with this book and ebook
Published: October 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

Implementing zoom and pan behavior

Zooming and panning are common and useful techniques in data visualization, which work particularly well with SVG based visualization since vector graphic does not suffer from pixelation as its bitmap counterpart would. Zooming is especially useful when dealing with large data set when it is impractical or impossible to visualize the entire data set, thus a zoom and drill-down approach needs to be employed. In this recipe we will explore D3's built-in support for both zooming and panning.

Getting ready

Open your local copy of the following file in your web browser:

https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter10/zoom.html.

How to do it...

In this recipe we will implement geometric zooming and panning using D3 zoom support. Let's see how this is done in code:

<script type="text/javascript"> var width = 960, height = 500, r = 50; var data = [ [width / 2 - r, height / 2 - r], [width / 2 - r, height / 2 + r], [width / 2 + r, height / 2 - r], [width / 2 + r, height / 2 + r] ]; var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .call( d3.behavior.zoom() .scaleExtent([1, 10]) .on("zoom", zoom) ) .append("g"); svg.selectAll("circle") .data(data) .enter().append("circle") .attr("r", r) .attr("transform", function (d) { return "translate(" + d + ")"; }); function zoom() { svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } </script>

This recipe generates the following zooming and panning effect:

Original

Zoom

Pan

How it works...

At this point you might be surprised to see how little code is necessary to implement this fully-functional zoom and pan effect with D3. If you have this recipe open in your browser, you will also notice zooming and panning reacts perfectly well to both mouse wheel and multi-touch gesture. Most of the heavy lifting is done by D3 library. What we have to do here is to simply define what zoom behavior is. Let's see how this is done in the code. Firstly, we need to define zoom behavior on a SVG container:

var svg = d3.select("body").append("svg") .attr("style", "1px solid black") .attr("width", width) .attr("height", height) .call( // <-A d3.behavior.zoom() // <-B .scaleExtent([1, 10]) // <-C .on("zoom", zoom) // <-D ) .append("g");

As we can see on line A, a d3.behavior.zoom function was created (line B) and invoked on the svg container. d3.behavior.zoom will automatically create event listeners to handle the low-level zooming and panning gesture on the associated SVG container (in our case the svg element itself). The low-level zoom gesture will then be translated to a high-level D3 zoom event. The default event listeners support both mouse and touch events. On line C we define scaleExtent with a 2-element array [1, 10] (a range). The scale extent defines how much zoom should be allowed (in our case we allow 10X zoom). Finally, on line D we register a custom zoom event handler to handle D3 zoom events. Now, let's take a look at what job this zoom event handler performs:

function zoom() { svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); }

In the zoom function we simply delegate the actual zooming and panning to SVG transformation. To further simplify this task D3 zoom event has also calculated necessary translate and scale. So all we need to do is embed them into SVG transform attribute. Here are the properties contained in a zoom event:

  • scale: A number representing the current scale
  • translate: A two-element array representing the current translation vector

At this point you might be asking what is the point of having this zoom function. Why can't D3 take care of this step for us? The reason is that D3 zoom behavior is not designed specifically for SVG, but rather designed as a general zoom behavior support mechanism. Therefore, this zoom function implements the translation of general zoom and pan events into SVG specific transformation.

There's more...

The zoom function is also capable of performing additional tasks other than simple coordinate system transformation. For example, a common technique is to load additional data when the user issues a zoom gesture, hence implementing the drill-down capability in zoom function. A well-known example is a digital map; as you increase zoom level on a map, more data and details then can be loaded and illustrated.

Implementing drag behavior

Another common behavior in interactive visualization that we will cover in this article is drag. Drag is useful to provide capabilities in visualization allowing graphical rearrangement or even user input through force. In this recipe we will explore how drag behavior is supported in D3.

Getting ready

Open your local copy of the following file in your web browser:

https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter10/drag.html.

How to do it...

Here, we will produce four circles that can be dragged using D3 drag behavior support and additionally with SVG boundary detection while dragging. Now, let's see how to implement this in code:

<script type="text/javascript"> var width = 960, height = 500, r = 50; var data = [ [width / 2 - r, height / 2 - r], [width / 2 - r, height / 2 + r], [width / 2 + r, height / 2 - r], [width / 2 + r, height / 2 + r] ]; var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append("g"); var drag = d3.behavior.drag() .on("drag", move); svg.selectAll("circle") .data(data) .enter().append("circle") .attr("r", r) .attr("transform", function (d) { return "translate(" + d + ")"; }) .call(drag); function move(d) { var x = d3.event.x, y = d3.event.y; if(inBoundaries(x, y)) d3.select(this) .attr("transform", function (d) { return "translate(" + x + ", " + y + ")"; }); } function inBoundaries(x, y){ return (x >= (0 + r) && x <= (width - r)) && (y >= (0 + r) && y <= (height - r)); } </script>

This recipe generates drag behavior on the following four circles:

Original

Dragged

How it works...

As we can see, similar to D3 zoom support, drag support follows a similar pattern. The main drag capability is provided by d3.behavior.drag function (line A). D3 drag behavior automatically creates appropriate low-level event listeners to handle drag gestures on the given element then translates low-level events to high-level D3 drag events. Both mouse and touch events are supported:

var drag = d3.behavior.drag() // <-A .on("drag", move);

In this recipe we are interested in the drag event and it is handled by our move function. Similar to the zoom behavior, D3 drag behavior support is event driven, therefore, allowing maximum flexibility in implementation, supporting not only SVG but also the HTML5 canvas. Once defined, the behavior can be attached to any element by calling it on a given selection:

svg.selectAll("circle") .data(data) .enter().append("circle") .attr("r", r) .attr("transform", function (d) { return "translate(" + d + ")"; }) .call(drag); // <-B

Next, in the move function we simply use SVG transformation to move the dragged element to proper location (line D) based on the information conveyed by the drag event (line C):

function move(d) { var x = d3.event.x, // <-C y = d3.event.y; if(inBoundaries(x, y)) d3.select(this) .attr("transform", function (d) { // <-D return "translate(" + x + ", " + y + ")"; }); }

One additional condition we check here is to calculate the SVG boundaries constraint so the user cannot drag an element outside of the SVG. This is achieved by the following check:

function inBoundaries(x, y){ return (x >= (0 + r) && x <= (width - r)) && (y >= (0 + r) && y <= (height - r)); }

There's more...

Other than the drag event, D3 drag behavior also supports two other event types. The following list shows all supported drag event types and their attributes:

  • dragstart: Triggered when a drag gesture starts.
  • drag: Fired when the element is dragged. d3.event will contain x and y properties representing the current absolute drag coordinates of the element. It will also contain dx and dy properties representing the element's coordinates relative to its position at the beginning of the gesture.
  • dragend: Triggered when a drag gesture has finished.

Summary

Thus in this article we learned about the interactons with various devices.

Resources for Article:


Further resources on this subject:


Data Visualization with D3.js Cookbook Over 70 recipes to create dynamic data-driven visualization with D3.js with this book and ebook
Published: October 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Nick Qi Zhu

Nick Qi Zhu is a professional programmer and visualization enthusiast with more than a decade of experience in software development. He is the author of dc.js—a popular multidimensional charting library built on D3. Currently he is having fun and learning as a lead consultant at ThoughtWorks.

Books From Packt


Data Visualization: a successful design process
Data Visualization: a successful design process

Circos Data Visualization How-to [Instant]
Circos Data Visualization How-to [Instant]

Data Visualization with d3.js
Data Visualization with d3.js

Python Data Visualization Cookbook
Python Data Visualization Cookbook

 Learning QlikView Data Visualization
Learning QlikView Data Visualization

Tableau Data Visualization Cookbook
Tableau Data Visualization Cookbook

 HTML5 Graphing and Data Visualization Cookbook
HTML5 Graphing and Data Visualization Cookbook

Google Visualization API Essentials
Google Visualization API Essentials


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
H
v
P
Z
f
4
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software