I’m building a scale for generating colors for a map, where various ranges of numbers correspond to colors. In the scale below, the a
, b
, and c
values (along with the min and max) form “steps” or “buckets” corresponding to colors. Anything between -1
and min
will be colored "#ffeda0
in this example, and anything between min
and a
will be colored differently:
return [ "#e7e9e9", -1, "#ffeda0", min, "#fed976", a "#feb24c", b "#fd8d3c", c "#fc4e2a", max "#e31a1c" ];
This scale will always start an a minimum value, called min, and will end at a maximum value, max. I’ve currently got a simple linear scale like this:
const min = Math.min(data); const range = Math.max(data) - min; const step = range / 4; return [ "#e7e9e9", -1, "#ffeda0", min, "#fed976", min + step, "#feb24c", min + step * 2, "#fd8d3c", min + step * 3, "#fc4e2a", max, "#e31a1c" ];
How could one write a function that, given a minimum, maximum, and the number of steps in a scale (in this case three) output the correct numbers for a logarithmic scale?
Advertisement
Answer
A logarithmic scale is linear in its logarithms. So:
const min = Math.min(data); const max = Math.max(data); const logmin = Math.log(min); const logmax = Math.log(max); const logrange = logmax - logmin; const logstep = logrange / 4;
Now where the linear scale has
value = min + n * step;
the logarithmic scale has:
value = Math.exp(logmin + n * logstep);
This will work only if both min
and max
are positive, because you cannot take the logarithm of zero or a negative number and bcause in logarithmic scale, each value has a constant positive factor to the previous one. You can make this work for negative min
and max
by adjusting the signs.
You can do the calculations in other bases, for example 10:
const logmin = Math.log10(min); const logmax = Math.log10(max); value = Math.pow(10, logmin + n * logstep);
Alternatively, you can find the common factor from one step to the next by taking the n-th root of the quotient of the min and max values:
const min = Math.min(data); const max = Math.max(data); const fact = Math.pow(max / min, 1.0 / 4.0);
The values are then found by repeated multiplication:
value = prev_value * fact
or
value = xmin * Math.pow(fact, n);
Both methods might yield “untidy” numbers even if the scale should have nice round numbers, because the logarithms and n-th roots could produce inaccurate floating-point results. You might get better results if you pick a round factor and adjust your min/max values.