Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~23 min· topic 2 of 7

Topic 6.2

Log, Power, Square-Root and Diverging Scales

In one line

When numbers span many sizes, when a circle must show a value by its area, or when a colour needs a meaningful middle, a straight-line scale misleads. Log, power, square-root, symlog and diverging scales bend the rule the right way, and each has inputs it cannot take.

Think of it like this

Sound. A whisper and a jet engine differ by a factor of a trillion in energy, so a loudness dial in plain numbers would need a page-wide ruler. Decibels fix it: every 10 steps on the dial means ten times more energy. The dial is a log scale. Equal steps mean equal ratios, not equal differences.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

logarithm
How many times you multiply 10 by itself to reach a number: the log of 1,000 is 3.
log scale
A scale where equal distance means equal ratio, so 1, 10, 100 and 1,000 are evenly spaced.
exponent
The power a number is raised to. The square root is the exponent 0.5.
area
How much page a shape covers. For a circle, π × radius × radius.
symlog
A scale that is log-like for big values and linear near zero, so zero and negatives can be drawn.

Step by step

01Equal ratios, equal distance

On the log scale with domain [1, 1000] and range [0, 300], 1, 10, 100 and 1,000 land on 0, 100, 200 and 300 pixels. On the linear scale, 10 rides is 5 pixels from 0 and 100 rides is 50: the night hours are crushed together at the left.

02The zero hours vanish

log(0) is NaN, so the 42 zero hours cannot be drawn. The code filters them out and prints the count: 678 drawn, 42 skipped. The chart is silent about the skipped hours unless you say so, which is an honesty problem. Symlog draws all 720 and puts 0 at 0.

03Why the circle's radius needs a square root

The second example draws the 25 stations' ride totals as circles, twice. Quay (58,584 rides) against Windmill (10,486) is a ratio of 5.59. With scaleSqrt the area ratio is 5.59. With a linear radius it is 31.21: the busiest stations look five times bigger than they are.

04A middle that means something

The diverging scale takes three numbers: the hottest day (24.9 °C), the year's mean (17.5 °C) and the coldest (9.9 °C). 17.5 gets the ramp's near-white middle, 24.9 the red end, 9.9 the blue end. A month colder than average is blue, warmer is red. Values outside the domain (30 degrees) get the end colour, not a new one.

Try it yourself

  1. 1

    Switch the radius to a straight line with a power scale

    In the second example replace d3.scaleSqrt() with d3.scalePow().exponent(1). The area ratio now prints 31.21. An exponent of 1 is a linear scale, and 0.5 is the square root. With .exponent(0.7) the ratio is 11.12, between the two.

  2. 2

    Change the symlog constant

    In the first example use d3.scaleSymlog().constant(10). 10 rides now lands at 50.6 pixels instead of 112.4, because the scale stays near-linear up to about 10 before it bends. With constant(100) it is 14.7: almost a straight line.

Code & diagrams

Log and symlog on 720 hours of rides New tab

The filter on Number.isFinite keeps NaN out of the page: without it the 42 zero hours would print 42 Expected length errors.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

hours: 720  zero-ride hours: 42  busiest hour: 599
0   linear    0.0  log    NaN  symlog    0.0
1   linear    0.5  log    0.0  symlog   32.5
10  linear    5.0  log  100.0  symlog  112.4
100 linear   50.0  log  200.0  symlog  216.4
599 linear  299.5  log  277.7  symlog  299.9
log ticks: 1 10 100 1000 (ticks() gives 28 )
log domain [0, 600] -> NaN   all-negative domain [-10, -1] -> 156.9   [-10, 10] -> NaN
linear                       dots: 720  skipped: 0
log (the 0s cannot be drawn) dots: 678  skipped: 42
symlog                       dots: 720  skipped: 0
Circle area with scaleSqrt, colour with scaleDiverging New tab

Area ratio 5.59 matches the rides ratio only with the square-root scale. The twelve squares are the monthly means, coloured by the same scale.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

busiest: Quay 58584  quietest: Windmill 10486
rides ratio: 5.59
area ratio with scaleSqrt:   5.59
area ratio with scaleLinear: 31.21
temperature domain: 9.9 17.5 24.9
coldest day rgb(5, 48, 97) | average day rgb(242, 239, 238) | hottest day rgb(103, 0, 31) | 30 degrees rgb(103, 0, 31)
January rgb(62, 137, 190)  July rgb(201, 71, 66)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Start a log domain at zero

In the first example, change d3.scaleLog().domain([1, 1000]) to d3.scaleLog().domain([0, 1000]). Zero looks like a natural start for a rides axis.

terminal
$ Console
── what you'll see ──
1 linear 0.5 log NaN symlog 32.5
10 linear 5.0 log NaN symlog 112.4
log ticks: (ticks() gives 0 )
log (the 0s cannot be drawn) dots: 0 skipped: 720

Break #2

Put the diverging domain the wrong way round

In the second example change .domain([hi, mid, lo]) to .domain([lo, mid, hi]).

terminal
$ Console
── what you'll see ──
coldest day rgb(103, 0, 31) | average day rgb(242, 239, 238) | hottest day rgb(5, 48, 97) | 30 degrees rgb(5, 48, 97)
January rgb(205, 79, 70) July rgb(57, 131, 187)

Myth vs fact

Myth

A log scale is just a linear scale with squashed numbers.

Fact

Equal distance means equal ratio, not equal difference, so 10 to 100 and 100 to 1,000 look the same size. The axis must say it is logarithmic, or readers will compare gaps as if they were differences.

Myth

Bubble charts can use the value as the radius.

Fact

Readers judge area. A linear radius exaggerates every difference by the ratio squared: 5.59 became 31.21 in the example.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    scalePow keeps the sign of negative inputs: d3.scalePow().exponent(0.5).domain([0, 16]).range([0, 4])(-4) is -2 (D3 applies the power to the absolute value). That is convenient for diverging bars, and wrong for areas, which are never negative.

  • ▸

    scaleLog().tickFormat() blanks most of the 28 tick labels so a crowded axis stays readable: with the default count it prints 1 2 3 then blanks, then 10 20 30, then 100 200 300, then 1k (checked with a run). Pass a count and a format, tickFormat(5, "~s"), and only the powers of ten keep a label: 1, 10, 100, 1k.

Remember this

  1. 1

    The problem this solves: a linear scale gives equal distance to equal differences. That fails when numbers range from 1 to 1,000 (the small ones pile up at the left), when you size circles (people read area, not radius), and when a colour must show "above" and "below" a meaningful middle. Each case has a scale for it.

  2. 2

    Log scale. (Recall from Topic 6.1: a scale maps a domain to a range, and returns undefined or NaN for input it cannot map.) d3.scaleLog().domain([1, 1000]).range([0, 300]) gives 0, 100, 200 and 300 pixels to 1, 10, 100 and 1,000: each step of ten takes the same distance. Use it when ratios matter and the data spans orders of magnitude, such as the 194 to 8,902 kilometres on Lumen's bikes. The logarithm of 0 does not exist and the logarithm of a negative number is undefined, so the domain must stay on one side of 0. domain([0, 600]) returns NaN for every input.

  3. 3

    A log scale is never a place for bars. A bar's length is read as "how much", and on a log scale the bar's start (the 1 line, not 0) is arbitrary. Use dots or lines, and say "log scale" on the axis. D3's default log ticks are every power of ten plus the multiples 2 to 9, which is 28 ticks for [1, 1000]; ask for fewer (ticks(3) gives 1, 10, 100, 1000).

  4. 4

    Power and square-root scales. scalePow().exponent(k) raises the value to the power k before the straight-line step. scaleSqrt is scalePow().exponent(0.5). A circle's area is π × radius², so a radius proportional to the value shows the value squared. With a radius from scaleSqrt, a station with 5.59 times the rides gets 5.59 times the area. With a linear radius, it gets 31.21 times.

  5. 5

    Symlog scale. scaleSymlog follows sign(x) × log(1 + |x| / c): nearly linear near zero (within about c, default 1), logarithmic further out, and defined for zero and negative numbers. It suits data with many zeros and a long tail, such as hourly rides: 42 of June's 720 hours had zero rides. The price is that readers cannot do ratio sums on its axis, so label it.

  6. 6

    Diverging scale. d3.scaleDiverging(d3.interpolateRdBu).domain([hi, mid, lo]) has three domain numbers and returns colours from a colour ramp. The middle is near white and stands for something real: the year's mean temperature, zero change, a target. Reversing the domain flips the colours (here so that hot is red). The ramp's colours are a starting point: Topic 10.2 checks them for colour-blind readers.

Explain it without notes

01

Why does a circle chart need scaleSqrt for the radius?

02

Why can a log scale not start at 0, and what are your options for data that has zeros?

03

What does the middle number of a diverging domain mean, and how can you choose it?

Practice

01

Write a function that turns a station's ride total into a circle radius, for a largest radius of 30 pixels, and prove that the busiest station's area is 5.59 times the quietest's.

02

Bike odometers range from 194 to 8,902 km. Create a log scale for 0 to 300 pixels and print the pixel of 194, 1,000 and 8,902. What does scaleLog().domain([0, 8902]) give for 1,000?

Trade-offs

  • ↔

    Log and symlog axes show everything on one chart, but ordinary readers misread them. Add a label and a reference line, or split the chart in two, when the audience is not technical.

Done when you can

  • I can choose between linear, log, square-root, symlog and diverging for a given set of data.

  • I can say why log(0) and a log domain crossing zero give NaN.

  • I can size circles so their area matches the value.

  • I can explain what the three numbers of a diverging domain mean.