<!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>