epubjs
Version:
Render ePub documents in the browser, across many devices
119 lines (114 loc) • 67.5 kB
HTML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:m="http://www.w3.org/1998/Math/MathML" xmlns:pls="http://www.w3.org/2005/01/pronunciation-lexicon" xmlns:ssml="http://www.w3.org/2001/10/synthesis" xmlns:svg="http://www.w3.org/2000/svg"><head><title>Chapter 10. Interactivity</title><link rel="stylesheet" type="text/css" href="core.css"/><meta name="generator" content="DocBook XSL Stylesheets V1.76.1"/><link rel="up" href="index.html" title="Interactive Data Visualization for the Web"/><link rel="prev" href="ch09.html" title="Chapter 9. Updates, Transitions, and Motion"/><link rel="next" href="ch11.html" title="Chapter 11. Layouts"/></head><body><section class="chapter" title="Chapter 10. Interactivity" epub:type="chapter" id="interactivity"><div class="titlepage"><div><div><h2 class="title">Chapter 10. Interactivity</h2></div></div></div><p>Now that you’re an old pro at data updates, transitions, and motion, let’s incorporate true interactivity.<a id="ix_interact" class="indexterm"/></p><div class="sect1" title="Binding Event Listeners"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_binding_event_listeners">Binding Event Listeners</h2></div></div></div><p>Say <span class="emphasis"><em>what?</em></span> I know, I know: First, we bound <span class="emphasis"><em>data</em></span>, which was weird enough. And now I’m talking about binding <span class="emphasis"><em>event listeners?</em></span> (This is how JavaScript earned its reputation for being such a strange language.)<a id="I_indexterm10_id312396" class="indexterm"/><a id="I_indexterm10_id312406" class="indexterm"/></p><p>As explained in <a class="xref" href="ch09.html" title="Chapter 9. Updates, Transitions, and Motion">Chapter 9</a>, JavaScript uses an <span class="emphasis"><em>event model</em></span> in which “events” are triggered by things happening, such as new input from the user, provided via a keyboard, mouse, or touch screen. Most of the time, events are being triggered constantly, left and right—it’s just that nobody is <span class="emphasis"><em>listening</em></span> for them, so they are ignored.<a id="I_indexterm10_id312430" class="indexterm"/><a id="I_indexterm10_id312436" class="indexterm"/></p><p>To make our pieces interactive, we define chunks of code that <span class="emphasis"><em>listen</em></span> for specific events being triggered on specific DOM elements. In <a class="xref" href="ch09.html" title="Chapter 9. Updates, Transitions, and Motion">Chapter 9</a>, we used the following code:</p><a id="I_programlisting10_id312459"/><pre class="programlisting"><code class="nx">d3</code><code class="p">.</code><code class="nx">select</code><code class="p">(</code><code class="s2">"p"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"click"</code><code class="p">,</code> <code class="kd">function</code><code class="p">()</code> <code class="p">{</code>
<code class="c1">//Do something on click</code>
<code class="p">});</code></pre><p>This <span class="emphasis"><em>binds</em></span> an <span class="emphasis"><em>event listener</em></span> to the <code class="literal">p</code> paragraph element. The listener happens to be listening for the <code class="literal">click</code> event, which is the JavaScript event triggered when the user clicks the mouse <span class="emphasis"><em>on that <code class="literal">p</code> element</em></span>. (D3 doesn’t use custom event names, although you can define your own. For the sake of supporting existing standards, D3 recognizes all the standard JavaScript events, such as <code class="literal">mouseover</code> and <code class="literal">click</code>. The events supported vary somewhat by browser. Peter-Paul Koch’s <a class="ulink" href="http://www.quirksmode.org/dom/events/" target="_top">event compatibility tables</a> are a useful reference.)<a id="I_indexterm10_id312508" class="indexterm"/><a id="I_indexterm10_id312514" class="indexterm"/></p><p>This gets at one of the nuances of JavaScript’s event model, which is that events don’t happen in a vacuum. Rather, they are always <span class="emphasis"><em>called on</em></span> a specific element. So the code just shown isn’t activated whenever <span class="emphasis"><em>any</em></span> click occurs; it is run just when a click occurs <span class="emphasis"><em>on the <code class="literal">p</code> element</em></span>.</p><p>You could achieve all this with raw JavaScript, but D3’s <code class="literal">on()</code> method is a handy way to quickly bind event listeners to D3 selections. As you can see, <code class="literal">on()</code> takes two arguments: the event name, and an anonymous function to be executed when the event is triggered on the selected element.</p><p>Making your visualization interactive is a simple, two-step process that includes:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">
Binding event listeners
</li><li class="listitem">
Defining the behavior
</li></ol></div></div><div class="sect1" title="Introducing Behaviors"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_introducing_behaviors">Introducing Behaviors</h2></div></div></div><p>Let’s start by working with an earlier, static version of our bar chart. See sample code <span class="emphasis"><em>01_start.html</em></span>.</p><p>The example code binds an event on only one element: <code class="literal">p</code>. This is an unusual usage for <code class="literal">on()</code>.<a id="I_indexterm10_id312611" class="indexterm"/><a id="ix_beh" class="indexterm"/><a id="I_indexterm10_id312631" class="indexterm"/><a id="I_indexterm10_id312637" class="indexterm"/> More commonly, you will want to bind event listeners to more than one element at a time, such as to <span class="emphasis"><em>all</em></span> of the visual elements in your visualization. Fortunately, that is very easy to do. Instead of using <code class="literal">select()</code> to select only one element, use <code class="literal">selectAll()</code> to select multiple elements and pass that selection to <code class="literal">on()</code>.</p><p>You can even bind those event listeners right at the moment when you first create elements. For example, here is our existing code that creates our bar chart’s <code class="literal">rect</code>s, to which I’ve simply tacked on <code class="literal">on()</code>:</p><a id="I_programlisting10_id312679"/><pre class="programlisting"><code class="c1">//Create bars</code>
<code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"rect"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">data</code><code class="p">(</code><code class="nx">dataset</code><code class="p">)</code>
<code class="p">.</code><code class="nx">enter</code><code class="p">()</code>
<code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"rect"</code><code class="p">)</code>
<code class="err">…</code> <code class="c1">//Set attributes (omitted here)</code>
<code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"click"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//This will run whenever *any* bar is clicked</code>
<code class="p">});</code></pre><p>When defining the anonymous function, you can reference <code class="literal">d</code>, or <code class="literal">d</code> and <code class="literal">i</code>, or neither, just as you’ve seen throughout D3. And then whatever code you put between the function’s brackets will execute on click.</p><p>This is a quick and easy way to verify your data values, for example:</p><a id="I_programlisting10_id312709"/><pre class="programlisting"><code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"click"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="nx">console</code><code class="p">.</code><code class="nx">log</code><code class="p">(</code><code class="nx">d</code><code class="p">);</code>
<code class="p">});</code></pre><p>Try that code by running <span class="emphasis"><em>02_click.html</em></span>, open the JavaScript console, and click on some bars. When you click on each bar, you should see that bar’s data value printed to the console. Nice!</p><div class="sect2" title="Hover to Highlight"><div class="titlepage"><div><div><h3 class="title" id="_hover_to_highlight">Hover to Highlight</h3></div></div></div><p>Highlighting elements in response to mouse interaction is a common way to make your visualization feel more responsive, and it can help users navigate and focus on the data of interest.<a id="I_indexterm10_id312743" class="indexterm"/><a id="I_indexterm10_id312750" class="indexterm"/><a id="I_indexterm10_id312758" class="indexterm"/><a id="I_indexterm10_id312764" class="indexterm"/><a id="I_indexterm10_id312773" class="indexterm"/></p><p>A simple hover effect can be achieved with CSS alone—no JavaScript required! The CSS pseudoclass selector <code class="literal">:hover</code> can be used in combination with any other selector to select, well, that same thing, but when the mouse is hovering <span class="emphasis"><em>over</em></span> the element. Here, we select all SVG <code class="literal">rect</code>s and set their fill to <code class="literal">orange</code> (see <a class="xref" href="ch10.html#simple_css_only_mouse_hover" title="Figure 10-1. A simple CSS-only mouse hover effect">Figure 10-1</a>):</p><a id="I_programlisting10_id312809"/><pre class="programlisting"><code class="nt">rect</code><code class="nd">:hover</code> <code class="p">{</code>
<code class="n">fill</code><code class="o">:</code> <code class="nb">orange</code><code class="p">;</code>
<code class="p">}</code></pre><div class="figure"><a id="simple_css_only_mouse_hover"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id312822"/><img src="httpatomoreillycomsourceoreillyimages1614836.png" alt="A simple CSS-only mouse hover effect"/></div></div><div class="figure-title">Figure 10-1. A simple CSS-only mouse hover effect</div></div><p>See <span class="emphasis"><em>03_hover.html</em></span>, and try it out yourself.</p><p>CSS hover styling is fast and easy, but limited. There’s only so much you can achieve with <code class="literal">:hover</code>. Fortunately, recent browsers support applying the new CSS3 transitions on SVG elements. Try adding this above the <code class="literal">rect:hover</code> rule in that example:</p><a id="I_programlisting10_id312859"/><pre class="programlisting"><code class="nt">rect</code> <code class="p">{</code>
<code class="o">-</code><code class="n">moz</code><code class="o">-</code><code class="n">transition</code><code class="o">:</code> <code class="n">all</code> <code class="m">0.3s</code><code class="p">;</code>
<code class="o">-</code><code class="n">o</code><code class="o">-</code><code class="n">transition</code><code class="o">:</code> <code class="n">all</code> <code class="m">0.3s</code><code class="p">;</code>
<code class="o">-</code><code class="n">webkit</code><code class="o">-</code><code class="n">transition</code><code class="o">:</code> <code class="n">all</code> <code class="m">0.3s</code><code class="p">;</code>
<code class="n">transition</code><code class="o">:</code> <code class="n">all</code> <code class="m">0.3s</code><code class="p">;</code>
<code class="p">}</code></pre><p>This tells browsers (including Mozilla, Opera, and Webkit-based browsers) to apply a 0.2-second transition to any changes to the <code class="literal">rect</code> elements. Run that, and you’ll see that the blue/orange switch no longer happens instantly, but smoothly, over a brief 0.2-second period. Nice!<a id="I_indexterm10_id313047" class="indexterm"/></p><p>Yet these transitions can also be managed using JavaScript and D3, for additional control and coordination with other parts of our visualization. Luckily for us, D3 handles all the hassle of transitions for us, so working with JavaScript is not so bad. Let’s re-create the orange hover effect without CSS.</p><p>Instead of referencing the <code class="literal">click</code> event, as we did earlier, we can call <code class="literal">on()</code> with <code class="literal">mouseover</code>, the JavaScript event equivalent of CSS’s <code class="literal">hover</code>:</p><a id="I_programlisting10_id313081"/><pre class="programlisting"><code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"mouseover"</code><code class="p">,</code> <code class="kd">function</code><code class="p">()</code> <code class="p">{</code>
<code class="c1">//Do something on mouseover of any bar</code>
<code class="p">});</code></pre><p>Now we want to set the <code class="literal">fill</code> of <span class="emphasis"><em>this</em></span> bar (the one on which the <code class="literal">mouseover</code> event is triggered) to orange. Yet we are operating in the context of an anonymous function—how could we possibly select the same element on which the event was just triggered?</p><p>The answer is <span class="emphasis"><em>this</em></span>. No, sorry, I mean <code class="literal">this</code>. Just select <code class="literal">this</code>, and set its fill to orange:</p><a id="I_programlisting10_id313119"/><pre class="programlisting"><code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"mouseover"</code><code class="p">,</code> <code class="kd">function</code><code class="p">()</code> <code class="p">{</code>
<code class="nx">d3</code><code class="p">.</code><code class="nx">select</code><code class="p">(</code><code class="k">this</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"fill"</code><code class="p">,</code> <code class="s2">"orange"</code><code class="p">);</code>
<code class="p">});</code></pre><p>Another reason “real” programmers hate JavaScript is because of its notoriously wishy washy use of the keyword <code class="literal">this</code>. In other languages, the meaning of <code class="literal">this</code> is very clearly defined; not so in JavaScript. (jQuery fans are used to this debate.)</p><p>For our purposes, here is all you need to know:</p><div class="itemizedlist"><ul class="itemizedlist"><li class="listitem">
Context is important.
</li><li class="listitem">
Within anonymous functions, D3 automatically sets the context of <code class="literal">this</code> so it references “the current element upon which we are acting.”
</li></ul></div><p>The end result is that, when we hand off anonymous functions to any of D3’s methods, we can reference <code class="literal">this</code> when trying to act on the current element.</p><p>Indeed, you can see this (ha!) in action in <span class="emphasis"><em>04_mouseover.html</em></span> (<a class="xref" href="ch10.html#using_d3" title="Figure 10-2. Using D3 to set an orange fill on mouseover">Figure 10-2</a>).</p><div class="figure"><a id="using_d3"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id313200"/><img src="httpatomoreillycomsourceoreillyimages1614837.png" alt="Using D3 to set an orange fill on mouseover"/></div></div><div class="figure-title">Figure 10-2. Using D3 to set an orange fill on <code class="literal">mouseover</code></div></div><p>Move the mouse over a <code class="literal">rect</code>, the event listener for that <code class="literal">rect</code> is triggered, that same <code class="literal">rect</code> is selected (as <code class="literal">this</code>), and then its fill is set to orange.</p><p><a class="xref" href="ch10.html#using_d3" title="Figure 10-2. Using D3 to set an orange fill on mouseover">Figure 10-2</a> looks good, but we should probably restore each bar’s original color once the hover is over, meaning on <code class="literal">mouseout</code>:</p><a id="I_programlisting10_id313248"/><pre class="programlisting"><code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"mouseout"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="nx">d3</code><code class="p">.</code><code class="nx">select</code><code class="p">(</code><code class="k">this</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"fill"</code><code class="p">,</code> <code class="s2">"rgb(0, 0, "</code> <code class="o">+</code> <code class="p">(</code><code class="nx">d</code> <code class="o">*</code> <code class="mi">10</code><code class="p">)</code> <code class="o">+</code> <code class="s2">")"</code><code class="p">);</code>
<code class="p">});</code></pre><p>Perfect! Try it yourself in <span class="emphasis"><em>05_mouseout.html</em></span>. See <a class="xref" href="ch10.html#results_d3" title="Figure 10-3. Moving the mouse left to right, with fills set on mouseover and mouseout">Figure 10-3</a>.</p><div class="figure"><a id="results_d3"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id313280"/><img src="httpatomoreillycomsourceoreillyimages1614838.png" alt="Moving the mouse left to right, with fills set on mouseover and mouseout"/></div></div><div class="figure-title">Figure 10-3. Moving the mouse left to right, with fills set on <code class="literal">mouseover</code> and <code class="literal">mouseout</code></div></div><p>I am really excited to have accomplished in eight lines of JavaScript what I did originally with CSS in only three! (Not!)</p><p>Actually, what I <span class="emphasis"><em>am</em></span> excited about is to now make the outbound transition silky smooth (see <a class="xref" href="ch10.html#smooth" title="Figure 10-4. Moving the mouse left to right (Smooth Operator Edition)">Figure 10-4</a>). As you remember from <a class="xref" href="ch09.html" title="Chapter 9. Updates, Transitions, and Motion">Chapter 9</a>, accomplishing that involves adding only two lines of code, for <code class="literal">transition()</code> and <code class="literal">duration()</code>:</p><a id="I_programlisting10_id313328"/><pre class="programlisting"><code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"mouseout"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="nx">d3</code><code class="p">.</code><code class="nx">select</code><code class="p">(</code><code class="k">this</code><code class="p">)</code>
<code class="p">.</code><code class="nx">transition</code><code class="p">()</code>
<code class="p">.</code><code class="nx">duration</code><code class="p">(</code><code class="mi">250</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"fill"</code><code class="p">,</code> <code class="s2">"rgb(0, 0, "</code> <code class="o">+</code> <code class="p">(</code><code class="nx">d</code> <code class="o">*</code> <code class="mi">10</code><code class="p">)</code> <code class="o">+</code> <code class="s2">")"</code><code class="p">);</code>
<code class="p">});</code></pre><p>Try that out in <span class="emphasis"><em>06_smoother.html</em></span>.</p><div class="figure"><a id="smooth"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id313349"/><img src="httpatomoreillycomsourceoreillyimages1614839.png" alt="Moving the mouse left to right (Smooth Operator Edition)"/></div></div><div class="figure-title">Figure 10-4. Moving the mouse left to right (Smooth Operator Edition)</div></div><div class="sidebar"><a id="I_sidebar10_id313369"/><div class="sidebar-title">Pointer Events on Overlapping Elements</div><p>Mouse events are triggered only on elements with pixels that can be “touched” by the mouse. If two elements overlap, and the mouse moves over the element that is “on top” (in other words, closer to the front), then the <code class="literal">mouseover</code> event will be triggered on the frontmost element, and <span class="emphasis"><em>not</em></span> on the element behind it.<a id="I_indexterm10_id313392" class="indexterm"/><a id="I_indexterm10_id313398" class="indexterm"/><a id="I_indexterm10_id313404" class="indexterm"/><a id="I_indexterm10_id313413" class="indexterm"/><a id="I_indexterm10_id313422" class="indexterm"/><a id="I_indexterm10_id313431" class="indexterm"/></p><p>You can see this in <span class="emphasis"><em>06_smoother.html</em></span>. Mouse over any bar, and then move your pointer directly above one of the value labels. You’ll see the bar fade back from orange to blue. The <code class="literal">text</code> elements are in front of the bars, so mousing over a label involves also <span class="emphasis"><em>mousing out</em></span> of the <code class="literal">rect</code> behind it. This is counterintuitive because, visually, we haven’t left the <code class="literal">rect</code> at all, but as far as JavaScript is concerned, we have.</p><p>Remember that in SVG, elements placed later in the DOM are rendered visually “in front” of earlier elements. (See the section “Layering and Drawing Order” in <a class="xref" href="ch03.html" title="Chapter 3. Technology Fundamentals">Chapter 3</a>.)</p><p>In many cases, you might want mouse events on some elements (such as our value labels) to be ignored. Luckily, this is as easy as applying one line of CSS to the elements you wish to have ignored:</p><a id="I_programlisting10_id313478"/><pre class="programlisting"><code class="nt">pointer-events</code><code class="o">:</code> <code class="nt">none</code><code class="o">;</code></pre><p>This magically tells the browser “Hey, this element shouldn’t trigger any pointer events (such as <code class="literal">click</code>, <code class="literal">mouseover</code>, or <code class="literal">mouseout</code>), so just behave as if this element isn’t here.” It lets events pass through to the next element below it.</p><p>Use normal CSS selectors to target the appropriate elements. For example, this would apply that to all SVG <code class="literal">text</code> elements:</p><a id="I_programlisting10_id313509"/><pre class="programlisting"><code class="nt">svg</code> <code class="nt">text</code> <code class="p">{</code>
<code class="n">pointer</code><code class="o">-</code><code class="n">events</code><code class="o">:</code> <code class="k">none</code><code class="p">;</code>
<code class="p">}</code></pre><p>Or, instead of including this in a stylesheet, you could specify the CSS with D3 directly when you create the <code class="literal">text</code> element, for example:</p><a id="I_programlisting10_id313576"/><pre class="programlisting"><code class="nx">svg</code><code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"text"</code><code class="p">)</code>
<code class="err">…</code> <code class="c1">//other stuff here</code>
<code class="p">.</code><code class="nx">style</code><code class="p">(</code><code class="s2">"pointer-events"</code><code class="p">,</code> <code class="s2">"none"</code><code class="p">);</code></pre></div></div></div><div class="sect1" title="Grouping SVG Elements"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_grouping_svg_elements">Grouping SVG Elements</h2></div></div></div><p>Note that <code class="literal">g</code> group elements <span class="emphasis"><em>do not</em></span>, by themselves, trigger any mouse events. The reason for this is that <code class="literal">g</code> elements have no pixels! Only their enclosed elements—like <code class="literal">rect</code>s, <code class="literal">circle</code>s, and <code class="literal">text</code> elements—have pixels.</p><p>You can still bind event listeners to <code class="literal">g</code> elements. Just keep in mind that the elements within that <code class="literal">g</code> will then behave as a group. For example, if <span class="emphasis"><em>any</em></span> of the enclosed elements are clicked or moused over, then the listener function will be activated.<a id="I_indexterm10_id313632" class="indexterm"/><a id="I_indexterm10_id313642" class="indexterm"/></p><p>This technique can be quite useful when you have several visual elements that should all act in concert. In our bar chart, for example, we could group <code class="literal">rect</code> and <code class="literal">text</code> elements each into their own groups. The element hierarchy currently looks like this:</p><pre class="screen">svg
rect
rect
rect
…
text
text
text
…</pre><p>After grouping elements, it could look like this:</p><pre class="screen">svg
g
rect
text
g
rect
text
…</pre><p>Instead of worrying about <code class="literal">pointer-events</code> and which element is on top, we just bind the event listener to the whole group. So clicking on some <code class="literal">text</code> will trigger the same code as clicking on a <code class="literal">rect</code> because they’re both in the same group.</p><p>Even better, throw an invisible <code class="literal">rect</code> with a <code class="literal">fill</code> of <code class="literal">none</code> and <code class="literal">pointer-events</code> value of <code class="literal">all</code> on the top of each group. Even though the <code class="literal">rect</code> is invisible, it will still trigger mouse events, so you could have the <code class="literal">rect</code> span the whole height of the chart. The net effect is that mousing <span class="emphasis"><em>anywhere</em></span> in that column—even in “empty” whitespace above a short blue bar—would trigger the highlight effect.</p><div class="sect2" title="Click to Sort"><div class="titlepage"><div><div><h3 class="title" id="_click_to_sort">Click to Sort</h3></div></div></div><p>Interactive visualization is most powerful when it can provide different <span class="emphasis"><em>views</em></span> of the data, empowering the user to explore the information from different angles.<a id="I_indexterm10_id313749" class="indexterm"/><a id="I_indexterm10_id313759" class="indexterm"/><a id="I_indexterm10_id313765" class="indexterm"/><a id="I_indexterm10_id313772" class="indexterm"/></p><p>The ability to <span class="emphasis"><em>sort</em></span> data is extremely important. And yes, as you just guessed, D3 makes it very easy to sort elements.</p><p>Continuing with the bar chart, let’s add an event listener for the <code class="literal">click</code> event, to which we bind an anonymous function that, in turn, will call a new function of our own creation, <code class="literal">sortBars()</code>.</p><a id="I_programlisting10_id313801"/><pre class="programlisting"><code class="err">…</code>
<code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"click"</code><code class="p">,</code> <code class="kd">function</code><code class="p">()</code> <code class="p">{</code>
<code class="nx">sortBars</code><code class="p">();</code>
<code class="p">});</code></pre><p>For simplicity, we are binding this to every bar, but of course you could bind this instead to a button or any other element, inside or outside of the SVG image.</p><p>At the end of the code, let’s define this new function and store it in <code class="literal">sortBars</code>:</p><a id="I_programlisting10_id313820"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">sortBars</code> <code class="o">=</code> <code class="kd">function</code><code class="p">()</code> <code class="p">{</code>
<code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"rect"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">sort</code><code class="p">(</code><code class="kd">function</code><code class="p">(</code><code class="nx">a</code><code class="p">,</code> <code class="nx">b</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">ascending</code><code class="p">(</code><code class="nx">a</code><code class="p">,</code> <code class="nx">b</code><code class="p">);</code>
<code class="p">})</code>
<code class="p">.</code><code class="nx">transition</code><code class="p">()</code>
<code class="p">.</code><code class="nx">duration</code><code class="p">(</code><code class="mi">1000</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"x"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">,</code> <code class="nx">i</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">xScale</code><code class="p">(</code><code class="nx">i</code><code class="p">);</code>
<code class="p">});</code>
<code class="p">};</code></pre><p>You can see this code in <span class="emphasis"><em>07_sort.html</em></span> and the result in <a class="xref" href="ch10.html#click-to-sort" title="Figure 10-5. The view after click-to-sort">Figure 10-5</a>. Try clicking any of the bars, and watch them reorganize.</p><div class="figure"><a id="click-to-sort"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id313848"/><img src="httpatomoreillycomsourceoreillyimages1614840.png" alt="The view after click-to-sort"/></div></div><div class="figure-title">Figure 10-5. The view after click-to-sort</div></div><p>When <code class="literal">sortBars()</code> is called, first we reselect all the <code class="literal">rect</code>s. Then we use D3’s handy <code class="literal">sort()</code> method, which reorders elements based on their bound data values. <code class="literal">sort()</code> needs to know how to decide which elements come first, and which later, so we pass into it a <span class="emphasis"><em>comparator</em></span> function.</p><p>Unlike our anonymous functions so far, the comparator doesn’t take <code class="literal">d</code> (the current datum) or <code class="literal">i</code> (the current index). Instead, it is passed <span class="emphasis"><em>two values</em></span>, <code class="literal">a</code> and <code class="literal">b</code>, which represent the data values of two different elements. (You could name them anything else; <code class="literal">a</code> and <code class="literal">b</code> are just the convention.) The comparator will be called on every pair of elements in our array, comparing <code class="literal">a</code> to <code class="literal">b</code>, until, in the end, all the array elements are sorted per whatever rules we specify.</p><p>We specify <span class="emphasis"><em>how</em></span> <code class="literal">a</code> and <code class="literal">b</code> should be compared within the comparator. Thankfully, D3 also provides a handful of comparison functions that spare us from writing more JavaScript. Here, we use <code class="literal">d3.ascending()</code>, into which both <code class="literal">a</code> and <code class="literal">b</code> are passed. Whichever one is bigger comes out the winner. And <code class="literal">sort()</code> loops through all the data values in this way until it has all the elements, er, sorted out. (Note that <code class="literal">d3.ascending</code> works well in this case, because our values are numbers. Comparing strings of text is a whole other can of worms.)</p><p>Finally, our new order in place, we initiate a transition, set a duration of one second, and then calculate the new <code class="literal">x</code> position for each <code class="literal">rect</code>. (This <code class="literal">attr</code> code is just copied from when we created the <code class="literal">rect</code>s initially.)</p><p>This works swimmingly, except for two catches.</p><p>First, you’ll notice that we haven’t accounted for the value labels yet, so they didn’t slide into place along with the bars. (I leave that to you as an exercise.)</p><p>Second, you might observe that if you mouse over some bars <span class="emphasis"><em>while</em></span> the transition is occurring, those bars don’t fall properly into place (see <a class="xref" href="ch10.html#interrupted" title="Figure 10-6. Transitions, interrupted">Figure 10-6</a>).<a id="I_indexterm10_id314017" class="indexterm"/></p><div class="figure"><a id="interrupted"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id314035"/><img src="httpatomoreillycomsourceoreillyimages1614841.png" alt="Transitions, interrupted"/></div></div><div class="figure-title">Figure 10-6. Transitions, interrupted</div></div><p>Yeeesh, that doesn’t look good.</p><p>Remember from <a class="xref" href="ch09.html" title="Chapter 9. Updates, Transitions, and Motion">Chapter 9</a> that newer transitions interrupt and override older transitions. Clicking the bars initiates one transition. Immediately mousing over a bar interrupts that initial transition in order to run the <code class="literal">mouseover</code> highlight transition we specified earlier. The end result is that those moused-over bars never make it to their final destinations.</p><p>But don’t worry. This example is just a good argument for keeping hover effects in CSS, while letting D3 and JavaScript manage the more visually intensive actions.</p><p>In <span class="emphasis"><em>08_sort_hover.html</em></span>, I’ve restored the CSS-only highlight and removed the <code class="literal">mouseover</code> and <code class="literal">mouseout</code> event listeners, so this transition conflict no longer occurs. (The only downside is we no longer have those smooth orange-to-blue fades.)</p><p>So far, this sort only goes one direction. Let’s revise this so a second click triggers a re-sort, placing the bars in descending order.</p><p>To remember the current state of the chart, we’ll need a Boolean variable:</p><a id="I_programlisting10_id314106"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">sortOrder</code> <code class="o">=</code> <code class="kc">false</code><code class="p">;</code></pre><p>Then, in the <code class="literal">sortBars()</code> function, we should flip the value of <code class="literal">sortOrder</code>, so if it starts out <code class="literal">true</code>, it is changed to <code class="literal">false</code>, and vice versa:</p><a id="I_programlisting10_id314130"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">sortBars</code> <code class="o">=</code> <code class="kd">function</code><code class="p">()</code> <code class="p">{</code>
<code class="c1">//Flip value of sortOrder</code>
<code class="nx">sortOrder</code> <code class="o">=</code> <code class="o">!</code><code class="nx">sortOrder</code><code class="p">;</code>
<code class="err">…</code></pre><p>Down in the comparator function, we can add a bit of logic to say <span class="emphasis"><em>if</em></span> <code class="literal">sortOrder</code> is <code class="literal">true</code>, then go ahead and sort<a id="I_indexterm10_id314153" class="indexterm"/><a id="I_indexterm10_id314159" class="indexterm"/> the bars in <span class="emphasis"><em>ascending</em></span> order. Otherwise, use <span class="emphasis"><em>descending</em></span> order:</p><a id="I_programlisting10_id314173"/><pre class="programlisting"> <code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"rect"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">sort</code><code class="p">(</code><code class="kd">function</code><code class="p">(</code><code class="nx">a</code><code class="p">,</code> <code class="nx">b</code><code class="p">)</code> <code class="p">{</code>
<code class="k">if</code> <code class="p">(</code><code class="nx">sortOrder</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">ascending</code><code class="p">(</code><code class="nx">a</code><code class="p">,</code> <code class="nx">b</code><code class="p">);</code>
<code class="p">}</code> <code class="k">else</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">descending</code><code class="p">(</code><code class="nx">a</code><code class="p">,</code> <code class="nx">b</code><code class="p">);</code>
<code class="p">}</code>
<code class="p">})</code>
<code class="err">…</code></pre><p>Give that a shot in <span class="emphasis"><em>09_resort.html</em></span>. Now each time you click, the sort order reverses, as shown in <a class="xref" href="ch10.html#second-sort" title="Figure 10-7. The second sort, now in descending order">Figure 10-7</a>.</p><div class="figure"><a id="second-sort"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id314202"/><img src="httpatomoreillycomsourceoreillyimages1614842.png" alt="The second sort, now in descending order"/></div></div><div class="figure-title">Figure 10-7. The second sort, now in descending order</div></div><p>One more thing would make this really nice: a per-element delay. (Remember that whole “object constancy” thing?)</p><p>As you know, to do that, we just add a simple <code class="literal">delay()</code> statement after <code class="literal">transition()</code>:</p><a id="I_programlisting10_id314237"/><pre class="programlisting"><code class="err">…</code>
<code class="p">.</code><code class="nx">transition</code><code class="p">()</code>
<code class="p">.</code><code class="nx">delay</code><code class="p">(</code><code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">,</code> <code class="nx">i</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">i</code> <code class="o">*</code> <code class="mi">50</code><code class="p">;</code>
<code class="p">})</code>
<code class="p">.</code><code class="nx">duration</code><code class="p">(</code><code class="mi">1000</code><code class="p">)</code>
<code class="err">…</code></pre><p>Now take a look at <span class="emphasis"><em>10_delay.html</em></span>, in which you can easily follow individual bars with your eyes as they move left and right during each sort.<a id="I_indexterm10_id314250" class="indexterm"/></p></div></div><div class="sect1" title="Tooltips"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_tooltips">Tooltips</h2></div></div></div><p>In interactive visualizations, tooltips are small overlays that present data values. In many cases, it’s not necessary to label every individual data value in the default view, but that level of detail should still be accessible to users. That’s where tooltips come in.<a id="I_indexterm10_id314269" class="indexterm"/><a id="ix_ttip" class="indexterm"/><a id="I_indexterm10_id314291" class="indexterm"/></p><p>In this section, I present three different methods to constructing tooltips with D3, ranging from the simplest to the most complex.</p><div class="sect2" title="Default Browser Tooltips"><div class="titlepage"><div><div><h3 class="title" id="_default_browser_tooltips">Default Browser Tooltips</h3></div></div></div><p>These should be your first stop. A quick-and-dirty, functional but not pretty option, default browser tooltips are usually those ugly yellow boxes you see floating over content when you hold your mouse still for too long. These are very easy to make, and the browser manages the placements for you, but you have zero control over how they look—that’s also set by the browser.<a id="I_indexterm10_id314322" class="indexterm"/></p><p><a class="xref" href="ch10.html#safari-tooltip" title="Figure 10-8. A ridiculously simple default browser tooltip, as seen in Safari">Figure 10-8</a> shows our bar chart, with value labels removed, and default browser tooltips implemented. The tooltips show up after hovering the mouse over any bar for a few seconds.</p><div class="figure"><a id="safari-tooltip"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id314347"/><img src="httpatomoreillycomsourceoreillyimages1614843.png" alt="A ridiculously simple default browser tooltip, as seen in Safari"/></div></div><div class="figure-title">Figure 10-8. A ridiculously simple default browser tooltip, as seen in Safari</div></div><p>See <span class="emphasis"><em>11_browser_tooltip.html</em></span> for the code and a demo. To make these tooltips, simply inject a <code class="literal">title</code> element into whatever element should have the tooltip applied. For example, after we create all those <code class="literal">rect</code>s:</p><a id="I_programlisting10_id314382"/><pre class="programlisting"><code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"rect"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">data</code><code class="p">(</code><code class="nx">dataset</code><code class="p">)</code>
<code class="p">.</code><code class="nx">enter</code><code class="p">()</code>
<code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"rect"</code><code class="p">)</code>
<code class="err">…</code></pre><p>we can just tack on to the end of that chain:</p><a id="I_programlisting10_id314392"/><pre class="programlisting"> <code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"title"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">text</code><code class="p">(</code><code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">d</code><code class="p">;</code>
<code class="p">});</code></pre><p><code class="literal">append()</code> creates the new <code class="literal">title</code> element, and then <code class="literal">text()</code> sets its content to <code class="literal">d</code>, the bound value.</p><p>We could make this text a little less spare by prefixing it with something (see <a class="xref" href="ch10.html#default-tooltip" title="Figure 10-9. A default browser tooltip, with a prefix added">Figure 10-9</a>):</p><a id="I_programlisting10_id314424"/><pre class="programlisting"> <code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"title"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">text</code><code class="p">(</code><code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="s2">"This value is "</code> <code class="o">+</code> <code class="nx">d</code><code class="p">;</code>
<code class="p">});</code></pre><div class="figure"><a id="default-tooltip"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject10_id314439"/><img src="httpatomoreillycomsourceoreillyimages1614844.png" alt="A default browser tooltip, with a prefix added"/></div></div><div class="figure-title">Figure 10-9. A default browser tooltip, with a prefix added</div></div><p>See <span class="emphasis"><em>12_browser_tooltip_text.html</em></span> for that code.</p></div><div class="sect2" title="SVG Element Tooltips"><div class="titlepage"><div><div><h3 class="title" id="_svg_element_tooltips">SVG Element Tooltips</h3></div></div></div><p>For more visual control over your tooltips, code them as SVG elements.<a id="I_indexterm10_id314477" class="indexterm"/><a id="I_indexterm10_id314486" class="indexterm"/></p><p>As usual, there are many different approaches you could take. I’ll suggest adding event listeners, so on each <code class="literal">mouseover</code>, a new value label is created, and on <code class="literal">mouseout</code> it is destroyed. (Another idea would be to pregenerate all the labels, but then just show or hide them based on mouse hover status. Or just stick with one label, but show or hide it and change its position as needed.)</p><p>Back to the bars we go. We’ll add back in a <code class="literal">mouseover</code> event listener, in which we first get the <code class="literal">x</code> and <code class="literal">y</code> values for the current element (<code class="literal">this</code>, remember?). We’ll need this information to know where to place the new tooltip, so it appears nicely “on top of” the bar that’s triggering the rollover.</p><p>When we retrieve those values, we wrap them in <code class="literal">parseFloat()</code>, which is a JavaScript function for “Hey, even if this information is a string of text, please convert it to a floating point number for me.”</p><p>Lastly, I’m adding a bit to both the <code class="literal">x</code> and <code class="literal">y</code> values, to center the new tooltips near the top of any given bar:</p><a id="I_programlisting10_id314550"/><pre class="programlisting"><code class="p">.</code><code class="nx">on</code><code class="p">(</code><code class="s2">"mouseover"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//Get this bar's x/y values, then augment for the tooltip</code>
<code class="kd">var</code> <code class="nx">xPosition</code> <code class="o">=</code> <code class="nb">parseFloat</code><code class="p">(</code><code class="nx">d3</code><code class="p">.</code><code class="nx">select</code><code class="p">(</code><code class="k">this</code><code class="p">).</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"x"</code><code class="p">))</code> <code class="o">+</code> <code class="nx">xScale</code><code class="p">.</code><code class="nx">rangeBand</code><code class="p">()</code> <code class="o">/</code> <code class="mi">2</code><code class="p">;</code>
<code class="kd">var</code> <code class="nx">yPosition</code> <code class="o">=</code> <code class="nb">parseFloat</code><code class="p">(</code><code class="nx">d3</code><code class="p">.</code><code class="nx">select</code><code class="p">(</code><code class="k">this</code><code class="p">).</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"y"</code><code class="p">))</code> <code class="o">+</code> <code class="mi">14</code><code class="p">;</code></pre><p>That’s the hard part. Now all we do is create the tooltip as a simple <code class="literal">text</code> element, in this case, but of course you could add a background <code class="literal">rect</code> or do anything else here for visual effect:</p><a id="I_programlisting10_id314570"/><pre class="programlisting"><code class="c1">//Create the tooltip label</code>
<code class="nx">svg</code><code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"text"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"id"</code><code class="p">,</code> <code class="s2">"tooltip"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"x"</code><code class="p">,</code> <code class="nx">xPosition</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"y"</code><code class="p">,</code> <code class="nx">yPosition</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"text-anchor"</code><code class="p">,</code> <code class="s2">"middle"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"font-family"</code><code class="p">,</code> <code class="s2">"sans-serif"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"font-size"</code><code class="p">,</code> <code class="s2">"11px"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"font-weight"</code><code class="p">,</code> <code class="s2">"bold"</code><code class="p">)</code>
<code class="p">.</code><c