UNPKG

epubjs

Version:

Render ePub documents in the browser, across many devices

228 lines (227 loc) 49 kB
<?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 7. Scales</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="ch06.html" title="Chapter 6. Drawing with Data"/><link rel="next" href="ch08.html" title="Chapter 8. Axes"/></head><body><section class="chapter" title="Chapter 7. Scales" epub:type="chapter" id="scales-chapter7"><div class="titlepage"><div><div><h2 class="title">Chapter 7. Scales</h2></div></div></div><p>“Scales are functions that map from an input domain to an output range.”<a id="ix_scales" class="indexterm"/></p><p>That’s <a class="ulink" href="https://github.com/mbostock/d3/wiki/Quantitative-Scales" target="_top">Mike Bostock’s definition of D3 scales</a>, and there is no clearer way to say it.<a id="I_indexterm7_id303170" class="indexterm"/></p><p>The values in any dataset are unlikely to correspond exactly to pixel measurements for use in your visualization. Scales provide a convenient way to map those data values to new values useful for visualization purposes.</p><p>D3 scales are <span class="emphasis"><em>functions</em></span> with parameters that you define. Once they are created, you call the <code class="literal">scale</code> function, pass it a data value, and it nicely returns a scaled output value. You can define and use as many scales as you like.<a id="I_indexterm7_id303197" class="indexterm"/></p><p>It might be tempting to think of a scale as something that appears visually in the final image—like a set of tick marks, indicating a progression of values. <span class="emphasis"><em>Do not be fooled!</em></span> Those tick marks are part of an <span class="emphasis"><em>axis</em></span>, which is a <span class="emphasis"><em>visual representation</em></span> of a scale. A scale is a mathematical relationship, with no direct visual output. I encourage you to think of scales and axes as two different, yet related, elements.<a id="I_indexterm7_id303222" class="indexterm"/><a id="I_indexterm7_id303231" class="indexterm"/></p><p>This chapter addresses only <a class="ulink" href="https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear" target="_top"><span class="emphasis"><em>linear</em></span></a> scales, because they are most common and easiest understood. Once you understand linear scales, the others—ordinal, logarithmic, square root, and so on—will be a piece of cake.<a id="I_indexterm7_id303256" class="indexterm"/></p><div class="sect1" title="Apples and Pixels"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_apples_and_pixels">Apples and Pixels</h2></div></div></div><p>Imagine that the following dataset represents the number of apples sold at a roadside fruit stand each month:</p><a id="I_programlisting7_id303280"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">dataset</code> <code class="o">=</code> <code class="p">[</code> <code class="mi">100</code><code class="p">,</code> <code class="mi">200</code><code class="p">,</code> <code class="mi">300</code><code class="p">,</code> <code class="mi">400</code><code class="p">,</code> <code class="mi">500</code> <code class="p">];</code></pre><p>First of all, this is great news, as the stand is selling 100 additional apples each month! Business is booming. To showcase this success, you want to make a bar chart illustrating the steep upward climb of apple sales, with each data value corresponding to the height of one bar.</p><p>Until now, we’ve used data values directly as display values, ignoring unit differences. So if 500 apples were sold, the corresponding bar would be 500 pixels tall.</p><p>That could work, but what about next month, when 600 apples are sold? And a year later, when 1,800 apples are sold? Your audience would have to purchase ever-larger displays just to be able to see the full height of those very tall apple bars! (Mmm, apple bars!)</p><p>This is where scales come in. Because apples are not pixels (which are also not oranges), we need scales to translate between them.</p></div><div class="sect1" title="Domains and Ranges"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_domains_and_ranges">Domains and Ranges</h2></div></div></div><p>A scale’s <span class="emphasis"><em>input domain</em></span> is the range of possible input data values. Given the preceding apples data, appropriate input domains would be either 100 and 500 (the minimum and maximum values of the dataset) or 0 and 500.<a id="I_indexterm7_id303329" class="indexterm"/><a id="I_indexterm7_id303336" class="indexterm"/><a id="I_indexterm7_id303342" class="indexterm"/></p><p>A scale’s <span class="emphasis"><em>output range</em></span> is the range of possible output values, commonly used as display values in pixel units. The output range is completely up to you, as the information designer. If you decide the shortest apple bar will be 10 pixels tall, and the tallest will be 350 pixels tall, then you could set an output range of 10 and 350.</p><p>For example, create a scale with an input domain of <code class="literal">[100,500]</code> and an output range of <code class="literal">[10,350]</code>. If you handed the low input value of <code class="literal">100</code> to that scale, it would return its lowest range value, <code class="literal">10</code>. If you gave it <code class="literal">500</code>, it would spit back <code class="literal">350</code>. If you gave it <code class="literal">300</code>, it would hand <code class="literal">180</code> back to you on a silver platter. (<code class="literal">300</code> is in the center of the domain, and <code class="literal">180</code> is in the center of the range.)</p><p>We can visualize the domain and range as corresponding axes, side-by-side, displayed in <a class="xref" href="ch07.html#input_output_axes" title="Figure 7-1. An input domain and an output range, visualized as parallel axes">Figure 7-1</a>.</p><div class="figure"><a id="input_output_axes"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id303421"/><img src="httpatomoreillycomsourceoreillyimages1614787.png" alt="An input domain and an output range, visualized as parallel axes"/></div></div><div class="figure-title">Figure 7-1. An input domain and an output range, visualized as parallel axes</div></div><p>One more thing: to prevent your brain from mixing up the <span class="emphasis"><em>input domain</em></span> and <span class="emphasis"><em>output range</em></span> terminology, I’d like to propose a little exercise. When I say “input,” you say “domain.” Then I say “output,” and you say “range.” Ready? Okay:</p><div class="blockquote"><blockquote class="blockquote"><p>Input! Domain!</p></blockquote></div><div class="blockquote"><blockquote class="blockquote"><p>Output! Range!</p></blockquote></div><div class="blockquote"><blockquote class="blockquote"><p>Input! Domain!</p></blockquote></div><div class="blockquote"><blockquote class="blockquote"><p>Output! Range!</p></blockquote></div><p>Got it? Great.</p></div><div class="sect1" title="Normalization"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_normalization">Normalization</h2></div></div></div><p>If you’re familiar with the concept of <span class="emphasis"><em>normalization</em></span>, it might be helpful to know that, with a linear scale, that’s all that is really going on here.<a id="I_indexterm7_id303504" class="indexterm"/><a id="I_indexterm7_id303513" class="indexterm"/></p><p>Normalization is the process of mapping a numeric value to a new value between 0 and 1, based on the possible minimum and maximum values. For example, with 365 days in the year, day number 310 maps to about 0.85, or 85 percent of the way through the year.</p><p>With linear scales, we are just letting D3 handle the math of the normalization process. The input value is normalized according to the domain, and then the normalized value is scaled to the output range.</p></div><div class="sect1" title="Creating a Scale"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_creating_a_scale">Creating a Scale</h2></div></div></div><p>D3’s scale function generators are accessed with <code class="literal">d3.scale</code> followed by<a id="I_indexterm7_id303551" class="indexterm"/> the type of scale you want. I recommend opening up the sample code page <span class="emphasis"><em>01_scale_test.html</em></span> and typing each of the following into the console:</p><a id="I_programlisting7_id303567"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">scale</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">scale</code><code class="p">.</code><code class="nx">linear</code><code class="p">();</code></pre><p>Congratulations! Now <code class="literal">scale</code> is a function to which you can pass input values. (Don’t be misled by the <code class="literal">var</code>. Remember that in JavaScript, variables can store functions.)</p><a id="I_programlisting7_id303584"/><pre class="programlisting"><code class="nx">scale</code><code class="p">(</code><code class="mf">2.5</code><code class="p">);</code> <code class="c1">//Returns 2.5</code></pre><p>Because we haven’t set a domain and a range yet, this function will map input to output on a 1:1 scale. That is, whatever we input will be returned unchanged.</p><p>We can set the scale’s input domain to <code class="literal">100,500</code> by passing those values to the <code class="literal">domain()</code> method as an array. Note the hard brackets indicating an array:</p><a id="I_programlisting7_id303611"/><pre class="programlisting"><code class="nx">scale</code><code class="p">.</code><code class="nx">domain</code><code class="p">([</code><code class="mi">100</code><code class="p">,</code> <code class="mi">500</code><code class="p">]);</code></pre><p>Set the output range in similar fashion, with <code class="literal">range()</code>:</p><a id="I_programlisting7_id303625"/><pre class="programlisting"><code class="nx">scale</code><code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="mi">10</code><code class="p">,</code> <code class="mi">350</code><code class="p">]);</code></pre><p>These steps can be done separately, as just shown, or chained together into one line of code:</p><a id="I_programlisting7_id303635"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">scale</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">scale</code><code class="p">.</code><code class="nx">linear</code><code class="p">()</code> <code class="p">.</code><code class="nx">domain</code><code class="p">([</code><code class="mi">100</code><code class="p">,</code> <code class="mi">500</code><code class="p">])</code> <code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="mi">10</code><code class="p">,</code> <code class="mi">350</code><code class="p">]);</code></pre><p>Either way, our scale is ready to use!</p><a id="I_programlisting7_id303646"/><pre class="programlisting"><code class="nx">scale</code><code class="p">(</code><code class="mi">100</code><code class="p">);</code> <code class="c1">//Returns 10</code> <code class="nx">scale</code><code class="p">(</code><code class="mi">300</code><code class="p">);</code> <code class="c1">//Returns 180</code> <code class="nx">scale</code><code class="p">(</code><code class="mi">500</code><code class="p">);</code> <code class="c1">//Returns 350</code></pre><p>Typically, you will call scale functions from within an <code class="literal">attr()</code> method or similar, not on their own. Let’s modify our scatterplot visualization to use dynamic scales.<a id="I_indexterm7_id303659" class="indexterm"/><a id="I_indexterm7_id303669" class="indexterm"/><a id="I_indexterm7_id303675" class="indexterm"/></p></div><div class="sect1" title="Scaling the Scatterplot"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_scaling_the_scatterplot">Scaling the Scatterplot</h2></div></div></div><p>To revisit our dataset from the scatterplot:</p><a id="I_programlisting7_id303701"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">dataset</code> <code class="o">=</code> <code class="p">[</code> <code class="p">[</code><code class="mi">5</code><code class="p">,</code> <code class="mi">20</code><code class="p">],</code> <code class="p">[</code><code class="mi">480</code><code class="p">,</code> <code class="mi">90</code><code class="p">],</code> <code class="p">[</code><code class="mi">250</code><code class="p">,</code> <code class="mi">50</code><code class="p">],</code> <code class="p">[</code><code class="mi">100</code><code class="p">,</code> <code class="mi">33</code><code class="p">],</code> <code class="p">[</code><code class="mi">330</code><code class="p">,</code> <code class="mi">95</code><code class="p">],</code> <code class="p">[</code><code class="mi">410</code><code class="p">,</code> <code class="mi">12</code><code class="p">],</code> <code class="p">[</code><code class="mi">475</code><code class="p">,</code> <code class="mi">44</code><code class="p">],</code> <code class="p">[</code><code class="mi">25</code><code class="p">,</code> <code class="mi">67</code><code class="p">],</code> <code class="p">[</code><code class="mi">85</code><code class="p">,</code> <code class="mi">21</code><code class="p">],</code> <code class="p">[</code><code class="mi">220</code><code class="p">,</code> <code class="mi">88</code><code class="p">]</code> <code class="p">];</code></pre><p>You’ll recall that this <code class="literal">dataset</code> is an array of arrays. We mapped the first value in each array onto the x-axis, and the second value onto the y-axis. Let’s start with the x-axis.</p><p>Just by eyeballing the x values, it looks like they range from 5 to 480, so a reasonable input domain to specify might be <code class="literal">0,500</code>, right?</p><p>Why are you giving me that look? Oh, because you want to keep your code flexible and scalable, so it will continue to work even if the data changes in the future. Very smart! Remember, if we were building a data dashboard for the roadside apple stand, we’d want our code to accommodate the enormous projected growth in apple sales. Our chart should work just as well with 5 apples sold as 5 million.</p><div class="sect2" title="d3.min() and d3.max()"><div class="titlepage"><div><div><h3 class="title" id="_d3_min_and_d3_max">d3.min() and d3.max()</h3></div></div></div><p>Instead of specifying fixed values for the domain, we can use the convenient array functions <code class="literal">d3.min()</code> and <code class="literal">d3.max()</code> to analyze our data set on the fly. For example, this loops through each of the x values in our arrays and returns the value of the greatest one:</p><a id="I_programlisting7_id303756"/><pre class="programlisting"><code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">dataset</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="mi">0</code><code class="p">];</code> <code class="c1">//References first value in each subarray</code> <code class="p">});</code></pre><p>That code will return the value 480, because 480 is the largest x value in our dataset. Let me explain how it works.</p><p>Both <code class="literal">min()</code> and <code class="literal">max()</code> work the same way, and they can take either one or two arguments. The first argument must be a reference to the array of values you want evaluated, which is <code class="literal">dataset</code>, in this case. If you have a simple, one-dimensional array of numeric values, like <code class="literal">[7, 8, 4, 5, 2]</code>, then it’s obvious how to compare the values against each other, and no second argument is needed. For example:</p><a id="I_programlisting7_id303789"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">simpleDataset</code> <code class="o">=</code> <code class="p">[</code><code class="mi">7</code><code class="p">,</code> <code class="mi">8</code><code class="p">,</code> <code class="mi">4</code><code class="p">,</code> <code class="mi">5</code><code class="p">,</code> <code class="mi">2</code><code class="p">];</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">simpleDataset</code><code class="p">);</code> <code class="c1">// Returns 8</code></pre><p>The <code class="literal">max()</code> function simply loops through each value in the array, and identifies the largest one.</p><p>But our <code class="literal">dataset</code> is not just an array of numbers; it is an array of arrays. Calling <code class="literal">d3.max(dataset)</code> might produce unexpected results:</p><a id="I_programlisting7_id303815"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">dataset</code> <code class="o">=</code> <code class="p">[</code> <code class="p">[</code><code class="mi">5</code><code class="p">,</code> <code class="mi">20</code><code class="p">],</code> <code class="p">[</code><code class="mi">480</code><code class="p">,</code> <code class="mi">90</code><code class="p">],</code> <code class="p">[</code><code class="mi">250</code><code class="p">,</code> <code class="mi">50</code><code class="p">],</code> <code class="p">[</code><code class="mi">100</code><code class="p">,</code> <code class="mi">33</code><code class="p">],</code> <code class="p">[</code><code class="mi">330</code><code class="p">,</code> <code class="mi">95</code><code class="p">],</code> <code class="p">[</code><code class="mi">410</code><code class="p">,</code> <code class="mi">12</code><code class="p">],</code> <code class="p">[</code><code class="mi">475</code><code class="p">,</code> <code class="mi">44</code><code class="p">],</code> <code class="p">[</code><code class="mi">25</code><code class="p">,</code> <code class="mi">67</code><code class="p">],</code> <code class="p">[</code><code class="mi">85</code><code class="p">,</code> <code class="mi">21</code><code class="p">],</code> <code class="p">[</code><code class="mi">220</code><code class="p">,</code> <code class="mi">88</code><code class="p">]</code> <code class="p">];</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">dataset</code><code class="p">);</code> <code class="c1">// Returns [85, 21]. What???</code></pre><p>To tell <code class="literal">max()</code> which <span class="emphasis"><em>specific</em></span> values we want compared, we must include a second argument, an <span class="emphasis"><em>accessor function</em></span>:</p><a id="I_programlisting7_id303838"/><pre class="programlisting"><code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">dataset</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="mi">0</code><code class="p">];</code> <code class="p">});</code></pre><p>The accessor function is an anonymous function to which <code class="literal">max()</code> hands<a id="I_indexterm7_id303853" class="indexterm"/><a id="I_indexterm7_id303859" class="indexterm"/> off each value in the data array, one at a time, as <code class="literal">d</code>. The accessor function specifies <span class="emphasis"><em>how to access</em></span> the value to be used for the comparison. In this case, our data array is <code class="literal">dataset</code>, and we want to compare only the x values, which are the first values in each subarray, meaning in position <code class="literal">[0]</code>. So our accessor function looks like this:</p><a id="I_programlisting7_id303885"/><pre class="programlisting"><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="mi">0</code><code class="p">];</code> <code class="c1">//Return the first value in each subarray</code> <code class="p">}</code></pre><p>Note that this looks suspiciously similar to the syntax we used when generating our scatterplot circles, which also used anonymous functions to retrieve and return values:</p><a id="I_programlisting7_id303896"/><pre class="programlisting"><code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cx"</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="mi">0</code><code class="p">];</code> <code class="p">})</code> <code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cy"</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="mi">1</code><code class="p">];</code> <code class="p">})</code></pre><p>This is a common D3 pattern. Soon you will be very comfortable with all manner of anonymous functions crawling all over your code.</p></div><div class="sect2" title="Setting Up Dynamic Scales"><div class="titlepage"><div><div><h3 class="title" id="_setting_up_dynamic_scales">Setting Up Dynamic Scales</h3></div></div></div><p>Putting together what we’ve covered, let’s create the scale function for<a id="I_indexterm7_id303920" class="indexterm"/><a id="I_indexterm7_id303930" class="indexterm"/> our x-axis:</p><a id="I_programlisting7_id303939"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">xScale</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">scale</code><code class="p">.</code><code class="nx">linear</code><code class="p">()</code> <code class="p">.</code><code class="nx">domain</code><code class="p">([</code><code class="mi">0</code><code class="p">,</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">dataset</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="mi">0</code><code class="p">];</code> <code class="p">})])</code> <code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="mi">0</code><code class="p">,</code> <code class="nx">w</code><code class="p">]);</code></pre><p>First, notice that I named it <code class="literal">xScale</code>. Of course, you can name your scales whatever you want, but a name like <code class="literal">xScale</code> helps me remember what this function does.</p><p>Second, notice that both the domain and range are specified as two-value arrays in hard brackets.</p><p>Third, notice that I set the low end of the input domain to 0. (Alternatively, you could use <code class="literal">min()</code> to calculate a dynamic value.) The upper end of the domain is set to the maximum value in <code class="literal">dataset</code> (which is currently 480, but could change in the future).</p><p>Finally, observe that the output range is set to <code class="literal">0</code> and <code class="literal">w</code>, the SVG’s width.</p><p>We’ll use very similar code to create the scale function for the y-axis:</p><a id="I_programlisting7_id303990"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">yScale</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">scale</code><code class="p">.</code><code class="nx">linear</code><code class="p">()</code> <code class="p">.</code><code class="nx">domain</code><code class="p">([</code><code class="mi">0</code><code class="p">,</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">dataset</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="mi">1</code><code class="p">];</code> <code class="p">})])</code> <code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="mi">0</code><code class="p">,</code> <code class="nx">h</code><code class="p">]);</code></pre><p>Note that the <code class="literal">max()</code> function here references <code class="literal">d[1]</code>, the y value of each subarray. Also, the upper end of <code class="literal">range()</code> is set to <code class="literal">h</code> instead of <code class="literal">w</code>.</p><p>The scale functions are in place! Now all we need to do is use them.</p></div><div class="sect2" title="Incorporating Scaled Values"><div class="titlepage"><div><div><h3 class="title" id="_incorporating_scaled_values">Incorporating Scaled Values</h3></div></div></div><p>Revisiting our scatterplot code, we now simply modify the original line<a id="I_indexterm7_id304036" class="indexterm"/> where we created a <code class="literal">circle</code> for each data value:</p><a id="I_programlisting7_id304053"/><pre class="programlisting"><code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cx"</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="mi">0</code><code class="p">];</code> <code class="c1">//Returns original value bound from dataset</code> <code class="p">})</code></pre><p>to return a scaled value (instead of the original value):</p><a id="I_programlisting7_id304063"/><pre class="programlisting"><code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cx"</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">xScale</code><code class="p">(</code><code class="nx">d</code><code class="p">[</code><code class="mi">0</code><code class="p">]);</code> <code class="c1">//Returns scaled value</code> <code class="p">})</code></pre><p>Likewise, for the y-axis, this:</p><a id="I_programlisting7_id304074"/><pre class="programlisting"><code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cy"</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="mi">1</code><code class="p">];</code> <code class="p">})</code></pre><p>is modified as:</p><a id="I_programlisting7_id304083"/><pre class="programlisting"><code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cy"</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">yScale</code><code class="p">(</code><code class="nx">d</code><code class="p">[</code><code class="mi">1</code><code class="p">]);</code> <code class="p">})</code></pre><p>For good measure, let’s make the same change where we set the coordinates for the text labels, so these lines:</p><a id="I_programlisting7_id304094"/><pre class="programlisting"><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="p">{</code> <code class="k">return</code> <code class="nx">d</code><code class="p">[</code><code class="mi">0</code><code class="p">];</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="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="mi">1</code><code class="p">];</code> <code class="p">})</code></pre><p>become this:</p><a id="I_programlisting7_id304104"/><pre class="programlisting"><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="p">{</code> <code class="k">return</code> <code class="nx">xScale</code><code class="p">(</code><code class="nx">d</code><code class="p">[</code><code class="mi">0</code><code class="p">]);</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="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">yScale</code><code class="p">(</code><code class="nx">d</code><code class="p">[</code><code class="mi">1</code><code class="p">]);</code> <code class="p">})</code></pre><p>And there we are!</p><p>Check out the working code in <span class="emphasis"><em>02_scaled_plot.html</em></span>. Visually, the result in <a class="xref" href="ch07.html#Scatterplot_using_x_and_y_scales" title="Figure 7-2. Scatterplot using x and y scales">Figure 7-2</a> is disappointingly similar to our original scatterplot! Yet we are making more progress than might be apparent.<a id="I_indexterm7_id304128" class="indexterm"/></p><div class="figure"><a id="Scatterplot_using_x_and_y_scales"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id304146"/><img src="httpatomoreillycomsourceoreillyimages1614788.png.jpg" alt="Scatterplot using x and y scales"/></div></div><div class="figure-title">Figure 7-2. Scatterplot using x and y scales</div></div></div></div><div class="sect1" title="Refining the Plot"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_refining_the_plot">Refining the Plot</h2></div></div></div><p>You might have noticed that smaller y values are at the top of the plot, and the larger y values are toward the bottom. Now that we’re using D3 scales, it’s super easy to reverse that, so greater values are higher up, as you would expect. It’s just a matter of changing the output range of <code class="literal">yScale</code> from:</p><a id="I_programlisting7_id304186"/><pre class="programlisting"><code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="mi">0</code><code class="p">,</code> <code class="nx">h</code><code class="p">]);</code></pre><p>to:</p><a id="I_programlisting7_id304196"/><pre class="programlisting"><code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="nx">h</code><code class="p">,</code> <code class="mi">0</code><code class="p">]);</code></pre><p>See <span class="emphasis"><em>03_scaled_plot_inverted.html</em></span> for the code that results in <a class="xref" href="ch07.html#Scatterplot_with_y_scale_inverted" title="Figure 7-3. Scatterplot with y scale inverted">Figure 7-3</a>.</p><div class="figure"><a id="Scatterplot_with_y_scale_inverted"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id304222"/><img src="httpatomoreillycomsourceoreillyimages1614789.png.jpg" alt="Scatterplot with y scale inverted"/></div></div><div class="figure-title">Figure 7-3. Scatterplot with y scale inverted</div></div><p>Yes, now a <span class="emphasis"><em>smaller</em></span> input to <code class="literal">yScale</code> will produce a <span class="emphasis"><em>larger</em></span> output value, thereby pushing those <code class="literal">circle</code>s and <code class="literal">text</code> elements down, closer to the base of the image. I know, it’s almost too easy!</p><p>Yet some elements are getting cut off. Let’s introduce a <code class="literal">padding</code> variable:</p><a id="I_programlisting7_id304271"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">padding</code> <code class="o">=</code> <code class="mi">20</code><code class="p">;</code></pre><p>Then we’ll incorporate the <code class="literal">padding</code> amount when setting the range of both scales. This will help push our elements in, away from the edges of the SVG, to prevent them from being clipped.<a id="I_indexterm7_id304284" class="indexterm"/></p><p>The range for <code class="literal">xScale</code> was <code class="literal">range([0, w])</code>, but now it’s:</p><a id="I_programlisting7_id304303"/><pre class="programlisting"><code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="nx">padding</code><code class="p">,</code> <code class="nx">w</code> <code class="o">-</code> <code class="nx">padding</code><code class="p">]);</code></pre><p>The range for <code class="literal">yScale</code> was <code class="literal">range([h, 0])</code>, but now it’s:</p><a id="I_programlisting7_id304320"/><pre class="programlisting"><code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="nx">h</code> <code class="o">-</code> <code class="nx">padding</code><code class="p">,</code> <code class="nx">padding</code><code class="p">]);</code></pre><p>This should provide us with 20 pixels of extra room on the left, right, top, and bottom edges of the SVG. And it does (see <a class="xref" href="ch07.html#Scatterplot_with_padding" title="Figure 7-4. Scatterplot with padding">Figure 7-4</a>)!</p><div class="figure"><a id="Scatterplot_with_padding"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id304344"/><img src="httpatomoreillycomsourceoreillyimages1614790.png.jpg" alt="Scatterplot with padding"/></div></div><div class="figure-title">Figure 7-4. Scatterplot with padding</div></div><p>But the text labels on the far right are still getting cut off, so I’ll double the amount of <code class="literal">xScale</code>’s padding on the right side by multiplying by two to achieve the result shown in <a class="xref" href="ch07.html#Scatterplot_with_more_padding" title="Figure 7-5. Scatterplot with more padding">Figure 7-5</a>:</p><a id="I_programlisting7_id304374"/><pre class="programlisting"><code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="nx">padding</code><code class="p">,</code> <code class="nx">w</code> <code class="o">-</code> <code class="nx">padding</code> <code class="o">*</code> <code class="mi">2</code><code class="p">]);</code></pre><div class="figure"><a id="Scatterplot_with_more_padding"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id304388"/><img src="httpatomoreillycomsourceoreillyimages1614791.png.jpg" alt="Scatterplot with more padding"/></div></div><div class="figure-title">Figure 7-5. Scatterplot with more padding</div></div><div class="tip" title="Tip"><h3 class="title">Tip</h3><p>The way I’ve introduced padding here is simple, but not elegant. Eventually, you’ll want more control over how much padding is on each side of your charts (top, right, bottom, left), and it’s useful to standardize how you specify those values across projects. Although I haven’t used <a class="ulink" href="http://bl.ocks.org/3019563" target="_top">Mike Bostock’s margin convention</a> for the code samples in this book, I recommend taking a look to see if it could work for you.</p></div><p>Better! Reference the code so far in <span class="emphasis"><em>04_scaled_plot_padding.html</em></span>. But there’s one more change I’d like to make. Instead of setting the radius of each <code class="literal">circle</code> as the square root of its y value (which was a bit of a hack, and not visually useful), why not create another custom scale?</p><a id="I_programlisting7_id304436"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">rScale</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">scale</code><code class="p">.</code><code class="nx">linear</code><code class="p">()</code> <code class="p">.</code><code class="nx">domain</code><code class="p">([</code><code class="mi">0</code><code class="p">,</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">dataset</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="mi">1</code><code class="p">];</code> <code class="p">})])</code> <code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="mi">2</code><code class="p">,</code> <code class="mi">5</code><code class="p">]);</code></pre><p>Then, setting the radius looks like this:</p><a id="I_programlisting7_id304447"/><pre class="programlisting"><code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"r"</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">rScale</code><code class="p">(</code><code class="nx">d</code><code class="p">[</code><code class="mi">1</code><code class="p">]);</code> <code class="p">});</code></pre><p>This is exciting because we are guaranteeing that our radius values will <span class="emphasis"><em>always</em></span> fall within the range of <code class="literal">2,5</code>. (Or <span class="emphasis"><em>almost</em></span> always; see reference to <code class="literal">clamp()</code> later.) So data values of <code class="literal">0</code> (the minimum input) will get circles of radius <code class="literal">2</code> (or a diameter of 4 pixels). The very largest data value will get a circle of radius <code class="literal">5</code> (diameter of 10 pixels).</p><p>Voila: <a class="xref" href="ch07.html#Scatterplot_with_scaled_radii" title="Figure 7-6. Scatterplot with scaled radii">Figure 7-6</a> shows our first scale used for a visual property other than an axis value. (See <span class="emphasis"><em>05_scaled_plot_radii.html</em></span>.)</p><div class="figure"><a id="Scatterplot_with_scaled_radii"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id304505"/><img src="httpatomoreillycomsourceoreillyimages1614792.png.jpg" alt="Scatterplot with scaled radii"/></div></div><div class="figure-title">Figure 7-6. Scatterplot with scaled radii</div></div><p>Finally, just in case the power of scales hasn’t yet blown your mind, I’d like to add one more array to the dataset: <code class="literal">[600, 150]</code>.</p><p>Boom! Check out <span class="emphasis"><em>06_scaled_plot_big.html</em></span>. Notice how all the old points in <a class="xref" href="ch07.html#Scatterplot_with_big_numbers_added" title="Figure 7-7. Scatterplot with big numbers added">Figure 7-7</a> maintained their relative positions but have migrated closer together, down and to the left, to accommodate the newcomer in the top-right corner.</p><div class="figure"><a id="Scatterplot_with_big_numbers_added"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id304553"/><img src="httpatomoreillycomsourceoreillyimages1614793.png.jpg" alt="Scatterplot with big numbers added"/></div></div><div class="figure-title">Figure 7-7. Scatterplot with big numbers added</div></div><p>And now, one final revelation: we can now very easily change the size of our SVG, and <span class="emphasis"><em>everything scales accordingly</em></span>. In <a class="xref" href="ch07.html#Large_scaled_scatterplot" title="Figure 7-8. Large, scaled scatterplot">Figure 7-8</a>, I’ve increased the value of <code class="literal">h</code> from <code class="literal">100</code> to <code class="literal">300</code> and made <span class="emphasis"><em>no other changes</em></span>.</p><div class="figure"><a id="Large_scaled_scatterplot"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject7_id304606"/><img src="httpatomoreillycomsourceoreillyimages1614794.png.jpg" alt="Large, scaled scatterplot"/></div></div><div class="figure-title">Figure 7-8. Large, scaled scatterplot</div></div><p>Boom, again! See <span class="emphasis"><em>07_scaled_plot_large.html</em></span>. Hopefully, you are seeing this and realizing: no more late nights tweaking your code because the client decided the graphic should be 800 pixels wide instead of 600. Yes, you will get more sleep because of me (and D3’s brilliant built-in methods). Being well-rested is a competitive advantage. You can thank me later.</p></div><div class="sect1" title="Other Methods"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_other_methods">Other Methods</h2></div></div></div><p><code class="literal">d3.scale.linear()</code> has several other handy methods that deserve a brief<a id="I_indexterm7_id304650" class="indexterm"/> mention here:</p><div class="variablelist"><dl><dt><span class="term"> <a class="ulink" href="http://bit.ly/ZR9Si0" target="_top"><code class="literal">nice()</code></a> </span></dt><dd> This tells the scale to take whatever input domain that you gave to <code class="literal">range()</code> and expand both ends to the nearest round value. From the D3 wiki: “For example, for a domain of [0.20147987687960267, 0.996679553296417], the nice domain is [0.2, 1].” This is useful for normal people, who are not computers and find it hard to read numbers like 0.20147987687960267. </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/Z2ZLke" target="_top"><code class="literal">rangeRound()</code></a> </span></dt><dd> Use <code class="literal">rangeRound()</code> in place of <code class="literal">range()</code>, and all values output by the scale will be rounded to the nearest whole number. This is useful if you want shapes to have exact pixel values, to avoid the fuzzy edges that could arise with antialiasing. </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/12h7uTf" target="_top"><code class="literal">clamp()</code></a> </span></dt><dd> By default, a linear scale <span class="emphasis"><em>can</em></span> return values outside of the specified range. For example, if given a value outside of its expected input domain, a scale will return a number also outside of the output range. Calling <code class="literal">clamp(true)</code> on a scale, however, forces all output values to be within the specified range. This means excessive values will be rounded to the range’s low or high value (whichever is nearest). </dd></dl></div><p>To use any of these special methods, just tack them onto the chain in which you define the original scale function. For example, to use <code class="literal">nice()</code>:</p><a id="I_programlisting7_id304773"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">scale</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">scale</code><code class="p">.</code><code class="nx">linear</code><code class="p">()</code> <code class="p">.</code><code class="nx">domain</code><code class="p">([</code><code class="mf">0.123</code><code class="p">,</code> <code class="mf">4.567</code><code class="p">])</code> <code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="mi">0</code><code class="p">,</code> <code class="mi">500</code><code class="p">])</code> <code class="p">.</code><code class="nx">nice</code><code class="p">();</code></pre></div><div class="sect1" title="Other Scales"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_other_scales">Other Scales</h2></div></div></div><p>In addition to<a id="I_indexterm7_id304794" class="indexterm"/> <a class="ulink" href="https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear" target="_top"><code class="literal">linear</code> scales</a> (discussed earlier), D3 has several other built-in scale methods:</p><div class="variablelist"><dl><dt><span class="term"> <a class="ulink" href="http://bit.ly/15ePKWb" target="_top"><code class="literal">sqrt</code></a> </span></dt><dd> A square root scale. </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/XBKvuq" target="_top"><code class="literal">pow</code></a> </span></dt><dd> A power scale (good for the gym, er, I mean, useful when working with exponential series of values, as in “to the power of” some exponent). </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/YTuRdX" target="_top"><code class="literal">log</code></a> </span></dt><dd> A logarithmic scale. </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/ZEJXtP" target="_top"><code class="literal">quantize</code></a> </span></dt><dd> A linear scale with discrete values for its output range, for when you want to sort data into “buckets.” </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/XxlWlr" target="_top"><code class="literal">quantile</code></a> </span></dt><dd> Similar to <code class="literal">quantize</code>, but with discrete values for its input domain (when you already have “buckets”). </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/XWSVvB" target="_top"><code class="literal">ordinal</code></a> </span></dt><dd> Ordinal scales use nonquantitative values (like category names) for output; perfect for comparing apples and oranges. </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/X6O0fT" target="_top"><code class="literal">d3.scale.category10()</code></a>, <a class="ulink" href="http://bit.ly/Wn3QPH" target="_top"><code class="literal">d3.scale.category20()</code></a>, <a class="ulink" href="http://bit.ly/13bmamp" target="_top"><code class="literal">d3.scale.category20b()</code></a>, and <a class="ulink" href="http://bit.ly/Wn3Saf" target="_top"><code class="literal">d3.scale.category20c()</code></a> </span></dt><dd> Handy preset ordinal scales that output either 10 or 20 categorical colors. </dd><dt><span class="term"> <a class="ulink" href="http://bit.ly/Xxm6tc" target="_top"><code class="literal">d3.time.scale()</code></a> </span></dt><dd> A scale method for date and time values, with special handling of ticks for dates. </dd></dl></div><p>Now that you have mastered the power of scales, it’s time to express them visually as, yes, <span class="emphasis"><em>axes</em></span>!</p></div></section></body></html>