Multivariate Color Scheme
As a follow-up to discussion in https://github.com/d3/d3-scale-chromatic/issues/32 , I'd like to pose the question of how to best calculate a multivariate color scheme. The use case is for building multivariate choropleth maps.
I'm investigating how one might approximate the colors of a dot density map, like this one.

The most sensible approach to me at the moment seems to be:
- Define multiple single-hue color ramps that vary in opacity from 0 to 1, for each variable.
- Blend those RGBA colors together.
Related:
- value-by-alpha concept.
- Multivariate interpolation
Perhaps this implementation of a bivariate color scheme could be generalized to handle an arbitrary number of variables.
colors = blendMode => {
const scale1 = chroma.scale([color1, lightest]).mode(colorMode).correctLightness().colors(rows)
const scale2 = chroma.scale([color2, lightest]).mode(colorMode).correctLightness().colors(rows)
const data = []
for(let i = 0; i < rows; i++) {
for(let j = 0; j < rows; j++) {
data[i * rows + j] = {
color: chroma.blend(scale1[i], scale2[j], blendMode),
x: i * size / rows,
y: j * size / rows
}
}
}
return data
}
From https://observablehq.com/@benjaminadk/bivariate-choropleth-color-generator
This blends the colors using https://vis4.net/chromajs/#chroma-blend
d3-scale-chromatic is for concrete color schemes; what you’re describing is not a specific color scheme but a strategy for encoding multivariate data as a color, so I’ve transferred this issue to d3-scale. However, I don’t have any plans to implement this feature in d3-scale either, since d3-scale is currently exclusively for one-dimensional encodings.
Got it. Thanks!
To clarify scope: the premise of this issue is to propose that d3-scale include some functionality for multivariate color scales/schemes.
I'll be investigating this design space in external work, and will post updates here if any concrete ideas come to mind for additions to d3-scale.
Here's a first stab at an idea for how to do this.
The approach here is to blend the colors together, using colorBlend.js.
const multivariateScale = (d) => {
const colors = [
whiteScale(whiteValue(d)),
blackScale(blackValue(d)),
asianScale(asianValue(d)),
];
const blended = colors.map(rgba).reduce(colorBlend.multiply);
const { r, g, b, a } = blended;
return `rgba(${r},${g},${b},${a}`;
};
The color ramps are square root scales that go from transparent to opaque.
const whiteScale = scaleSqrt()
.domain([0, maxValue])
.range([transparent(whiteColor), opaque(whiteColor)]);
I started with a linear scale and randomly tried square root, and it looked better. Not grounded at all in best practices.
I'm interested in the larger conversation around best practices for this sort of thing. I have a hunch that this technique can be very powerful for certain datasets.
