d3 Swatches imports in Framework

Hi Again,

Likely another silly question. I’m trying to implement the static colors in Observable Framework but run into an import error. (Example followed is here: Static Colors / Aaron Kyle Dennis | Observable).

I was able to make this work in Notebook but can’t seem to replicate in the .md file.

I get the following error:

TypeError: Module name, ‘@d3/color-legend’ does not resolve to a valid URL.
RuntimeError: Module name, ‘@d3/color-legend’ does not resolve to a valid URL.

You can’t import notebooks into Observable Framework. Two alternatives:

  • Use Observable Plot instead of the D3 color legend notebook. (It’ll happily make standalone color legends for you and is more capable than the notebook.)
  • Copy the code out of the notebook and into a standalone JavaScript file. (But please preserve the copyright notice when you copy D3 code out of our notebooks.) You might find the observable convert command helpful here.
1 Like

As a general temporary workaround you can also use the following helper to import notebooks directly:

~~~js
function importNotebook(invalidated, url) {
  const Runtime = import("npm:@observablehq/runtime@5").then(m => m.Runtime);
  const define = import(url).then(m => m.default);
  const module = Promise.all([Runtime, define]).then(([R, d]) => {
    const r = new R;
    invalidated.then(() => r.dispose());
    return r.module(d);
  });
  return new Proxy({}, {
    get: (_, name) => name === "then" ? module : module.then(m => m.value(name))
  });
}
~~~

~~~js echo
const {Swatches} = importNotebook(invalidation, "https://api.observablehq.com/@d3/color-legend.js?v=4");
~~~

~~~js echo
display(Swatches(d3.scaleOrdinal(["blueberries", "oranges", "apples"], d3.schemeCategory10)))
~~~

Be aware though that this is highly inefficient since it loads a separate Runtime module and relies on external dependencies that can neither be bundled nor prefetched.

Maybe reframing the question, when I applied filtering to my dataset in Framework - I noticed that the color would stay the same when selecting a specific “TCXO”. In general I was able to figure this out by just adding another mark where I plot all the data with the stroke being TCXO but set the opacity to “0”. This is the only way I was able to get the color legend to show but I may be approaching this the wrong way. I’m not necessarily focused on implementing “Swatches” but that was the reference I found on how to keep static colors when toggling the data. My new implementation works but, again, I’m not sure if it’s the best approach.

I do not recommend importing from notebooks like @mootari suggests. (I thought about explicitly mentioning that in my post.) Yes, it’s possible, but it’s complicated and performs poorly, and also means your site could break if the notebook changes. I would go further and say that showing users how to do this is likely to cause more problems than it fixes. :sweat_smile:

1 Like

The problem is that you’re filtering the data before passing it to Plot, so Plot is seeing a different subset of the data and therefore is giving inconsistent color encodings.

Here are a few ways to fix this:

  • Use Plot’s filter transform instead of filtering the data yourself. This way Plot sees the unfiltered data and, assuming the unfiltered data isn’t changing, will produce a consistent color encoding.
  • Specify the color scale domain explicitly, e.g., color: {domain: ["A", "B", "C", "D"])}
  • Declare a standalone color scale from the full data and then pass it into the plot, e.g., const color = Plot.scale({color: {domain: …}}); and then Plot.plot({color, …}).
1 Like

Fwiw, neither did I:

Thanks a lot! I’ll try this out. I’ll still mark @mootari’s solution as the correct one since that is what the original question was. This is super helpful though and very clear!