236 lines
9.6 KiB
HTML
236 lines
9.6 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<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." />
|
|
<!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
|
|
<meta charset="UTF-8">
|
|
<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);
|
|
|
|
/** @override */
|
|
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 = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft),
|
|
port.getDocumentPoint(go.Spot.BottomRight));
|
|
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,
|
|
initialContentAlignment: go.Spot.Center,
|
|
"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 }, // 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,
|
|
nodeIndent: 2,
|
|
nodeIndentPastParent: 0.88,
|
|
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>
|