Show node with details from nodeData of other node

Hi,
I have node based on a complex node data structure. The node template displays only some of the available data.
Now I would like to click on some panel of the node and add a separate “detail” node to the diagram which displays a table with the detail information.

Let’s take this node for example:
grafik

The Products "p1 and “p2” are actually a more complex structure with additional properties in the nodeData, something like this:

{
	"parameterGroups": [
		{
			"parameters": [
				{
					"class": "Product",
					"value": 32
				},
				{
					"class": "InitialStock",
					"value": 0
				},
				{
					"class": "CarrierCapacityProduct",
					"value": 1
				}
			]
		},
		{
			"parameters": [
				{
					"class": "InitialStock",
					"value": 0
				},
				{
					"class": "CarrierCapacityProduct",
					"value": -1
				},
				{
					"class": "Product",
					"value": 39
				}
			]
		}
	]
}

Now, if the user clicks on the p1/p2 table row, I would like to display a separate node (permanently, so no adornment) with a table structure displaying the additional attributes. (Similar to the comments example).

With my current knowledge, I would copy the parameterGroups array to the new node when the row has been clicked. However, that means I would duplicate the data, and each time the data in the original node changes, I would also need to update the data in the detail node.

Is there any way to avoid copying the data? Or some other approach to handle this situation?

Should those parameter definitions exist even if there are no nodes? If so, put them in the Model.modelData.

Otherwise, it is OK to have shared data, but you’ll need to manage them. For example, Model.toJson and Model.fromJson won’t maintain the sharing of those data. And modifying them requires you to update all bindings using the property.

Hi,
the parameter definitions depend on the main node, so they should not exist if there are no nodes.

So how can I share data between nodes? Or is your “otherwise” suggestion also refering to the modelData approach?

@walter Could you clarify how I can have shared data between nodes except for the Model.modelData?

Sorry, we’ve been busy releasing 2.1.7 and addressing another bug that came in this afternoon. I’ll create a simple demonstration as soon as I find some free time.

1 Like
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
  function init() {
    var $ = go.GraphObject.make;

    function isDetailsLink(link) {
      return link instanceof go.Link && link.category === "Comment" && link.fromNode.category === "Details";
    }

    function hasDetails(node) {
      return node instanceof go.Node && node.linksConnected.any(isDetailsLink);
    }

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
          {
            "undoManager.isEnabled": true,
            "draggingTool.computeEffectiveCollection": function(parts, options) {
              var map = go.DraggingTool.prototype.computeEffectiveCollection.call(this, parts, options);
              parts.each(function(node) {
                if (node instanceof go.Node) {
                  node.linksConnected.each(function(l) {
                    if (isDetailsLink(l)) {
                      map.add(l, new go.DraggingInfo());
                      map.add(l.fromNode, new go.DraggingInfo(l.fromNode.location));
                    }
                  });
                }
              });
              return map;
            },
            "ModelChanged": function(e) {     // just for demonstration purposes,
              if (e.isTransactionFinished) {  // show the model data in the page's TextArea
                document.getElementById("mySavedModel").textContent = e.model.toJson();
              }
            }
          });

    myDiagram.nodeTemplate =
      $(go.Node, "Auto",
        $(go.Shape,
          { fill: "white", portId: "" },
          new go.Binding("fill", "color")),
        $(go.Panel, "Vertical",
          { margin: 8 },
          $(go.TextBlock, new go.Binding("text")),
          $(go.TextBlock, new go.Binding("text", "info", function(i) { return i.t; }))
        ),
        {
          locationSpot: go.Spot.Center,
          doubleClick: function(e, node) {
            if (hasDetails(node)) return;  // don't create a second Details node
            e.diagram.commit(function(d) {
              var newdata = { category: "Details", info: node.data.info };
              d.model.addNodeData(newdata);
              var newnode = d.findNodeForData(newdata);
              newnode.location = new go.Point(node.location.x, node.location.y + node.actualBounds.height + 40);
              var newlink = { category: "Comment", from: d.model.getKeyForNodeData(newdata), to: node.key };
              d.model.addLinkData(newlink);
            });
          }
        }
      );
    
    myDiagram.nodeTemplateMap.add("Details",
      $(go.Node, "Table",
        { background: "lightgray", locationSpot: go.Spot.Center },
        $(go.TextBlock, { row: 0 }, new go.Binding("text", "info", function(i) { return i.t1; })),
        $(go.TextBlock, { row: 1 }, new go.Binding("text", "info", function(i) { return i.t2; })),
        $(go.TextBlock, { row: 2 }, new go.Binding("text", "info", function(i) { return i.t3; })),
      ));

    myDiagram.model =
      $(go.GraphLinksModel,
        {
          nodeDataArray:
            [
              { key: 1, text: "Alpha", color: "lightblue",
                info: { t: "lowercase", t1: "text one", t2: "text two", t3: "text three" } },
              { key: 2, text: "Beta", color: "orange",
                info: { t: "UPPERCASE", t1: "ONE", t2: "TWO", t3: "THREE" } }
            ],
          linkDataArray:
            [
              { from: 1, to: 2 }
            ]
        });
  }

  function test() {
    myDiagram.commit(function(d) {
      d.selection.each(function(n) {
        if (n instanceof go.Node) {
          d.model.set(n.data.info, "t", n.data.info.t + ".");
          d.model.set(n.data.info, "t2", n.data.info.t2 + "!");
          n.updateTargetBindings("info");
          n.findNodesConnected().each(function(m) { m.updateTargetBindings("info"); })
        }
      })
    });
  }
</script>
</head>
<body onload="init()">
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
  <p>
    Double click on a node to add a "Details" node with a "Comment" link connecting to the regular node.
    The DraggingTool has been customized to drag the "Details" node (if one exists) along with the regular node.
  </p>
  <button onclick="test()">Test</button>
  <p>
    Clicking the "Test" button will append a character on the selected node's info.t and info.t2 properties.
  </p>
  <p>
    The following output of Model.toJson does not preserve the shared object that is the value of node.data.info.
  </p>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
</body>
</html>

Hello walter,
thank you for the example. The DraggingTool customization is also very useful!
I have copied your example to a codepen, if anybody is interested to try it out.

So basically after adding the details node, the info object is shared between both nodes, but when serializing/deserializing the model, the shared object is not preserved. I manually need to make sure that it will be shared again if desired.

Thanks!

Yes, that is correct.

It also occurs to me that if you copy a node, you might want to make copies of the shared object some times but not other times. I don’t know what sharing policies you want to implement. You will probably need to provide a custom Model.copyNodeDataFunction for each newly created model.