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.
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.
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.
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
Much appreciated!
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
}
}