Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculate color with two variables (t1, t2)? #32

Open
jfiala opened this issue Jan 27, 2021 · 15 comments
Open

Calculate color with two variables (t1, t2)? #32

jfiala opened this issue Jan 27, 2021 · 15 comments

Comments

@jfiala
Copy link

jfiala commented Jan 27, 2021

We'd like to calculate the color value on a 2-dimensional matrix with 4 colors:
farbverläufe

Is that possible with d3js-scale-chromatic?
It appears all functions provide only one variable t.

Any ideas? :)

Thank you & Best regards,
Johannes

@curran
Copy link
Contributor

curran commented Jan 28, 2021

I was looking at a similar problem lately, and have been meaning to try a solution where two separate scales are used, then the outputs can be blended into a matrix like the one you shared. I'm not sure what the best way of blending the colors is, or if there's any utility that does this inside D3 (color interpolate maybe?), but I did come across this neat library: https://github.com/Loilo/color-blend

Tangentially related:

Will post here if I find anything more.

@curran
Copy link
Contributor

curran commented Jan 28, 2021

Oh, here are some things https://observablehq.com/search?query=bivariate

@curran
Copy link
Contributor

curran commented Jan 28, 2021

In particular https://observablehq.com/@d3/bivariate-choropleth
image

@curran
Copy link
Contributor

curran commented Jan 28, 2021

And there's this:

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

@curran
Copy link
Contributor

curran commented Jan 28, 2021

FWIW:

It's funny that blending is part of the WC3 Spec (CSS does this blending), but do it in JS you need to re-implement blending. I wonder if there's any way to do color blending in JS without one of those libraries... Maybe draw pixels on a Canvas and read the result or something.

@mbostock
Copy link
Member

The typical way of doing this is bilinear interpolation, which you can certainly do using D3. However, if you’re specifically looking to make a bivariate choropleth, I suggest you use a well-designed color palette rather than creating one yourself. This is linked from the D3 bivariate choropleth example:

https://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/

@curran
Copy link
Contributor

curran commented Jan 28, 2021

How might one approach bilinear interpolation using D3?

This is the closest I could find, which implements the interpolation outside D3: https://observablehq.com/@sw1227/bilinear-interpolation-of-tile

Also I'm curious, how might one generalize the approach to more than two variables, for a multivariate choropleth map? I'm investigating how one might approximate the colors of a dot density map, like this one, using multiple single-hue color ramps. Blending the RGBA colors is what came to mind, but perhaps there's a notion of "multilinear interpolation" or something like that.

image

Related:

@mbostock
Copy link
Member

const i1 = d3.interpolateLab(color1, color2);
const i2 = d3.interpolateLab(color3, color4);
const i3 = d3.interpolateLab(i1(t1), i2(t1));
return i3(t2);

@curran
Copy link
Contributor

curran commented Jan 28, 2021

I assume color1, color2, color3 and color4 are outputs from color 4 separate color ramps that go from opacity 0 to opacity 1 with a constant hue, and that t1, t2 should be 0.5?

This works for an even number of colors, but what about an odd number like 3 or 5?

const color1Scale = scaleLinear()
    .domain([0, max(data, color1Value(d))])
    .range(['rgba(85, 255, 0, 0)', 'rgba(85, 255, 0, 0)']);
...
const color1 = color1Scale(color1Value(d)); // White
const color2 = color2Scale(color2Value(d)); // Black
const color3 = color3Scale(color3Value(d)); // Asian
const color4 = color4Scale(color4Value(d)); // Hispanic
const color5 = color5Scale(color5Value(d)); // Other

const t1 = 0.5;
const t2 = 0.5; // Maybe this could be tweaked to get the right balance?

const i1 = d3.interpolateLab(color1, color2);
const i2 = d3.interpolateLab(color3, color4);
const i3 = d3.interpolateLab(i1(t1), i2(t1));
const i4 = d3.interpolateLab(i3(t2), color5); // Valid? Too much weight for color5?
return i4(t2);

@mbostock
Copy link
Member

You’re describing a multivariate color scheme, not a bivariate one.

@jfiala
Copy link
Author

jfiala commented Jan 29, 2021

Whow, many thanks for all the inputs!
I'd like to have a chromatic matrix-map (e.g. with 1.000x1.000 "pixels") instead of the bivariate map with 3x3 or 4x4 in my sample.

The easy way would be to use a 3x3 matrix and then interpolate between the 9 colors, right as Mike suggested?
const i1 = d3.interpolateLab(color1, color2);

I'll give that a try.

And yes, of course it would be nice to have this also for > 2 variables = multivariate.

@jfiala
Copy link
Author

jfiala commented Jan 29, 2021

We generated a chromatic color scheme for one of the palettes of Joshua Stevens (
https://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/):

chromatic_colormap_small

This is what we meant.
Of course it would also be great to have that for 3+ variables, but for now we go with the 2 :).

@jfiala
Copy link
Author

jfiala commented Jan 29, 2021

We were discussing whether for map visualization it really makes sense to actually used the chromatic scheme.

Would a 10x10 matrix (as an improvement of the 3x3 matrix, which is definitely too scarce in variation) suffice? Any opinions on that?

IMHO the main advantage of using the chromatic scheme is to get the precise best matching color for a value t, but for humans probably the difference is not visible :).

Colorbrewer supports up to 9 classes for one variable.

This generator here up to 6 classes:
https://observablehq.com/@benjaminadk/bivariate-choropleth-color-generator

Any opinions on that?

@curran
Copy link
Contributor

curran commented Jan 29, 2021

Opinions on this vary wildly.

Some argue that because the eye can't really differentiate between more than 12 or so distinct colors, one should not use more than that.

Others (myself include) argue that ideally you'd use a continuous color ramp (which could be approximated with more classes, maybe 100 classes or so), because the eye is really good at picking up on subtle color differences when the polygons share a border and there's no stroke in between them.

@jfiala
Copy link
Author

jfiala commented Jan 30, 2021

Thanks for your opinion, I'll also follow that continuous approach, especially as it has the advantage of not having to classify the data. Thanks for the hint regarding removing the borders, we'll give that a try and let you know...!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants