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
Switch the radius to a straight line with a power scale
In the second example replace
d3.scaleSqrt()withd3.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
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. Withconstant(100)it is 14.7: almost a straight line.
Code & diagrams
The filter on Number.isFinite keeps NaN out of the page: without it the 42 zero hours would print 42 Expected length errors.
Preview
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: 0Area 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
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.
Break #2
Put the diverging domain the wrong way round
In the second example change .domain([hi, mid, lo]) to .domain([lo, mid, hi]).
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.
- ▸
scalePowkeeps 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 prints1 2 3then blanks, then10 20 30, then100 200 300, then1k(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
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
Log scale. (Recall from Topic 6.1: a scale maps a domain to a range, and returns
undefinedorNaNfor 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])returnsNaNfor every input. - 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
Power and square-root scales.
scalePow().exponent(k)raises the value to the powerkbefore the straight-line step.scaleSqrtisscalePow().exponent(0.5). A circle's area is π × radius², so a radius proportional to the value shows the value squared. With a radius fromscaleSqrt, a station with 5.59 times the rides gets 5.59 times the area. With a linear radius, it gets 31.21 times. - 5
Symlog scale.
scaleSymlogfollowssign(x) × log(1 + |x| / c): nearly linear near zero (within aboutc, 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
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
Why does a circle chart need scaleSqrt for the radius?
Why can a log scale not start at 0, and what are your options for data that has zeros?
What does the middle number of a diverging domain mean, and how can you choose it?
Practice
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.
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.