Show and Hide Bubble (attached to a node/group on the Canvas) where the bubble contains a collection of nodes and be able to drag and drop

Hi GoJS Team,
We would like to achieve the functionality as shown in the attached image:

  1. Show and hide bubble with list of nodes/shapes/parts
  2. should be able to drag and drop each node (purple and blue colored ones) from inside the bubble on to another bubble attached to a different Node elsewhere on the canvas.
    Could you please provide a best way to implement this if it is possible? Please guide me to any Sample if you have.

Thank you!

<!DOCTYPE html>
<html>
<head>
<title>Nodes with Popup Groups</title>
<!-- Copyright 1998-2019 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
  function init() {
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
          {
            allowCopy: false, // need to decide how to handle copying model data
            layout: $(go.TreeLayout, { layerSpacing: 130 }),
            "InitialLayoutCompleted": function(e) {
              // start off all groups not visible
              e.diagram.findTopLevelGroups().each(function(g) { g.visible = false; });
            },
            "LayoutCompleted": function(e) {
              // nodes may have moved -- move their corresponding groups
              e.diagram.findTopLevelGroups().each(locateGroup);
            },
            "SelectionMoved": function(e) {
              e.diagram.findTopLevelGroups().each(locateGroup);
            },
            mouseDrop: function(e) {
              // disallow dropping special nodes onto diagram background
              if (e.diagram.selection.any(isSpecial)) e.diagram.currentTool.doCancel();
            },
            "undoManager.isEnabled": true
          });

    // position a Group just below the corresponding Node's button
    function locateGroup(g) {
      var node = findNodeForGroup(g);
      if (node !== null) {
        g.ensureBounds();
        var p = node.findObject("BUTTON").getDocumentPoint(go.Spot.Bottom);
        g.moveTo(p.x, p.y + 5, true);
      }
    }

    // find the Node whose data.associated key is the given Group's key
    function findNodeForGroup(g) {
      var it = g.diagram.nodes;
      while (it.next()) {
        var n = it.value;
        if (n.data.associated === g.key) return n;
      }
      return null;
    }

    // find the Group associated with a given Node, based on its data.associated key
    function findGroupForNode(n) {
      var groupkey = n.data.associated;
      if (groupkey) {
        return n.diagram.findNodeForKey(groupkey);
      }
      return null;
    }

    myDiagram.nodeTemplate =
      $(go.Node, "Spot",
        { selectionObjectName: "BODY" },
        $(go.Panel, "Auto",
          { name: "BODY" },
          $(go.Shape,
            { fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
            new go.Binding("fill", "color")),
          $(go.TextBlock,
            { margin: 8, editable: true },
            new go.Binding("text").makeTwoWay())
        ),
        $("Button",
          $(go.Shape, "Circle", { width: 8, height: 8 }),
          {
            name: "BUTTON",
            alignment: go.Spot.BottomRight,
            click: function(e, button) {
              var node = button.part;
              var group = findGroupForNode(node);
              if (group !== null) {
                e.diagram.commit(function(d) {  // always make changes with a transaction
                  group.visible = !group.visible;
                  if (group.visible) locateGroup(group);
                });
              }
            }
          })
      );

    myDiagram.groupTemplate =
      $(go.Group, "Auto",
        {
          isLayoutPositioned: false,  // the Diagram.layout will not position these Groups
          locationSpot: go.Spot.Top,
          computesBoundsAfterDrag: true,  // no real-time recomputing the Placeholder's bounds
          layout: $(go.GridLayout, { wrappingColumn: 3 }),
          handlesDragDropForMembers: true,  // a drop onto a member Node acts as a drop onto the Group
          mouseDragEnter: function(e, group) {
            group.elt(0).stroke = "green";  // highlight
          },
          mouseDragLeave: function(e, group) {
            group.elt(0).stroke = "gray";
          },
          mouseDrop: function(e, group) {
            group.addMembers(e.diagram.selection.filter(isSpecial), true);
          }
        },
        $(go.Shape, { fill: "whitesmoke", stroke: "gray" }),
        $(go.Placeholder, { padding: 10 })
      );

    // true if it's a Node of a particular category that belongs in a Group
    function isSpecial(n) {
      return n.category === "blue" || n.category === "purple";
    }

    myDiagram.nodeTemplateMap.add("blue",
      $(go.Part,
        // a selected Part goes into the "Foreground" Layer, so it won't be behind any Group
        new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
        $(go.Shape, "Circle", { width: 40, height: 40, fill: "blue" }))
    );
    myDiagram.nodeTemplateMap.add("purple",
      $(go.Part,
        new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
        $(go.Shape, "Triangle", { width: 40, height: 40, fill: "purple" }))
    );

    myDiagram.model = new go.GraphLinksModel(
    [
      { key: 1, text: "Alpha", category: "lightblue", associated: "G1" },
      { key: 2, text: "Beta", category: "orange", associated: "G2" },
      { key: 3, text: "Gamma", category: "lightgreen", associated: "G3" },
      { key: 4, text: "Delta", category: "pink", associated: "G4" },
      { key: "G1", isGroup: true },
      { category: "blue", group: "G1" },
      { category: "purple", group: "G1" },
      { key: "G2", isGroup: true },
      { category: "blue", group: "G2" },
      { category: "purple", group: "G2" },
      { key: "G3", isGroup: true },
      { category: "blue", group: "G3" },
      { category: "purple", group: "G3" },
      { category: "purple", group: "G3" },
      { key: "G4", isGroup: true },
      { category: "blue", group: "G4" },
      { category: "blue", group: "G4" },
      { category: "blue", group: "G4" },
      { category: "purple", group: "G4" },
      { category: "purple", group: "G4" }
    ],
    [
      { from: 1, to: 2 },
      { from: 1, to: 3 },
      { from: 3, to: 4 }
    ]);
  }
</script>
</head>
<body onload="init()">
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
</body>
</html>
1 Like