Importing a cell and re-using it multiple times?

Hi,

I have a notebook where I’m using the import {x} with {y} syntax to import a chart from another notebook and override its data. I’d like to use this same chart multiple times within the notebook I’m importing to.

Currently I’m solving this by importing the chart multiple times and renaming the chart, e.g.

import { chart as someOtherChart } with { myData as data} from "@clhenrick/chart-notebook"
import { chart as anotherChart } with { myOtherData as data} from "@clhenrick/chart-notebook"

which seems to be working fine, but I’m curious if there’s a more “dry” way to do it.

thanks in advance!

  • Chris

Hi Chris,

Given a chart that references some data reactively — yes, that is currently the best way to handle multiple bindings of the chart.

But clearly, it’s not ideal. Another way to go would be to implement your chart as a function of data:

function chart(data) {
  // Previous contents of chart go here.
}

Then you can import chart once, and call it multiple times with different data definitions.

This pattern — taking a reactive cell definition, and rolling it up into a function of one or more of its dependencies — is something that we’re looking into adding to Observable as a first-class part of the language. It’s an important pattern for reusing cells as components, and we want to support it better.

cc-ing @tophtucker, in case he has some thoughts.

3 Likes

Yeah, I agree with you both. Once I find myself importing a cell multiple times like that, I usually do end up rewriting it like Jeremy described, but it’s frustrating that I have to, and then the chart cell just returns a function instead of showing you a nice reactive chart, so I add another cell to call the function. It ends up working fine but not as smooth as I’d like!!

1 Like

Thanks @jashkenas and @tophtucker. I did think about turning the “chart” cell into a function that accepts data, however one downside of doing this is that any cells within the chart’s notebook that react to a “data” cell (e.g. x and y scales that use data attributes for their domains) will no longer be reactive. So it seems that with this approach you would have to refactor much of the chart’s notebook to live within the chart function cell and thus lose some of the benefits of Observable’s reactivity.

2 Likes

I made a little utility that you can use in your notebooks that will emulate this, without having to re-write cells. Here’s how it could work in your case:

import {notebookFactory} from '@asg017/notebook-factory'

factory = notebookFactory("@clhenrick/chart-notebook")

factory('chart', {data:myData}) // renders what would've been "someOtherChart"

factory('chart', {data:myOtherData}) // renders what would've been "anotherChart"

But, I’d love to see this have first-class support on Observable! I’m working on a project that uses Oservable tools, and it would be great to have this pattern there as well. Here’s a few ideas I’ve had on how this could look, to start up a conversation:


// Idea 1: bracket syntax in with statement makes the imports factory functions
import {chart} with [data, color] from '@some/color-chart'

chart(myOtherData, myOtherColor) // arguments in same order as with NamedImports

// Idea 2: bracket NamedImports in import to make factory function
import {[chart], data} from '@some/color-chart'

// keys are cell names to redefined, values are the values to override with
chart({
  data: myData,
  color: myColor
})

data // non-bracketed imports would import like normal. This would be the normal data array

Idea #3: Maybe there could be a special `runtime` builtin cell that exposes the runtime of the current notebook? This could be similar to how `invalidation` or `visibility` are special runtime-defined builtins. That way, you could define modules on the fly in code, and create factory-like functions without having to load the Observable runtime yourself.

Let me know if I should move this into a Github issue or some place else!

1 Like