|
<html> |
|
<head> |
|
<meta http-equiv="content-type" content="text/html;charset=UTF-8"> |
|
<title>D3: Getting Interactive With Your SVG Elements</title> |
|
<link rel="stylesheet" type="text/css" href="./slides.css" /> |
|
<link rel="stylesheet" type="text/css" href="./prism.css" /> |
|
</head> |
|
<body> |
|
<div class="title"> |
|
<a href="http://artandlogic.com/2015/09/d3-getting-interactive-with-your-svg-elements/">D3: Getting Interactive With Your SVG Elements</a> |
|
</div> |
|
|
|
<section id="slide"> |
|
<div class="heading"></div> |
|
<div id="demo"> |
|
<div id="widgets"></div> |
|
<button id="execute">Run Again</button> |
|
</div> |
|
<div class="content"></div> |
|
</section><section id="src"> |
|
<div class="heading">Source</div> |
|
</section> |
|
|
|
<!-- TEMPLATES --> |
|
<script id="links" type="text/template"><div class="links"> |
|
<a data-nav="prev" class="<%=prev_hidden%>" href="<%=prev%>"><%=prev_title%> ←</a> |
|
<a data-nav="next" class="<%=next_hidden%>" href="<%=next%>">→ <%=next_title%></a> |
|
</div></script> |
|
|
|
</script> |
|
<!-- /TEMPLATES --> |
|
|
|
<!-- SLIDES --> |
|
<script type="text/slide" |
|
data-title="Introduction" |
|
data-function="intro" |
|
data-highlight-lines="6"> |
|
<p> |
|
This tutorial will take you through several slides leading up |
|
to demonstrating user interaction with a SVG canvas |
|
using <a href="http://d3js.org/">D3</a>. |
|
</p> |
|
<p> |
|
This tutorial is a single page app requiring viewing in a |
|
modern browser (recent chrome or firefox, for best viewing |
|
pleasure). |
|
</p> |
|
<p> |
|
It is assumed you have some basic familiarity with D3. If |
|
you're new to D3, I'd recommend you first read some |
|
introductory material. I have written two other articles |
|
about D3 you may find interesting: |
|
<a href="http://artandlogic.com/2015/06/d3-data-driven-documents/">Meet |
|
D3: Data Driven Documents</a> |
|
and <a href="http://artandlogic.com/2015/06/d3-binding-data/">D3: |
|
Binding Data</a>. (Each of those articles has links to other |
|
D3 resources.) In this tutorial, it is assumed the reader has |
|
a solid understanding of the information in those articles, or |
|
articles like them. |
|
</p> |
|
<p> |
|
Each slide in the tutorial will have the same format as this |
|
slide: |
|
</p> |
|
<ol> |
|
<li> The left pane will have a narative description of the |
|
current topic. At top and bottom there will be |
|
navigation links taking you to the next or previous |
|
slides. |
|
</li> |
|
<li> In the upper right of the left pane will be a SVG |
|
element. Here you will be able to see (and interact!) |
|
with elements inside the SVG. Just below the SVG is a |
|
button labeled <strong>Run Again</strong>. Depending on |
|
the state of the chart, this button may be disabled by |
|
the demo. Clicking this button will redraw the SVG |
|
element and execute the function you see in the right |
|
pane all over again. |
|
</li> |
|
<li> In the right pane is the javascript function run for each |
|
slide. When a slide is loaded the following steps are |
|
taken: |
|
<ol> |
|
<li> The text of the left pane is assembled along with the |
|
navigation links. |
|
</li> |
|
<li> The javascript source in the right pane is |
|
highlighted and inserted. |
|
</li> |
|
<li> The SVG element is inserted in the left pane. |
|
</li> |
|
<li> The javascript function you see in the right pane is |
|
executed. |
|
</li> |
|
</ol> |
|
Clicking the <strong>Run Again</strong> button does steps |
|
3 and 4 over again. |
|
</li> |
|
</ol> |
|
<p> |
|
In this slide, there is no animation or interaction with the |
|
chart. A red circle is drawn at point 75,200 with radius 50. |
|
You'll notice that in line #6 (highlighted) we assign a |
|
variable to the D3 selection of the SVG element on the page |
|
and then use that variable to append our red circle. Each |
|
page has exactly one SVG element and each function will start |
|
with a line like #6. |
|
</p> |
|
<p> |
|
Jumping right to user interactions would be daunting. We'll |
|
get there, but first we'll explore moving elements around |
|
programatically. Once we see what needs to happen to move |
|
elements around the canvas, we can look at browser event |
|
management to do user interactions. |
|
</p> |
|
<p> |
|
We'll start with something very simple. In the next slide, the |
|
red circle will be moved horizontally to the right. |
|
</p> |
|
</script> |
|
|
|
<script type="text/slide" |
|
data-title="Animate Elements" |
|
data-function="animate" |
|
data-highlight-lines="22"> |
|
<p> |
|
Did it go by too quickly? Click <strong>Run Again</strong> to |
|
see the simple animation again. |
|
</p> |
|
<p> |
|
It's very intuitive: to move the circle element horizontally, |
|
change the X-coordinate of the circle. There are no cirlce |
|
object methods to call! Simply manipulate the |
|
SVG <code><circle></code> element's <code>cx</code> |
|
attribute and the SVG engine built into the browser will do |
|
the rest! |
|
</p> |
|
<p> |
|
At line #16 we define a function to shift the circle to the |
|
right and to reschedule itself if the circle hasn't reached |
|
its destination. The function is scheduled |
|
with <code>setTimeout()</code> at line #35 and, internally, |
|
with each invocation at line #27. All the magic happens at |
|
line #22 (highlighted). |
|
</p> |
|
<p> |
|
Note how the <strong>Run Again</strong> button is disabled at |
|
line #10 and not enabled again |
|
until <code>shift_right()</code> is about to exit for the last |
|
time (line #30). |
|
</p> |
|
<p> |
|
Speaking of those disable/enable functions...where did they |
|
come from? There are six variables available to these |
|
functions (assigned in a closure you don't see), five of which |
|
are used in this function: |
|
</p> |
|
<ul> |
|
<li> |
|
<strong>d3</strong> - The global variable from the d3 |
|
package. |
|
</li> |
|
<li> |
|
<strong>WIDTH</strong> - The width of the SVG element. |
|
</li> |
|
<li> |
|
<strong>HEIGHT</strong> - The height of the SVG element. |
|
</li> |
|
<li> |
|
<strong>disable_run_again()</strong> - A function to disable |
|
the <strong>Run Again</strong> button. |
|
</li> |
|
<li> |
|
<strong>enable_run_again()</strong> - A function to enable |
|
the <strong>Run Again</strong> button. |
|
</li> |
|
<li> |
|
The sixth variable, <strong>el_widgets</strong>, is not used |
|
until the last slide and will be discussed there. |
|
</li> |
|
</ul> |
|
<p> |
|
In the next slide, we'll get more sophisticated and move our |
|
circle according to a formula: tracing the circumference of a |
|
circle. |
|
</p> |
|
|
|
</script> |
|
|
|
<script type="text/slide" |
|
data-title="Animate by Formula" |
|
data-function="formula"> |
|
|
|
<p> |
|
This is conceptually identical to the previous tutorial: we're |
|
moving the circle according to a formula. In the previous |
|
slide, it was a straight line. In this slide, a more complex |
|
formula, moving the red circle around the circumference of |
|
another circle. |
|
</p> |
|
<p> |
|
There is one big difference, however. In the this slide, we |
|
are using <a href="http://artandlogic.com/2015/06/d3-binding-data/">D3 |
|
data binding</a> and update/enter selections to create and |
|
update the circle attributes. |
|
</p> |
|
<p> |
|
The critical piece of data is declared in line |
|
#7, <code>radians</code>, which measures the number of radians |
|
the red circle has traveled around the circumference. In the |
|
function, <code>draw()</code>, we do a data bind to a |
|
single-element array holding the current radian value (line |
|
#25). The first time <code>draw()</code> is called the update |
|
selection will be empty, so the enter selection will be |
|
non-empty and the circle is appended. On subsequent calls, the |
|
update selection will be non-empty, so all that happens is the |
|
cx,cy coordinates get updated (lines #36-42) using |
|
trigonometry to place the circle so many radians around the |
|
circumference. |
|
</p> |
|
<p> |
|
Why use data binding? We could have, like in the previous |
|
slide, created the circle outside of the scheduled moving |
|
function, then inside the scheduled function, just update the |
|
location of the red circle based on the current radian value. |
|
Two reasons: 1) its idiomatic, but more importantly, 2) when |
|
we DO have more than one object we want to manage, the power |
|
of using data binding is unleashed upon our charts. |
|
</p> |
|
<p> |
|
To demonstrate this power, we will look at this function again |
|
with only a few line changes... |
|
</p> |
|
</script> |
|
|
|
<script type="text/slide" |
|
data-title="The Joy of Data Binding" |
|
data-function="formula2" |
|
data-highlight-lines="7-10,28,35-38,54-57"> |
|
|
|
<p> |
|
This slide is nearly identical to the previous slide. The |
|
differences are highlighted in the source: |
|
</p> |
|
<ul> |
|
<li><strong>7-10</strong> - In this slide, <code>radians</code> |
|
is an array of starting locations for each of our circles. |
|
In addition, we use a matching (in length) array of colors, |
|
one for each of our circles. |
|
</li> |
|
<li><strong>28</strong> - A minor difference, |
|
since <code>radians</code> is an array, we can use it |
|
directly, instead of wrapping it in an array literal. |
|
</li> |
|
<li><strong>35-38</strong> - Instead of assigning the color as |
|
a static value, we use a function to grab a color. Here we |
|
use the index of the current datum to fetch the |
|
corresponding color in <code>colors</code>. |
|
</li> |
|
<li><strong>54-57</strong> - We need to update all the radian |
|
values, so we use <code>Array.prototype.map()</code>. As our |
|
sentinel, we use the first element (which started at 0rad) |
|
to see if it has gone all the way around the circumference. |
|
</li> |
|
</ul> |
|
<p> |
|
Consider this: we could easily rewrite the previous slide |
|
using this function with only two changes, to lines #8 and |
|
#10: |
|
</p> |
|
<pre><code> |
|
// in this version, radians is an array |
|
radians = [0], |
|
// and we having a length-matching array for colors |
|
colors = ['red']; |
|
</code></pre> |
|
<p> |
|
Which begs the question: why not always use data binding, even |
|
when you only have one data item? You never know when one will |
|
become many! |
|
</p> |
|
<hr/> |
|
<p> |
|
So far, we have seen only computer generated animation. What |
|
about human interaction? Now that we see moving items is a |
|
simple matter of updating SVG element locaton attributes, we |
|
can use browser events to help us manage element movement... |
|
</p> |
|
</script> |
|
|
|
<script type="text/slide" |
|
data-title="Drag & Drop - The Hard Way" |
|
data-function="dnd"> |
|
<p> |
|
In the chart, we have a red circle that is partially hidden by |
|
a blue square (which has some opacity, so we can see the |
|
circle underneath it). The blue square cannot be moved (it's |
|
an obstacle), but the red circle can: hover the cursor over |
|
the red circle and the cursor will change, indicating it can |
|
be moved. Note how the cursor does not change when it's over |
|
the blue square, even the portion of the square that has the |
|
circle beneath it. Click-hold on the circle and drag the |
|
mouse to the left, pulling the circle out from behind the blue |
|
square. |
|
</p> |
|
<p> |
|
The first half of the function looks familiar: set up some |
|
variables and then create our circle and rectangle. The rest |
|
of the function is something we haven't seen, yet: we set up a |
|
series of event listeners on our circle element: |
|
</p> |
|
<ul> |
|
<li><strong>mousedown</strong> - when the mouse is clicked on |
|
the circle, the <code>dragging</code> flag is set to true. |
|
</li> |
|
<li><strong>mouseup, mouseleave</strong> - when the mouse is |
|
released, or the mouse leaves the circle, the flag is set to |
|
false. |
|
</li> |
|
<li><strong>mousemove</strong> - when the mouse is moved while |
|
over the circle AND the flag is true (which means the user |
|
must be holding the mouse click), the circle will move along |
|
with the mouse. Since the circle moves with the mouse, |
|
the <strong>mouseleave</strong> event will not be fired |
|
until you hit the edge of the canvas or cross paths with |
|
that pesky blue square. |
|
</li> |
|
</ul> |
|
<p> |
|
Note line #44. The function <code>d3.mouse(container)</code> |
|
returns an array of the x,y coordinates of the mouse relative |
|
to the container (typically the SVG element or a G element). |
|
Here we use the circle as the parameter in which case the |
|
container holding the circle will be used. Using these |
|
coordinates will place the center of the circle under the |
|
mouse, regardless of where you first click on the mouse. |
|
</p> |
|
<p> |
|
It works. Yay! But it's clunky, particularly how the browser |
|
behaves when you get to the edges of the canvas. And that |
|
dang blue square! Let's take care of those issues... |
|
</p> |
|
</script> |
|
|
|
<script type="text/slide" |
|
data-title="Drag & Drop - The Hard Way (cont'd)" |
|
data-function="dnd2" |
|
data-highlight-lines="25-27,51-54"> |
|
<p> |
|
This version is nearly identical to the previous slide, with |
|
only two changes: |
|
</p> |
|
<ul> |
|
<li><strong>25-27</strong> - With SVG, elements that appear |
|
later in the DOM are <em>on top</em> of earlier elements, i.e, |
|
if two elements overlap, the element that was <em>last</em> in |
|
the DOM will appear on top and by default, receive all browser |
|
events. This is why the blue square is over the red circle |
|
and why mouse events on the circle, when obscured by the blue |
|
square, do not reach the circle, because the blue square comes |
|
later in the DOM. Line #27 fixes this. When the user clicks |
|
on any part of the circle, we raise the circle to the top by |
|
placing it <em>last</em> among its parent's child nodes (a |
|
little known feature of <code>appendChild()</code> is when the |
|
node to be appended is already a child of the target node, it |
|
is moved to the end of its child list). |
|
</li> |
|
<li><strong>51-54</strong> - We check the values of x,y to |
|
make sure they will not place the circle beyond the |
|
boundaries and only update the location if the circle will |
|
remain fully within the canvas. |
|
</li> |
|
</ul> |
|
<p> |
|
This is better, but still klunky, particular at the edges of |
|
the canvas. As we move the mouse beyond the boundaries, the |
|
x,y are out of range, so we don't do an update, but that makes |
|
the circle come to a full-stop. It's difficult to glide the |
|
circle along the canvas edges. |
|
</p> |
|
<p> |
|
To fix this, we need more information. We can acquire the |
|
information we need (in particular, it would be nice to have |
|
the <em>delta</em> in x,y from its previous location), but |
|
this is difficult information to retrieve and there are |
|
(shock!) browser differences to deal with. Fortunately, D3 |
|
provides its own mechanism for managing drag & drop which |
|
fixes all of these issues. Why didn't we start there? I'm a |
|
firm believer that understanding the base technology can only |
|
help understand higher level tools. In other words, while the |
|
tools you'll see next will simplify a lot of our code, it's |
|
not magic! It's just helping us maintain mouse (and for touch |
|
screeens, touch) events... |
|
</p> |
|
</script> |
|
|
|
<script type="text/slide" |
|
data-title="The Easy Way: Using d3.behavior.drag()" |
|
data-function="d3drag"> |
|
<p> |
|
D3 |
|
defines <a href="https://github.com/mbostock/d3/wiki/Behaviors">two |
|
behaviors</a>, drag and zoom. Behaviors encapsulate a |
|
collection of low-level user-interactions (such as we saw in |
|
the previous slides) into higher level abstractons. This slide |
|
demonstrates the drag behavior. |
|
</p> |
|
<p> |
|
In line #10 we construct |
|
a <a href="https://github.com/mbostock/d3/wiki/Drag-Behavior">drag |
|
behavior</a>. The returned value is actually a function that |
|
we can use to call on a D3 selection, or use as a parameter to |
|
<a href="https://github.com/mbostock/d3/wiki/Selections#call">selection.call()</a>, |
|
which is what we do in line #43. Drag behaviours have three |
|
high-level events we can bind to the drag object: |
|
</p> |
|
<ul> |
|
<li><strong>dragstart</strong> - when a drag gesture starts |
|
(e.g, mousedown, touch). |
|
</li> |
|
<li><strong>drag</strong> - when the drag gesture moves. |
|
</li> |
|
<li><strong>dragend</strong> - when the drag gesture finishes |
|
(e.g, mouseup) |
|
</li> |
|
</ul> |
|
<p> |
|
All the machinery we used in the prior slides to manage |
|
dragging are now encapsulated in D3's implementation of the |
|
drag behavior, freeing us to focus on what we <em>want</em> to happen |
|
on these events, rather than on the <em>how</em>. |
|
</p> |
|
<p> |
|
Note: these events are applied to the behavior, <em>not</em> |
|
the selection! |
|
</p> |
|
<p> |
|
We use two of the drag events, <code>dragstart</code> |
|
and <code>drag</code>. In <code>dragstart</code> we raise the |
|
circle to the top and in <code>drag</code> we calculate the |
|
new center point of the circle, confirm it's within our |
|
boundary and, if so, set the new values on the circle. |
|
</p> |
|
<p> |
|
Another benefit of D3's drag event is that it exposes deltas |
|
of the x,y coordinates, relative to its position at the |
|
beginning of the gesture. We use these in lines #22-23. The |
|
benefits of using the deltas, as opposed to the raw x,y values, |
|
as we did in the previous slides, is it permits an overall |
|
smoother user-experience: the circle will now glide along the |
|
edges and when a user first clicks on the circle, it doesn't |
|
snap into place on the new center point. |
|
</p> |
|
</script> |
|
|
|
<script type="text/slide" |
|
data-title="Interacting With a Legend" |
|
data-function="legend" |
|
data-highlight-lines="16-19,54-69,93-96"> |
|
<p> |
|
In our last slide, we'll look at how we can interact with our |
|
chart using a legend. |
|
<p> |
|
<p> |
|
The <a href="?s=1">second slide</a> listed a number of |
|
variables that are available to the slide functions from a |
|
closure, one of which we haven't seen until |
|
now: <code>el_widgets</code>. This is a D3 selection of a DIV |
|
just below the SVG element. The function inserts into the DIV |
|
two checkboxes to limit movement, either in the x-direction or |
|
the y-direction, and three radio buttons to change the color |
|
of the circle. Clicking on these input elements will affect |
|
change on the chart. |
|
</p> |
|
<p> |
|
Changing the color is quite simple and intuitive: set up a |
|
radio button group and when one is clicked, change |
|
the <code>fill</code> attribute of the circle to the value of |
|
the newly selected value. You can see this in lines #93-96 |
|
(highlighted). |
|
</p> |
|
<p> |
|
Limiting movement is a little more involved. At the top of |
|
the function we set up two boolean |
|
variables, <code>xonly</code> and <code>yonly</code>, both |
|
initially <code>false</code>, to track the current state |
|
established by the checkboxes. In the <code>drag</code> |
|
event, lines #16-19 (higlighted), we adjust the center point |
|
values based on the truthiness of <code>xonly</code> |
|
and <code>yonly</code>. The rest of the functionality is |
|
enabled by managing the state in the <code>change</code> |
|
event, lines #54-69 (highlighted). |
|
</p> |
|
<hr> |
|
<h2>Fini!</h2> |
|
<p> |
|
Comments can be left on |
|
the <a href="http://artandlogic.com/2015/09/d3-getting-interactive-with-your-svg-elements/">Art |
|
& Logic blog post</a> introducing this tutorial. |
|
</p> |
|
<p> |
|
This will be a good starting point for the next planned |
|
tutorial in this series of articles, where we will explore |
|
managing time series data in a chart we can interact with, |
|
both on the chart, and with a legend that affects behavior |
|
change. |
|
</p> |
|
<p> |
|
<a href="http://artandlogic.com/tag/d3/">Stay tuned</a>! |
|
</p> |
|
</script> |
|
|
|
<!-- /SLIDES --> |
|
|
|
<!-- ------------------------------------------------------------ --> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js" |
|
charset="utf-8"> |
|
</script> |
|
<script src="./prism.js" data-manual></script> |
|
<script src="./demo_functions.js"></script> |
|
<script src="./slides.js"></script> |
|
<script>init_slides(d3, Prism, demo_functions);</script> |
|
|
|
</body> |
|
</html> |