This is really neat. I was just wishing for something like it the other day :)
Out of curiosity, under what circumstances might the scale not be available? In the example plot above, since the value of "fill" is "imdb_rating", wouldn't that cause Array.from(data).fill(contrast(options.fill)) to try to parse "imdb_rating" as a color string? (Maybe this never happens in practice due to the internal logic of when the scale might not be available)
This might be answered below in the comment about the hexbin ("note however that the color scale must, in that case, be constructed in advance by specifying its domain").
Good idea! Hmm, interesting. So `options.fill` is undefined in that case (possibly since hexbin consumes the option and does not pass it on), and the d3 color constructor turns out to accept undefined and turns it into a NaN color, which is why that branch still works.
I think I still don't totally understand why the hexbin case requires the color domain to be set in advance, and by what mechanism setting the domain makes it work, but that's probably a question for another day... 😄
Minor opinionated nitpick: I feel the example becomes clearer if "fill" is not aliased to "F". I would even consider not to destructure the argument, because it's not immediately obvious that it represents the channels, even when looking at the documentation.
I'd also suggest to use an arrow function, so that no one goes searching for "this" references.