Combining Inputs.select and drill down chart

Hello,
I would welcome advice, please? How do I combine an ‘Inputs.select’ drop-down menu with a drill-down chart? Currently when I drill down to a second level of chart, and then change the ‘Inputs.select’ drop-down menu, then the chart resets to the upper (default) chart.

A example is provided here: Test2 drill down-drill up plot with dropdown input / fionaebi / Observable

For example, I would like to drill down from the ‘rectangles’ chart to the ‘circles’ chart, and then change the drop-down menu from e.g. ‘ClinChem’ to ‘Hematology’ and stay with the ‘circles’ chart for the updated ‘Hematology’ data. Currently, the chart resets to show the upper level ‘rectangles’ chart for the updated ‘Hematology’ data.

I got a bit overwhelmed trying to fix your chart directly (sorry, maybe I’m sleepy!) but I put together a stripped-down example that might help: Combining Inputs with internal interactivity in a chart / Toph Tucker / Observable

The core idea is that, if a cell references another cell, the cell re-runs entirely when the cell it depends on changes. So, if your chart depends on an Input, the whole chart gets destroyed and recreated every time the Input changes.

The trick is that a chart is a DOM node, and a DOM node is a JavaScript object, and you can stick functions onto that object and call them. So we stick an update method onto the chart and return it with the chart. Then, a different cell refers to the Input (so it’ll re-run whenever the Input changes) and calls the update method each time with the value of the Input.

That way, you can run only the code in update, and it can change the existing chart without destroying it.

2 Likes

I had the same issue; a lot of subtle interdependencies in the original notebook.

FYI: Your notebook doesn’t seem to work in Firefox on my Mac.

Oh interesting, it works for me in Firefox for Mac, v97. What do you see broken?

The problem, evidently, is that I don’t know how to use Firefox. :confused:
Sorry for the confusion!

Dear tophtucker,
Many thanks for your prompt and helpful simplified code example and explanation about the ‘update’ method. I’ve reworked my approach to apply these suggestions, and have now got a version up and running, which is great.
I completely understand that my original posted example was a bit too complicated :slight_smile:
Much appreciated!

1 Like

BTW The only thing that I don’t understand about your solution code is why the line “// Call render once initially // render();” is required. The code seems to work with or without this line included?
Thanks

Glad it helped, and good question! You don’t need that initial render call if you know that chart.update(color) is gonna run and call render inside update. In this case, we do know that. But if you didn’t have the update cell — e.g., if you wanted to reuse this chart somewhere as a static thing without controls — then it wouldn’t. So I guess it just felt like a good practice here to have the update code also run on initialization.

(Another weird thing (added to the end of the notebook) is that if you want to import the chart into another notebook, you also have to import and run the update cell!)

The alternative would be to not use a cell that has side effects, but instead reference the select (or color in Toph’s case) input via its viewof prefix. That way the chart cell won’t be invalidated when the value changes:

viewof myInput = Inputs.select(/* ... */)
chart = {
  const sel = d3.select(/* ... */);
  const input = viewof myInput;

  // Draw the chart for the first time.
  updateChart(input.value);

  // Redraw the chart whenever the selection changes.
  input.addEventListener('input', updateChart);
  // Remove event handler when this cell is invalidated.
  invalidation.then(() => input.removeEventListener('input', updateChart));

  return sel.node();

  function updateChart() {
    const value = input.value;
    // do update stuff
  }
}