epubjs
Version:
Render ePub documents in the browser, across many devices
228 lines (227 loc) • 49 kB
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>