Import a chart into another chart?

I’ve been studying this example of how to embed a D3 chart in a tooltip of another D3 chart.

Given that Observable allows to you import a chart from another notebook. (e.g.
import {chart as histogram} with {histogram_data as data} from "@mbostock/d3-histogram"), is it possible to use import to embed one chart into another, rather than writing the D3 for both all in one cell?

Hey Bob,

Yep, when you import a chart like in that example - {chart as histogram}, you can also embed it in another graphic if you’d like.

In that example, histogram is an HTML node - an SVG element that is the chart. So you can append it inside of another visualization if you want, instead of making it the main value of a cell.

Here’s an admitted contrived example of showing a chart inside of itself with an import:

Hi Tom,

Belated thank you for your help and example. I was able to make this work, but can you help me understand the purpose of .node() in your example?

    .attr("transform", `translate(${margin.left},${})`)

As you can imagine, trying to Google “Javascript .node function” returns a lot of noise. :slight_smile:

It’s a D3 API, selection.node. It returns the first (non-null) element in this selection. Once you have the SVG node, you can call node.appendChild.

Another way to do this would be

    .attr("transform", `translate(${margin.left},${})`)
  .append(() => embeddedChart);

OK, I’m part way there. I am able to import a static mouseover chart into my main chart. But when I try to change the imported chart based on the moused-over data, all hell breaks loose as it appends new versions of the imported chart. I just published my attempt here:

It behaves itself until I uncomment the “with” clause here:

import {chart as top_10_manufacturers_for_year} 
// with {hover_year as year}
from "@bawbgale/top-aircraft-manufacturers-by-year-built"

For reference, I am trying to reproduce a viz I created in Tableau here:!/vizhome/1946AircraftStillFlying/1946Dashboard

Any help would be appreciated with this learn-me-some-D3 exercise. :slight_smile:

1 Like

The reason it’s “exploding” :boom: is that you have a circular dependency and are using side-effects.

The circular dependency is that the chart sets mutable hover_year, and top_10_manufacturers_for_year depends on hover_year, so whenever you assign to mutable hover_year, it re-evaluates the chart cell and creates a new chart from scratch.

The side-effect is that the chart cell appends the tooltip to the document.body, and so every time the chart cell is run, it adds a new tooltip to the body and never disposes the old tooltip.

There are ways to fix both of these problems… but it’ll take me a bit more time, so here’s a quick reply in the interim.

Here’s a quick sketch at an alternative design that’s easier to achieve: two charts in separate cells, with the second chart being derived from a subset of the data defined by the first chart.

It is possible to do this in a tooltip style, but you have to do some additional work to avoid the circular dependency problem.

Thanks for the insights, @mbostock. Sounds like if I want to embed a viz in a tooltip, I’m better off building it all in one cell (like the “finalCloro” example I originally linked to) rather than trying to piece it together via import.

The primary advantage of the second chart in a separate cell, rather than in a tooltip, is that you break the circular dependency: the first chart exposes its selection (the subdata) which then drives the second chart. You could still use imports to implement the charts separately; I did it in one notebook because it was quicker to sketch.

Here’s an update to the sketch that shows how to combine the two cells into a tooltip style:

To combine the cells, there’s a new cell at the top like so:

${viewof subdata}
${subchart ? html`<div style="
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  background: white;
  border: solid;
">${subchart}</div>` : ""}

In addition, I changed the two charts definitions to wrap the returned cells in a DIV so as to prevent the inspector from displaying each chart in a separate cell.

// Wrap to prevent the inspector from displaying inline.
return document.createElement("div").appendChild(svg.node());