gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
233 lines (215 loc) • 9.89 kB
HTML
<html>
<head>
<meta charset="UTF-8">
<title>GoJS Tree Mapper</title>
<meta name="description" content="A tree mapper diagram to show and edit the relationships between items in two different trees." />
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
<script id="code">
function TreeNode() {
go.Node.call(this);
this.treeExpandedChanged = function(node) {
if (node.containingGroup !== null) {
node.containingGroup.findExternalLinksConnected().each(function(l) { l.invalidateRoute(); });
}
};
}
go.Diagram.inherit(TreeNode, go.Node);
TreeNode.prototype.findVisibleNode = function() {
// redirect links to lowest visible "ancestor" in the tree
var n = this;
while (n !== null && !n.isVisible()) {
n = n.findTreeParentNode();
}
return n;
};
// end TreeNode
function MappingLink() {
go.Link.call(this);
}
go.Diagram.inherit(MappingLink, go.Link);
MappingLink.prototype.getLinkPoint = function(node, port, spot, from, ortho, othernode, otherport) {
var r = port.getDocumentBounds();
var group = node.containingGroup;
var b = (group !== null) ? group.actualBounds : node.actualBounds;
var op = othernode.getDocumentPoint(go.Spot.Center);
var x = (op.x > r.centerX) ? b.right : b.left;
return new go.Point(x, r.centerY);
};
// end MappingLink
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make; // for conciseness in defining templates
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"commandHandler.copiesTree": true,
"commandHandler.deletesTree": true,
// newly drawn links always map a node in one tree to a node in another tree
"linkingTool.archetypeLinkData": { category: "Mapping" },
"linkingTool.linkValidation": checkLink,
"relinkingTool.linkValidation": checkLink,
"undoManager.isEnabled": true,
"ModelChanged": function(e) {
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
}
});
// All links must go from a node inside the "Left Side" Group to a node inside the "Right Side" Group.
function checkLink(fn, fp, tn, tp, link) {
// make sure the nodes are inside different Groups
if (fn.containingGroup === null || fn.containingGroup.data.key !== -1) return false;
if (tn.containingGroup === null || tn.containingGroup.data.key !== -2) return false;
//// optional limit to a single mapping link per node
//if (fn.linksConnected.any(function(l) { return l.category === "Mapping"; })) return false;
//if (tn.linksConnected.any(function(l) { return l.category === "Mapping"; })) return false;
return true;
}
// Each node in a tree is defined using the default nodeTemplate.
myDiagram.nodeTemplate =
$(TreeNode,
{ movable: false, copyable: false, deletable: false }, // user cannot move an individual node
// no Adornment: instead change panel background color by binding to Node.isSelected
{ selectionAdorned: false },
// whether the user can start drawing a link from or to this node depends on which group it's in
new go.Binding("fromLinkable", "group", function(k) { return k === -1; }),
new go.Binding("toLinkable", "group", function(k) { return k === -2; }),
$("TreeExpanderButton", // support expanding/collapsing subtrees
{
width: 14, height: 14,
"ButtonIcon.stroke": "white",
"ButtonIcon.strokeWidth": 2,
"ButtonBorder.fill": "goldenrod",
"ButtonBorder.stroke": null,
"ButtonBorder.figure": "Rectangle",
"_buttonFillOver": "darkgoldenrod",
"_buttonStrokeOver": null,
"_buttonFillPressed": null
}),
$(go.Panel, "Horizontal",
{ position: new go.Point(16, 0) },
new go.Binding("background", "isSelected", function(s) { return (s ? "lightblue" : "white"); }).ofObject(),
//// optional icon for each tree node
//$(go.Picture,
// { width: 14, height: 14,
// margin: new go.Margin(0, 4, 0, 0),
// imageStretch: go.GraphObject.Uniform,
// source: "images/defaultIcon.png" },
// new go.Binding("source", "src")),
$(go.TextBlock,
new go.Binding("text", "key", function(s) { return "item " + s; }))
) // end Horizontal Panel
); // end Node
// These are the links connecting tree nodes within each group.
myDiagram.linkTemplate = $(go.Link); // without lines
myDiagram.linkTemplate = // with lines
$(go.Link,
{
selectable: false,
routing: go.Link.Orthogonal,
fromEndSegmentLength: 4,
toEndSegmentLength: 4,
fromSpot: new go.Spot(0.001, 1, 7, 0),
toSpot: go.Spot.Left
},
$(go.Shape,
{ stroke: "lightgray" }));
// These are the blue links connecting a tree node on the left side with one on the right side.
myDiagram.linkTemplateMap.add("Mapping",
$(MappingLink,
{ isTreeLink: false, isLayoutPositioned: false, layerName: "Foreground" },
{ fromSpot: go.Spot.Right, toSpot: go.Spot.Left },
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape, { stroke: "blue", strokeWidth: 2 })
));
myDiagram.groupTemplate =
$(go.Group, "Auto",
new go.Binding("position", "xy", go.Point.parse).makeTwoWay(go.Point.stringify),
{
deletable: false,
layout:
$(go.TreeLayout,
{
alignment: go.TreeLayout.AlignmentStart,
angle: 0,
compaction: go.TreeLayout.CompactionNone,
layerSpacing: 16,
layerSpacingParentOverlap: 1,
nodeIndentPastParent: 1.0,
nodeSpacing: 0,
setsPortSpot: false,
setsChildPortSpot: false
})
},
$(go.Shape, { fill: "white", stroke: "lightgray" }),
$(go.Panel, "Vertical",
{ defaultAlignment: go.Spot.Left },
$(go.TextBlock,
{ font: "bold 14pt sans-serif", margin: new go.Margin(5, 5, 0, 5) },
new go.Binding("text")),
$(go.Placeholder, { padding: 5 })
)
);
var nodeDataArray = [
{ isGroup: true, key: -1, text: "Left Side", xy: "0 0" },
{ isGroup: true, key: -2, text: "Right Side", xy: "300 0" }
];
var linkDataArray = [
{ from: 6, to: 1012, category: "Mapping" },
{ from: 4, to: 1006, category: "Mapping" },
{ from: 9, to: 1004, category: "Mapping" },
{ from: 1, to: 1009, category: "Mapping" },
{ from: 14, to: 1010, category: "Mapping" }
];
// initialize tree on left side
var root = { key: 0, group: -1 };
nodeDataArray.push(root);
for (var i = 0; i < 11;) {
i = makeTree(3, i, 17, nodeDataArray, linkDataArray, root, -1, root.key);
}
// initialize tree on right side
root = { key: 1000, group: -2 };
nodeDataArray.push(root);
for (var i = 0; i < 15;) {
i = makeTree(3, i, 15, nodeDataArray, linkDataArray, root, -2, root.key);
}
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
}
// help create a random tree structure
function makeTree(level, count, max, nodeDataArray, linkDataArray, parentdata, groupkey, rootkey) {
var numchildren = Math.floor(Math.random() * 10);
for (var i = 0; i < numchildren; i++) {
if (count >= max) return count;
count++;
var childdata = { key: rootkey + count, group: groupkey };
nodeDataArray.push(childdata);
linkDataArray.push({ from: parentdata.key, to: childdata.key });
if (level > 0 && Math.random() > 0.5) {
count = makeTree(level - 1, count, max, nodeDataArray, linkDataArray, childdata, groupkey, rootkey);
}
}
return count;
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagramDiv" style="border: 1px solid black; width: 700px; height: 350px"></div>
<p>
This sample is like the <a href="records.html">Mapping Fields of Records</a> sample,
but it has a collapsible tree of nodes on each side, rather than a simple list of items.
The implementation of the trees comes from the <a href="treeView.html">Tree View</a> sample.
</p>
<p>
Draw new links by dragging from any field (i.e. any tree node).
Reconnect a selected link by dragging its diamond-shaped handle.
</p>
<p>The model data, automatically updated after each change or undo or redo:</p>
<textarea id="mySavedModel" style="width:100%;height:300px"></textarea>
</div>
</body>
</html>