Dealing with circular definitions when zoom and pan are calling each other

I am following this example to recreate a zoomable bar chart in Javascript and it worked perfectly. I decided to add my graph to Observable but (unsurprisingly) it is not working in the Observable runtime environment. In the link I’ve shared it does say that we can’t copy-paste vanilla Javascript code into Observable and they have shared an example of how this should be rewritten in Observable but I am unable to understand it. Here is the new code: Focus + Context / D3 / Observable.

Can anybody explain to me how they’re handling zoom->brush and brush->zoom events here? Any pointers will be appreciated! Thank you.

Hi @AbreezaSaleem! Let’s go through those cells in order of data flow:

1a. chart:
This cell produces a DOM element (the chart SVG), but also “sneakily” attaches an .update() method to it:

  // ...
  return Object.assign(svg.node(), {
    update(focusX, focusY) {
      gx.call(xAxis, focusX, height);
      gy.call(yAxis, focusY, data.y);
      path.attr("d", area(focusX, focusY));
    }
  });

We can call chart.update(fromDate, toDate) from another cell to have the chart change its visible range. (As an aside, I think that focusX and focusY are legacy names from another implementation.)

1b. viewof focus:
This cell is a view, a special Observable construct. Think of it as a custom form element with a value property that reflects its user input. When you brush, this value property is set to the selected range:

  // ...
  function brushed({selection}) {
    if (selection) {
      svg.property("value", selection.map(x.invert, x).map(d3.utcDay.round));
      svg.dispatch("input");
    }
  }

Other cells can reference this value directly as focus (instead of viewof focus).

2. update:
This cell ties the previous two together. Whenever we get a new value for focus, Observable will automatically rerun this cell. The important line here is this one:

  // ...
  chart.update(x.copy().domain(focus), y.copy().domain([0, maxY]));

Sadly, as per @Fil’s comment, this example does not yet seem to implement zoom.

To make it work for Observable, we’d likely implement chart as a viewof, too, and ensure that its value can be set from outside. We can then bind both viewof focus and viewof chart together so that they update each other: Synchronized Inputs / Observable | Observable

1 Like

Thank you for the prompt detailed response! I initially made my graph using vanilla Javascript and now I see I have quite a lot to learn about Observable as well. I will further look into the chart as a viewof approach. Thanks agian

We have just published an extension of this notebook: Focus + context II / D3 | Observable
in which we use d3.dispatch to implement communication between the different cells.

Sorry for the belated answer; I hope this is still useful.

Thank you for your answer! It was exactly what I needed. I got caught up in other things since I posted my question but I finally got back to this notebook and I’m very happy to report that I’ve successfully made it work using d3.dispatch. :slight_smile:

Here’s a link to my notebook if you’d like to take a look: D3 Scatterplot / Abreeza Saleem | Observable

1 Like