Editting graph data from interactions within graph

So I’m trying to recreate this block in this notebook. In the notebook that that one is forked from, I managed to get a cell to update dynamicData, which in turn updated the graph. However in my new notebook, I’m trying to get a click on the graph to update the data, which in turn updates the graph. This feels circular, so I wouldn’t be surprised if it isn’t possible. Is there any way around this? Currently I feel the best solution is to merge the data into the chart cell.

Any feedback is appreciated!

That’s a cool block! Reminds me a bit of “Directed graph editor” by Ross Kirsling.

Re: your question: one way of doing it like the way you describe is to make dynamicData into a mutable variable. You could have your d3 selections in your chart cell depend on dynamicData directly and then have your mousedownDefault function reassign mutable dynamicData (you would need to call something like mutable dynamicData = newValue because it’s the “setter” method of mutable dynamicData which forces dynamicData to be re-evaluated). That way, when mousedownDefault is called, it will update mutable dynamicData which in turn updates dynamicData, which will then cause chart to be re-evaluated.

Here’s a toy example that illustrates the idea. Create one cell that says:

mutable a = 0

And another cell that says:

{
  const b = html`<button>click me! a = ${a}`;
  b.onclick = () => mutable a++;
  return b;
}

You should see that clicking on the button cell increments the value a displayed in its label.

Another way of doing this that you might consider is to instead structure the notebook so that dynamicData is a local variable in the chart cell so that you don’t have to worry as much about cross-cell communication. Then you could assign dynamicData to the svg.node().value and use the viewof syntax with chart to expose it to other cells.

1 Like

I’ve gotten the above notebook mostly working as intended. The only issue now is that when new nodes are created, a console error appears stating that positional properties are NaN, and I’m not sure whether it’s because of my implementation or an aspect of Observable. Any advice on why that might be?

It looks to me like the issue is that even after mousedownDefault is called, ticked can run one more time before the chart cell is rerun, and when that happens the dynamicData.nodes and dynamicData.links objects are still in a weird state.

One way of working around this is to put in a flag which is set to true when mousedownDefault is called:

  let postclick = false;
  function mousedownDefault(x, y) {
    addNode(d3.mouse(this)[0], d3.mouse(this)[1])
    postclick = true;
  }

And then wrap all of ticked in a guard like this:

  function ticked() {
    if(!postclick) {
    edges
        .attr("x1", d => {
            // console.log("::", d.source.x)
            // console.log(":", d.source)
            return d.source.x
        })
        .attr("y1", d => d.source.y)
        .attr("x2", d => d.target.x)
        .attr("y2", d => d.target.y);

    
    nodes
        .attr("transform", d => {
      // console.log(d.x)
      // if (!d.x) {
      //   console.log("bad!")
      // }
      return `translate(${d.x},${d.y})`
    })
  };
  }

Thanks for your help, I managed to get the notebook working as desired! I figured out that the root cause of the previously described problem was that I hadn’t been joining the data correctly with the id:

let nodes = svg.selectAll("g").data(dynamicData.nodes, d => d.id)

This made the postclick workaround unnecessary and fixed the other problems as well.

1 Like