Generate GraphLinksModel JSON which does not include any collapsed group's member parts

I have the initial state of the diagram as shown in the image.

Let’s say we have now collapsed the “Group 2” group and now the diagram looks like this.

I want to generate GraphLinksModel JSON which does not include any collapsed group’s member parts data. However, it should have all the links attached to those parts (links should connect to/from the collapsed group just like it does in the second image). We also want to store the count of group member parts in its node data.

Can you please help in generating such GraphLinksModel JSON most optimally?

Just to be clear, it seems like you want to produce some JSON-formatted text which cannot be read in to re-create the original graph, because all of the information about members of collapsed groups will be missing. Do you want that JSON text to be read by Model.fromJson?

It’s as if you changed all collapsed groups into regular nodes and deleted their subgraphs. Do you care if the Diagram and Model are actually modified that way?

Although changing a Group into a regular Node is not possible, you could copy the model and its data, make the replacements, and then write that out.

Yes, I want that JSON to be read by Model.fromJson. It’s fine if the diagram and model are modified, but would appreciate it if the modifications are done to a copy instead of the original.

Later, I can create a sample for you when I have more time.

1 Like

Thank you!

I can see the topic tag has been updated from GoDiagram 10 to GoJS. Just letting you know I am looking for a GoDiagram 10 solution and request you to provide a sample for the same.

I don’t have time now to do this in GoDiagram, but here’s a solution in GoJS. Note that the templates don’t really matter except that the Group template has to have a TwoWay Binding on “isSubGraphExpanded” so that the copied model/diagram has the groups in the desired expansion state. If you don’t want to remember that state in the model, you can work around it with additional code to restore the Groups in the copied Diagram the way that they are in the main Diagram.

<!DOCTYPE html>
<html>
<head>
  <title>Replacing Collapsed Groups with Simple Nodes</title>
  <!-- Copyright 1998-2023 by Northwoods Software Corporation. -->
  <meta name="description" content="Write out a GraphLinksModel in JSON form where the collapsed Groups have been replaced by simple Nodes">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:350px"></div>
  <button id="myInitButton">Initial</button>
  <button id="myFullButton">Full JSON</button>
  <button id="myTopOnlyButton">No Collapsed Groups JSON, Loaded</button>
  <textarea id="mySavedModel" style="width:100%;height:450px"></textarea>

  <script src="../release/go.js"></script>
  <script id="code">
const $ = go.GraphObject.make;

myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      // the layout doesn't really matter
      layout: $(go.TreeLayout, { setsPortSpot: false, setsChildPortSpot: false })
    });

// the templates don't really matter,
// EXCEPT that the group template(s) need to have a TwoWay Binding on "isSubGraphExpanded"
// so that the copied model produces a copied diagram with the right state for the groups

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    $(go.Shape, { fill: "white" },
      new go.Binding("fill", "color")),
    $(go.TextBlock, { margin: 8 },
      new go.Binding("text"))
  );

myDiagram.groupTemplate =
  $(go.Group, "Vertical",
    {
      defaultAlignment: go.Spot.Left,
      layout: $(go.GridLayout, { wrappingColumn: 1, cellSize: new go.Size(1, 1) })
    },
    new go.Binding("isSubGraphExpanded").makeTwoWay(),
    $(go.Panel, "Horizontal",
      $("SubGraphExpanderButton"),
      $(go.TextBlock,
        new go.Binding("text"))
    ),
    $(go.Panel, "Auto",
      $(go.Shape, { fill: "transparent", strokeWidth: 1.5 },
        new go.Binding("stroke", "color")),
      $(go.Placeholder, { padding: 10 })
    )
  );

myDiagram.linkTemplate =
  $(go.Link,
    { relinkableFrom: true, relinkableTo: true },
    $(go.Shape, { stroke: "blue", strokeWidth: 2 }),
    $(go.Shape, { toArrow: "OpenTriangle" })
  );

function init() {
  myDiagram.model = new go.GraphLinksModel(
    {
      nodeDataArray: [
        { key: -1, text: "Group 1", isGroup: true },
        { key: 1, text: "Alpha", color: "lightblue", group: -1 },
        { key: 2, text: "Beta", color: "orange", group: -1 },
        { key: 3, text: "Gamma", color: "orange", group: -1 },
        { key: -11, text: "SubGroup 1a", isGroup: true, group: -1 },
        { key: 4, text: "Theta", color: "lightgreen", group: -11 },
        { key: -12, text: "SubGroup 1b", isGroup: true, group: -1 },
        { key: 5, text: "Iota", color: "lightblue", group: -12 },
        { key: -2, text: "Group 2", isGroup: true },
        { key: 11, text: "Delta", color: "lightgreen", group: -2 },
        { key: 12, text: "Epsilon", color: "pink", group: -2 },
        { key: -22, text: "SubGroup 2", isGroup: true, group: -2 },
        { key: 21, text: "Zeta", color: "orange", group: -22 },
        { key: 13, text: "Eta", color: "lightgreen", group: -2 },
        { key: -3, text: "Group 3", isGroup: true },
        { key: 31, text: "Kappa", color: "lightgreen", group: -3 },
        { key: 32, text: "Lambda", color: "lightblue", group: -3 },
        { key: 33, text: "Mu", color: "pink", group: -3 },
        { key: 99, text: "Omega", color: "gray" }  // test: not a group and not in a group
      ],
      linkDataArray: [
        { from: 1, to: 11 },
        { from: 1, to: 12 },
        { from: 2, to: 21 },
        { from: 3, to: 21 },
        { from: 3, to: 4 },
        { from: 21, to: 4 },
        { from: 21, to: 4 },
        { from: 13, to: 4 },
        { from: 4, to: 5 },
        { from: 5, to: 99 },
        { from: 11, to: 31 },
        { from: 12, to: 32 },
        { from: 13, to: 33 },
      ]
    });
}
init();

document.getElementById("myInitButton").addEventListener("click", e => {
  init();
});

document.getElementById("myFullButton").addEventListener("click", e => {
  document.getElementById("mySavedModel").textContent = myDiagram.model.toJson();
});

document.getElementById("myTopOnlyButton").addEventListener("click", e => {
  // copy the model
  const model = go.Model.fromJson(myDiagram.model.toJson());
  // construct a new view-less Diagram from the copied Model
  const diag = new go.Diagram(
    { // no HTMLDivElement, so we need to specify a viewSize
      viewSize: new go.Size(100, 100),
      nodeTemplateMap: myDiagram.nodeTemplateMap,
      groupTemplateMap: myDiagram.groupTemplateMap,
      linkTemplateMap: myDiagram.linkTemplateMap,
      "InitialLayoutCompleted": e => {
        replaceCollapsedGroupsWithNodes(diag);
        // show the simpler JSON
        document.getElementById("mySavedModel").textContent = diag.model.toJson();

        // OPTIONAL: show the new model in the current diagram 
        myDiagram.model = go.Model.fromJson(diag.model.toJson());
      }
    });
  diag.model = model;
});

function replaceCollapsedGroupsWithNodes(diag) {
  diag.findTopLevelGroups().each(g => walkGroupsReplaceCollapsed(g));
}

function walkGroupsReplaceCollapsed(group) {
  if (group.isSubGraphExpanded) {
    group.memberParts.each(mem => {
      if (mem instanceof go.Group) walkGroupsReplaceCollapsed(mem);
    });
  } else {
    const diag = group.diagram;
    const model = diag.model;
    const nogroup = { __gohashid: undefined, isGroup: false };
    const nodedata = Object.assign({}, group.data, nogroup);
    delete nodedata.isGroup;
    model.addNodeData(nodedata);
    const node = diag.findNodeForData(nodedata);
    group.findExternalLinksConnected().each(link => {
      const from = link.fromNode;
      if (from && (from === group || from.isMemberOf(group))) {
        link.fromNode = node;
      }
      const to = link.toNode;
      if (to && (to === group || to.isMemberOf(group))) {
        link.toNode = node;
      }
    });
    if (group.containingGroup) node.containingGroup = group.containingGroup;
    diag.remove(group);
  }
}
  </script>
</body>
</html>
1 Like