406 lines
20 KiB
HTML
406 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Tree Layout</title>
|
|
<meta name="description" content="Interactive demonstration of tree layout features by the TreeLayout class." />
|
|
<!-- 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 -->
|
|
<style type="text/css">
|
|
input[type="number"] {
|
|
width:45px;
|
|
}
|
|
</style>
|
|
<script id="code">
|
|
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", // must be the ID or reference to div
|
|
{
|
|
initialAutoScale: go.Diagram.UniformToFill,
|
|
layout: $(go.TreeLayout,
|
|
{ comparer: go.LayoutVertex.smartComparer }) // have the comparer sort by numbers as well as letters
|
|
// other properties are set by the layout function, defined below
|
|
});
|
|
|
|
// define the Node template
|
|
myDiagram.nodeTemplate =
|
|
$(go.Node, "Spot",
|
|
{ locationSpot: go.Spot.Center },
|
|
new go.Binding("text", "text"), // for sorting
|
|
$(go.Shape, "Ellipse",
|
|
{ fill: "lightgray", // the initial value, but data binding may provide different value
|
|
stroke: null,
|
|
desiredSize: new go.Size(30, 30) },
|
|
new go.Binding("desiredSize", "size"),
|
|
new go.Binding("fill", "fill")),
|
|
$(go.TextBlock,
|
|
new go.Binding("text", "text"))
|
|
);
|
|
|
|
// define the Link template
|
|
myDiagram.linkTemplate =
|
|
$(go.Link,
|
|
{ routing: go.Link.Orthogonal,
|
|
selectable: false },
|
|
$(go.Shape,
|
|
{ strokeWidth: 3, stroke: "#333" }));
|
|
|
|
// generate a tree with the default values
|
|
rebuildGraph();
|
|
}
|
|
|
|
function rebuildGraph() {
|
|
var minNodes = document.getElementById("minNodes").value;
|
|
minNodes = parseInt(minNodes, 10);
|
|
|
|
var maxNodes = document.getElementById("maxNodes").value;
|
|
maxNodes = parseInt(maxNodes, 10);
|
|
|
|
var minChil = document.getElementById("minChil").value;
|
|
minChil = parseInt(minChil, 10);
|
|
|
|
var maxChil = document.getElementById("maxChil").value;
|
|
maxChil = parseInt(maxChil, 10);
|
|
|
|
var hasRandomSizes = document.getElementById("randomSizes").checked;
|
|
|
|
// create and assign a new model
|
|
var nodeDataArray = generateNodeData(minNodes, maxNodes, minChil, maxChil, hasRandomSizes);
|
|
myDiagram.model = new go.TreeModel(nodeDataArray);
|
|
|
|
// update the diagram layout customized by the various control values
|
|
layout();
|
|
}
|
|
|
|
// Creates a random number (between MIN and MAX) of randomly colored nodes.
|
|
function generateNodeData(minNodes, maxNodes, minChil, maxChil, hasRandomSizes) {
|
|
var nodeArray = [];
|
|
if (minNodes === undefined || isNaN(minNodes) || minNodes < 1) minNodes = 1;
|
|
if (maxNodes === undefined || isNaN(maxNodes) || maxNodes < minNodes) maxNodes = minNodes;
|
|
|
|
// Create a bunch of node data
|
|
var numNodes = Math.floor(Math.random() * (maxNodes - minNodes + 1)) + minNodes;
|
|
for (var i = 0; i < numNodes; i++) {
|
|
nodeArray.push({
|
|
key: i, // the unique identifier
|
|
// "parent" is set by code below that assigns children
|
|
text: i.toString(), // some text to be shown by the node template
|
|
fill: go.Brush.randomColor(), // a color to be shown by the node template
|
|
size: (hasRandomSizes) ? new go.Size(Math.random()*50 + 20, Math.random()*50 + 20) : new go.Size(30, 30)
|
|
});
|
|
}
|
|
|
|
// Randomize the node data
|
|
for (i = 0; i < nodeArray.length; i++) {
|
|
var swap = Math.floor(Math.random() * nodeArray.length);
|
|
var temp = nodeArray[swap];
|
|
nodeArray[swap] = nodeArray[i];
|
|
nodeArray[i] = temp;
|
|
}
|
|
|
|
// Takes the random collection of node data and creates a random tree with them.
|
|
// Respects the minimum and maximum number of links from each node.
|
|
// The minimum can be disregarded if we run out of nodes to link to.
|
|
if (nodeArray.length > 1) {
|
|
if (minChil === undefined || isNaN(minChil) || minChil < 0) minChil = 0;
|
|
if (maxChil === undefined || isNaN(maxChil) || maxChil < minChil) maxChil = minChil;
|
|
|
|
// keep the Set of node data that do not yet have a parent
|
|
var available = new go.Set();
|
|
available.addAll(nodeArray);
|
|
for (var i = 0; i < nodeArray.length; i++) {
|
|
var parent = nodeArray[i];
|
|
available.remove(parent);
|
|
|
|
// assign some number of node data as children of this parent node data
|
|
var children = Math.floor(Math.random() * (maxChil - minChil + 1)) + minChil;
|
|
for (var j = 0; j < children; j++) {
|
|
var child = available.first();
|
|
if (child === null) break; // oops, ran out already
|
|
available.remove(child);
|
|
// have the child node data refer to the parent node data by its key
|
|
child.parent = parent.key;
|
|
}
|
|
if (available.count === 0) break; // nothing left?
|
|
}
|
|
}
|
|
return nodeArray;
|
|
}
|
|
|
|
// Update the layout from the controls, and then perform the layout again
|
|
function layout() {
|
|
myDiagram.startTransaction("change Layout");
|
|
var lay = myDiagram.layout;
|
|
|
|
var style = document.getElementById("style").value;
|
|
if (style === "Layered") lay.treeStyle = go.TreeLayout.StyleLayered;
|
|
else if (style === "Alternating") lay.treeStyle = go.TreeLayout.StyleAlternating;
|
|
else if (style === "LastParents") lay.treeStyle = go.TreeLayout.StyleLastParents;
|
|
else if (style === "RootOnly") lay.treeStyle = go.TreeLayout.StyleRootOnly;
|
|
|
|
var layerStyle = document.getElementById("layerStyle").value;
|
|
if (layerStyle === "Individual") lay.layerStyle = go.TreeLayout.LayerIndividual;
|
|
else if (layerStyle === "Siblings") lay.layerStyle = go.TreeLayout.LayerSiblings;
|
|
else if (layerStyle === "Uniform") lay.layerStyle = go.TreeLayout.LayerUniform;
|
|
|
|
var angle = getRadioValue("angle");
|
|
angle = parseFloat(angle, 10);
|
|
lay.angle = angle;
|
|
|
|
var align = document.getElementById("align").value;
|
|
if (align === "CenterChildren") lay.alignment = go.TreeLayout.AlignmentCenterChildren;
|
|
else if (align === "CenterSubtrees") lay.alignment = go.TreeLayout.AlignmentCenterSubtrees;
|
|
else if (align === "Start") lay.alignment = go.TreeLayout.AlignmentStart;
|
|
else if (align === "End") lay.alignment = go.TreeLayout.AlignmentEnd;
|
|
else if (align === "Bus") lay.alignment = go.TreeLayout.AlignmentBus;
|
|
else if (align === "BusBranching") lay.alignment = go.TreeLayout.AlignmentBusBranching;
|
|
else if (align === "TopLeftBus") lay.alignment = go.TreeLayout.AlignmentTopLeftBus;
|
|
else if (align === "BottomRightBus") lay.alignment = go.TreeLayout.AlignmentBottomRightBus;
|
|
|
|
var nodeSpacing = document.getElementById("nodeSpacing").value;
|
|
nodeSpacing = parseFloat(nodeSpacing, 10);
|
|
lay.nodeSpacing = nodeSpacing;
|
|
|
|
var nodeIndent = document.getElementById("nodeIndent").value;
|
|
nodeIndent = parseFloat(nodeIndent, 10);
|
|
lay.nodeIndent = nodeIndent;
|
|
|
|
var nodeIndentPastParent = document.getElementById("nodeIndentPastParent").value;
|
|
nodeIndentPastParent = parseFloat(nodeIndentPastParent, 10);
|
|
lay.nodeIndentPastParent = nodeIndentPastParent;
|
|
|
|
var layerSpacing = document.getElementById("layerSpacing").value;
|
|
layerSpacing = parseFloat(layerSpacing, 10);
|
|
lay.layerSpacing = layerSpacing;
|
|
|
|
var layerSpacingParentOverlap = document.getElementById("layerSpacingParentOverlap").value;
|
|
layerSpacingParentOverlap = parseFloat(layerSpacingParentOverlap, 10);
|
|
lay.layerSpacingParentOverlap = layerSpacingParentOverlap;
|
|
|
|
var sorting = document.getElementById("sorting").value;
|
|
if (sorting === "Forwards") lay.sorting = go.TreeLayout.SortingForwards;
|
|
else if (sorting === "Reverse") lay.sorting = go.TreeLayout.SortingReverse;
|
|
else if (sorting === "Ascending") lay.sorting = go.TreeLayout.SortingAscending;
|
|
else if (sorting === "Descending") lay.sorting = go.TreeLayout.SortingDescending;
|
|
|
|
var compaction = getRadioValue("compaction");
|
|
if (compaction === "Block") lay.compaction = go.TreeLayout.CompactionBlock;
|
|
else if (compaction === "None") lay.compaction = go.TreeLayout.CompactionNone;
|
|
|
|
var breadthLimit = document.getElementById("breadthLimit").value;
|
|
breadthLimit = parseFloat(breadthLimit, 10);
|
|
lay.breadthLimit = breadthLimit;
|
|
|
|
var rowSpacing = document.getElementById("rowSpacing").value;
|
|
rowSpacing = parseFloat(rowSpacing, 10);
|
|
lay.rowSpacing = rowSpacing;
|
|
|
|
var rowIndent = document.getElementById("rowIndent").value;
|
|
rowIndent = parseFloat(rowIndent, 10);
|
|
lay.rowIndent = rowIndent;
|
|
|
|
var setsPortSpot = document.getElementById("setsPortSpot").checked;
|
|
lay.setsPortSpot = setsPortSpot;
|
|
|
|
var setsChildPortSpot = document.getElementById("setsChildPortSpot").checked;
|
|
lay.setsChildPortSpot = setsChildPortSpot;
|
|
|
|
if (style !== "Layered") {
|
|
var altAngle = getRadioValue("altAngle");
|
|
altAngle = parseFloat(altAngle, 10);
|
|
lay.alternateAngle = altAngle;
|
|
|
|
var altAlign = document.getElementById("altAlign").value;
|
|
if (altAlign === "CenterChildren") lay.alternateAlignment = go.TreeLayout.AlignmentCenterChildren;
|
|
else if (altAlign === "CenterSubtrees") lay.alternateAlignment = go.TreeLayout.AlignmentCenterSubtrees;
|
|
else if (altAlign === "Start") lay.alternateAlignment = go.TreeLayout.AlignmentStart;
|
|
else if (altAlign === "End") lay.alternateAlignment = go.TreeLayout.AlignmentEnd;
|
|
else if (altAlign === "Bus") lay.alternateAlignment = go.TreeLayout.AlignmentBus;
|
|
else if (altAlign === "BusBranching") lay.alternateAlignment = go.TreeLayout.AlignmentBusBranching;
|
|
else if (altAlign === "TopLeftBus") lay.alternateAlignment = go.TreeLayout.AlignmentTopLeftBus;
|
|
else if (altAlign === "BottomRightBus") lay.alternateAlignment = go.TreeLayout.AlignmentBottomRightBus;
|
|
|
|
var altNodeSpacing = document.getElementById("altNodeSpacing").value;
|
|
altNodeSpacing = parseFloat(altNodeSpacing, 10);
|
|
lay.alternateNodeSpacing = altNodeSpacing;
|
|
|
|
var altNodeIndent = document.getElementById("altNodeIndent").value;
|
|
altNodeIndent = parseFloat(altNodeIndent, 10);
|
|
lay.alternateNodeIndent = altNodeIndent;
|
|
|
|
var altNodeIndentPastParent = document.getElementById("altNodeIndentPastParent").value;
|
|
altNodeIndentPastParent = parseFloat(altNodeIndentPastParent, 10);
|
|
lay.alternateNodeIndentPastParent = altNodeIndentPastParent;
|
|
|
|
var altLayerSpacing = document.getElementById("altLayerSpacing").value;
|
|
altLayerSpacing = parseFloat(altLayerSpacing, 10);
|
|
lay.alternateLayerSpacing = altLayerSpacing;
|
|
|
|
var altLayerSpacingParentOverlap = document.getElementById("altLayerSpacingParentOverlap").value;
|
|
altLayerSpacingParentOverlap = parseFloat(altLayerSpacingParentOverlap, 10);
|
|
lay.alternateLayerSpacingParentOverlap = altLayerSpacingParentOverlap;
|
|
|
|
var altSorting = document.getElementById("altSorting").value;
|
|
if (altSorting === "Forwards") lay.alternateSorting = go.TreeLayout.SortingForwards;
|
|
else if (altSorting === "Reverse") lay.alternateSorting = go.TreeLayout.SortingReverse;
|
|
else if (altSorting === "Ascending") lay.alternateSorting = go.TreeLayout.SortingAscending;
|
|
else if (altSorting === "Descending") lay.alternateSorting = go.TreeLayout.SortingDescending;
|
|
|
|
var altCompaction = getRadioValue("altCompaction");
|
|
if (altCompaction === "Block") lay.alternateCompaction = go.TreeLayout.CompactionBlock;
|
|
else if (altCompaction === "None") lay.alternateCompaction = go.TreeLayout.CompactionNone;
|
|
|
|
var altBreadthLimit = document.getElementById("altBreadthLimit").value;
|
|
altBreadthLimit = parseFloat(altBreadthLimit, 10);
|
|
lay.alternateBreadthLimit = altBreadthLimit;
|
|
|
|
var altRowSpacing = document.getElementById("altRowSpacing").value;
|
|
altRowSpacing = parseFloat(altRowSpacing, 10);
|
|
lay.alternateRowSpacing = altRowSpacing;
|
|
|
|
var altRowIndent = document.getElementById("altRowIndent").value;
|
|
altRowIndent = parseFloat(altRowIndent, 10);
|
|
lay.alternateRowIndent = altRowIndent;
|
|
|
|
var altSetsPortSpot = document.getElementById("altSetsPortSpot").checked;
|
|
lay.alternateSetsPortSpot = altSetsPortSpot;
|
|
|
|
var altSetsChildPortSpot = document.getElementById("altSetsChildPortSpot").checked;
|
|
lay.alternateSetsChildPortSpot = altSetsChildPortSpot;
|
|
}
|
|
|
|
myDiagram.commitTransaction("change Layout");
|
|
}
|
|
|
|
function getRadioValue(name) {
|
|
var radio = document.getElementsByName(name);
|
|
for (var i = 0; i < radio.length; i++)
|
|
if (radio[i].checked) return radio[i].value;
|
|
}
|
|
</script>
|
|
</head>
|
|
<body onload="init()">
|
|
<div id="sample">
|
|
<div style="margin-bottom: 5px; padding: 5px; background-color: aliceblue">
|
|
<span style="display: inline-block; vertical-align: top; padding: 5px">
|
|
<b>Tree Style</b><br />
|
|
<select name="style" id="style" onchange="layout()">
|
|
<option value="Layered" selected="selected">Layered</option>
|
|
<option value="Alternating">Alternating</option>
|
|
<option value="LastParents">LastParents</option>
|
|
<option value="RootOnly">RootOnly</option>
|
|
</select>
|
|
<br />
|
|
<b>Layer Style</b><br />
|
|
<select name="layerStyle" id="layerStyle" onchange="layout()">
|
|
<option value="Individual" selected="selected">Individual</option>
|
|
<option value="Siblings">Siblings</option>
|
|
<option value="Uniform">Uniform</option>
|
|
</select>
|
|
<br />
|
|
<br />
|
|
<b>New Tree</b><br />
|
|
MinNodes: <input type="number" width="2" id="minNodes" value="20" /><br />
|
|
MaxNodes: <input type="number" width="2" id="maxNodes" value="100" /><br />
|
|
MinChildren: <input type="number" width="2" id="minChil" value="1" /><br />
|
|
MaxChildren: <input type="number" width="2" id="maxChil" value="3" /><br />
|
|
Random Sizes: <input type="checkbox" id="randomSizes" /><br />
|
|
<button type="button" onclick="rebuildGraph()">Generate Tree</button>
|
|
</span>
|
|
<span style="display: inline-block; vertical-align: top; padding: 5px">
|
|
<b>Default Properties</b><br />
|
|
Angle:
|
|
<input type="radio" name="angle" onclick="layout()" value="0" checked="checked" />Right
|
|
<input type="radio" name="angle" onclick="layout()" value="90" />Down
|
|
<input type="radio" name="angle" onclick="layout()" value="180" />Left
|
|
<input type="radio" name="angle" onclick="layout()" value="270" />Up<br />
|
|
Alignment:
|
|
<select name="align" id="align" onchange="layout()">
|
|
<option value="CenterChildren" selected="selected">CenterChildren</option>
|
|
<option value="CenterSubtrees">CenterSubtrees</option>
|
|
<option value="Start">Start</option>
|
|
<option value="End">End</option>
|
|
<option value="Bus">Bus</option>
|
|
<option value="BusBranching">BusBranching</option>
|
|
<option value="TopLeftBus">TopLeftBus</option>
|
|
<option value="BottomRightBus">BottomRightBus</option>
|
|
</select>
|
|
<br />
|
|
NodeSpacing: <input type="number" size="2" id="nodeSpacing" value="20" onchange="layout()" /> (negative causes overlaps)<br />
|
|
NodeIndent: <input type="number" size="2" id="nodeIndent" value="0" onchange="layout()" /> (when Start or End; >= 0)<br />
|
|
NodeIndentPastParent: <input type="number" size="2" id="nodeIndentPastParent" value="0" onchange="layout()" /> (fraction; 0-1)<br />
|
|
LayerSpacing: <input type="number" size="2" id="layerSpacing" value="50" onchange="layout()" /> (negative causes overlaps)<br />
|
|
LayerSpacingParentOverlap: <input type="number" size="2" id="layerSpacingParentOverlap" value="0" onchange="layout()" /> (fraction; 0-1)<br />
|
|
Sorting:
|
|
<select name="sorting" id="sorting" onchange="layout()">
|
|
<option value="Forwards" selected="selected">Forwards</option>
|
|
<option value="Reverse">Reverse</option>
|
|
<option value="Ascending">Ascending</option>
|
|
<option value="Descending">Descending</option>
|
|
</select>
|
|
<br />
|
|
Compaction:
|
|
<input type="radio" name="compaction" onclick="layout()" value="Block" checked="checked" /> Block
|
|
<input type="radio" name="compaction" onclick="layout()" value="None" /> None<br />
|
|
BreadthLimit: <input type="number" size="2" id="breadthLimit" value="0" onchange="layout()" /> (0 means no limit)<br />
|
|
RowSpacing: <input type="number" size="2" id="rowSpacing" value="25" onchange="layout()" /> (negative causes overlaps)<br />
|
|
RowIndent: <input type="number" size="2" id="rowIndent" value="10" onchange="layout()" /> (>= 0)<br />
|
|
SetsPortSpot: <input type="checkbox" id="setsPortSpot" checked="checked" onclick="layout()" />
|
|
SetsChildPortSpot: <input type="checkbox" id="setsChildPortSpot" checked="checked" onclick="layout()" /><br />
|
|
</span>
|
|
<span style="display: inline-block; vertical-align: top; padding: 5px">
|
|
<b>Alternates</b>
|
|
(only when TreeStyle is not Layered)
|
|
<br />
|
|
Angle:
|
|
<input type="radio" name="altAngle" onclick="layout()" value="0" checked="checked" />Right
|
|
<input type="radio" name="altAngle" onclick="layout()" value="90" />Down
|
|
<input type="radio" name="altAngle" onclick="layout()" value="180" />Left
|
|
<input type="radio" name="altAngle" onclick="layout()" value="270" />Up<br />
|
|
Alignment:
|
|
<select name="altAlign" id="altAlign" onchange="layout()">
|
|
<option value="CenterChildren" selected="selected">CenterChildren</option>
|
|
<option value="CenterSubtrees">CenterSubtrees</option>
|
|
<option value="Start">Start</option>
|
|
<option value="End">End</option>
|
|
<option value="Bus">Bus</option>
|
|
<option value="BusBranching">BusBranching</option>
|
|
<option value="TopLeftBus">TopLeftBus</option>
|
|
<option value="BottomRightBus">BottomRightBus</option>
|
|
</select>
|
|
<br />
|
|
NodeSpacing: <input type="number" size="2" id="altNodeSpacing" value="20" onchange="layout()" /> (negative causes overlaps)<br />
|
|
NodeIndent: <input type="number" size="2" id="altNodeIndent" value="0" onchange="layout()" /> (when Start or End; >= 0)<br />
|
|
NodeIndentPastParent: <input type="number" size="2" id="altNodeIndentPastParent" value="0" onchange="layout()" /> (fraction; 0-1)<br />
|
|
LayerSpacing: <input type="number" size="2" id="altLayerSpacing" value="50" onchange="layout()" /> (negative causes overlaps)<br />
|
|
LayerSpacingParentOverlap: <input type="number" size="2" id="altLayerSpacingParentOverlap" value="0" onchange="layout()" /> (fraction; 0-1)<br />
|
|
Sorting:
|
|
<select name="altSorting" id="altSorting" onchange="layout()">
|
|
<option value="Forwards" selected="selected">Forwards</option>
|
|
<option value="Reverse">Reverse</option>
|
|
<option value="Ascending">Ascending</option>
|
|
<option value="Descending">Descending</option>
|
|
</select>
|
|
<br />
|
|
Compaction:
|
|
<input type="radio" name="altCompaction" onclick="layout()" value="Block" checked="checked" /> Block
|
|
<input type="radio" name="altCompaction" onclick="layout()" value="None" /> None<br />
|
|
BreadthLimit: <input type="number" size="2" id="altBreadthLimit" value="0" onchange="layout()" /> (0 means no limit)<br />
|
|
RowSpacing: <input type="number" size="2" id="altRowSpacing" value="25" onchange="layout()" /> (negative causes overlaps)<br />
|
|
RowIndent: <input type="number" size="2" id="altRowIndent" value="10" onchange="layout()" /> (>= 0)<br />
|
|
SetsPortSpot: <input type="checkbox" id="altSetsPortSpot" checked="checked" onclick="layout()" />
|
|
SetsChildPortSpot: <input type="checkbox" id="altSetsChildPortSpot" checked="checked" onclick="layout()" /><br />
|
|
</span>
|
|
</div>
|
|
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
|
<p>
|
|
For information on <b>TreeLayout</b> and its properties, see the <a>TreeLayout</a> documentation page.
|
|
</p>
|
|
</div>
|
|
</body>
|
|
</html> |