feat: 后端全量更新 - 含所有本次需求
- Contract.php: 返回合约账户余额(balance_contract) - My.php: 地址管理增加BTC/ETH - AppContract.php: 一键平仓(closeall) - AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2) - site.php: 手续费减半(0.018→0.009) - agent_permission_setup.sql: 代理权限SQL - crypto_news_crawler.py: 新闻自动采集脚本
@@ -0,0 +1,155 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML DOM Tree</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive diagram showing the structure of the HTML DOM of this HTML page, allowing collapsing/expanding of subtrees." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
var names = {}; // hash to keep track of what names have been used
|
||||
|
||||
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",
|
||||
{
|
||||
initialAutoScale: go.Diagram.UniformToFill,
|
||||
// define the layout for the diagram
|
||||
layout: $(go.TreeLayout, { nodeSpacing: 5, layerSpacing: 30 })
|
||||
});
|
||||
|
||||
// Define a simple node template consisting of text followed by an expand/collapse button
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Horizontal",
|
||||
{ selectionChanged: nodeSelectionChanged }, // this event handler is defined below
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: "#1F4963", stroke: null }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "bold 13px Helvetica, bold Arial, sans-serif",
|
||||
stroke: "white", margin: 3
|
||||
},
|
||||
new go.Binding("text", "key"))
|
||||
),
|
||||
$("TreeExpanderButton")
|
||||
);
|
||||
|
||||
// Define a trivial link template with no arrowhead.
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ selectable: false },
|
||||
$(go.Shape)); // the link shape
|
||||
|
||||
// create the model for the DOM tree
|
||||
myDiagram.model =
|
||||
$(go.TreeModel, {
|
||||
isReadOnly: true, // don't allow the user to delete or copy nodes
|
||||
// build up the tree in an Array of node data
|
||||
nodeDataArray: traverseDom(document.activeElement)
|
||||
});
|
||||
}
|
||||
|
||||
// Walk the DOM, starting at document, and return an Array of node data objects representing the DOM tree
|
||||
// Typical usage: traverseDom(document.activeElement)
|
||||
// The second and third arguments are internal, used when recursing through the DOM
|
||||
function traverseDom(node, parentName, dataArray) {
|
||||
if (parentName === undefined) parentName = null;
|
||||
if (dataArray === undefined) dataArray = [];
|
||||
// skip everything but HTML Elements
|
||||
if (!(node instanceof Element)) return;
|
||||
// Ignore the navigation menus
|
||||
if (node.id === "navindex" || node.id === "navtop") return;
|
||||
// add this node to the nodeDataArray
|
||||
var name = getName(node);
|
||||
var data = { key: name, name: name };
|
||||
dataArray.push(data);
|
||||
// add a link to its parent
|
||||
if (parentName !== null) {
|
||||
data.parent = parentName;
|
||||
}
|
||||
// find all children
|
||||
var l = node.childNodes.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
traverseDom(node.childNodes[i], name, dataArray);
|
||||
}
|
||||
return dataArray;
|
||||
}
|
||||
|
||||
// Give every node a unique name
|
||||
function getName(node) {
|
||||
var n = node.nodeName;
|
||||
if (node.id) n = n + " (" + node.id + ")";
|
||||
var namenum = n; // make sure the name is unique
|
||||
var i = 1;
|
||||
while (names[namenum] !== undefined) {
|
||||
namenum = n + i;
|
||||
i++;
|
||||
}
|
||||
names[namenum] = node;
|
||||
return namenum;
|
||||
}
|
||||
|
||||
// When a Node is selected, highlight the corresponding HTML element.
|
||||
function nodeSelectionChanged(node) {
|
||||
if (node.isSelected) {
|
||||
names[node.data.name].style.backgroundColor = "lightblue";
|
||||
} else {
|
||||
names[node.data.name].style.backgroundColor = "";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<!-- The DIV needs an explicit size or else we won't see anything. -->
|
||||
<div id="myDiagramDiv" style="border: 1px solid black; width:100%; height:300px"></div>
|
||||
<p>
|
||||
This sample shows the DOM (Document Object Model) of this webpage displayed as a tree.
|
||||
Each Node in the Diagram shows information about the corresponding HTML element in the DOM.
|
||||
</p>
|
||||
<p>
|
||||
When a node is selected, the background color of the corresponding HTML Element changes to lightblue.
|
||||
Below the diagram are some more HTML elements to illustrate the effect.
|
||||
This sample also makes use of the <a href="../intro/buttons.html" target="_blank">TreeExpanderButton</a>,
|
||||
which allows for parent nodes to expand and collapse their child nodes. Buttons are defined in <a href="../extensions/Buttons.js">Buttons.js</a>.
|
||||
</p>
|
||||
<p id="lastParagraph">
|
||||
Elements with an id attribute are noted in parenthesis.
|
||||
</p>
|
||||
<div id="otherInfo">
|
||||
<div id="tableContainer" style="display: inline-block;">
|
||||
<table style="border: 1px; border-collapse: collapse;">
|
||||
<tr>
|
||||
<th id="firstHeader">Table header</th>
|
||||
<th id="secondHeader">Table header 2</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>row 1, cell 1</td>
|
||||
<td>row 1, cell 2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>row 2, cell 1</td>
|
||||
<td>row 2, cell 2</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="listContainer" style="display: inline-block; border: 1px solid gray; margin-left: 10px; width: 100px">
|
||||
<p>My grocery list</p>
|
||||
<ul id="groceryList">
|
||||
<li>Coffee</li>
|
||||
<li>Milk</li>
|
||||
<li>Bread</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
For more uses of the <a>TreeLayout</a> see the <a href="DOMTree.html">DOM Tree</a> and <a href="visualTree.html">Visual Tree</a> samples.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,250 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>IVR Tree</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="An IVR (Interactive Voice Response) diagram showing a call-menu tree with various prompts and responses." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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",
|
||||
{
|
||||
allowCopy: false,
|
||||
"draggingTool.dragsTree": true,
|
||||
"commandHandler.deletesTree": true,
|
||||
layout:
|
||||
$(go.TreeLayout,
|
||||
{ angle: 90, arrangement: go.TreeLayout.ArrangementFixedRoots }),
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
var bluegrad = $(go.Brush, "Linear", { 0: "#C4ECFF", 1: "#70D4FF" });
|
||||
var greengrad = $(go.Brush, "Linear", { 0: "#B1E2A5", 1: "#7AE060" });
|
||||
|
||||
// each action is represented by a shape and some text
|
||||
var actionTemplate =
|
||||
$(go.Panel, "Horizontal",
|
||||
$(go.Shape,
|
||||
{ width: 12, height: 12 },
|
||||
new go.Binding("figure"),
|
||||
new go.Binding("fill")
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ font: "10pt Verdana, sans-serif" },
|
||||
new go.Binding("text")
|
||||
)
|
||||
);
|
||||
|
||||
// each regular Node has body consisting of a title followed by a collapsible list of actions,
|
||||
// controlled by a PanelExpanderButton, with a TreeExpanderButton underneath the body
|
||||
myDiagram.nodeTemplate = // the default node template
|
||||
$(go.Node, "Vertical",
|
||||
new go.Binding("isTreeExpanded").makeTwoWay(), // remember the expansion state for
|
||||
new go.Binding("wasTreeExpanded").makeTwoWay(), // when the model is re-loaded
|
||||
{ selectionObjectName: "BODY" },
|
||||
// the main "BODY" consists of a RoundedRectangle surrounding nested Panels
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "BODY" },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: bluegrad, stroke: null }
|
||||
),
|
||||
$(go.Panel, "Vertical",
|
||||
{ margin: 3 },
|
||||
// the title
|
||||
$(go.TextBlock,
|
||||
{
|
||||
stretch: go.GraphObject.Horizontal,
|
||||
font: "bold 12pt Verdana, sans-serif"
|
||||
},
|
||||
new go.Binding("text", "question")
|
||||
),
|
||||
// the optional list of actions
|
||||
$(go.Panel, "Vertical",
|
||||
{ stretch: go.GraphObject.Horizontal, visible: false }, // not visible unless there is more than one action
|
||||
new go.Binding("visible", "actions", function(acts) {
|
||||
return (Array.isArray(acts) && acts.length > 0);
|
||||
}),
|
||||
// headered by a label and a PanelExpanderButton inside a Table
|
||||
$(go.Panel, "Table",
|
||||
{ stretch: go.GraphObject.Horizontal },
|
||||
$(go.TextBlock, "Choices",
|
||||
{
|
||||
alignment: go.Spot.Left,
|
||||
font: "10pt Verdana, sans-serif"
|
||||
}
|
||||
),
|
||||
$("PanelExpanderButton", "COLLAPSIBLE", // name of the object to make visible or invisible
|
||||
{ column: 1, alignment: go.Spot.Right }
|
||||
)
|
||||
), // end Table panel
|
||||
// with the list data bound in the Vertical Panel
|
||||
$(go.Panel, "Vertical",
|
||||
{
|
||||
name: "COLLAPSIBLE", // identify to the PanelExpanderButton
|
||||
padding: 2,
|
||||
stretch: go.GraphObject.Horizontal, // take up whole available width
|
||||
background: "white", // to distinguish from the node's body
|
||||
defaultAlignment: go.Spot.Left, // thus no need to specify alignment on each element
|
||||
itemTemplate: actionTemplate // the Panel created for each item in Panel.itemArray
|
||||
},
|
||||
new go.Binding("itemArray", "actions") // bind Panel.itemArray to nodedata.actions
|
||||
) // end action list Vertical Panel
|
||||
) // end optional Vertical Panel
|
||||
) // end outer Vertical Panel
|
||||
), // end "BODY" Auto Panel
|
||||
$(go.Panel, // this is underneath the "BODY"
|
||||
{ height: 17 }, // always this height, even if the TreeExpanderButton is not visible
|
||||
$("TreeExpanderButton")
|
||||
)
|
||||
);
|
||||
|
||||
// define a second kind of Node:
|
||||
myDiagram.nodeTemplateMap.add("Terminal",
|
||||
$(go.Node, "Spot",
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 55, height: 55, fill: greengrad, stroke: null }
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ font: "10pt Verdana, sans-serif" },
|
||||
new go.Binding("text")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, go.Link.Orthogonal,
|
||||
{ deletable: false, corner: 10 },
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 2 }
|
||||
),
|
||||
$(go.TextBlock, go.Link.OrientUpright,
|
||||
{
|
||||
background: "white",
|
||||
visible: false, // unless the binding sets it to true for a non-empty string
|
||||
segmentIndex: -2,
|
||||
segmentOrientation: go.Link.None
|
||||
},
|
||||
new go.Binding("text", "answer"),
|
||||
// hide empty string;
|
||||
// if the "answer" property is undefined, visible is false due to above default setting
|
||||
new go.Binding("visible", "answer", function(a) { return (a ? true : false); })
|
||||
)
|
||||
);
|
||||
|
||||
var nodeDataArray = [
|
||||
{
|
||||
key: 1, question: "Greeting",
|
||||
actions: [
|
||||
{ text: "Sales", figure: "ElectricalHazard", fill: "blue" },
|
||||
{ text: "Parts and Services", figure: "FireHazard", fill: "red" },
|
||||
{ text: "Representative", figure: "IrritationHazard", fill: "yellow" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 2, question: "Sales",
|
||||
actions: [
|
||||
{ text: "Compact", figure: "ElectricalHazard", fill: "blue" },
|
||||
{ text: "Mid-Size", figure: "FireHazard", fill: "red" },
|
||||
{ text: "Large", figure: "IrritationHazard", fill: "yellow" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 3, question: "Parts and Services",
|
||||
actions: [
|
||||
{ text: "Maintenance", figure: "ElectricalHazard", fill: "blue" },
|
||||
{ text: "Repairs", figure: "FireHazard", fill: "red" },
|
||||
{ text: "State Inspection", figure: "IrritationHazard", fill: "yellow" }
|
||||
]
|
||||
},
|
||||
{ key: 4, question: "Representative" },
|
||||
{ key: 5, question: "Compact" },
|
||||
{ key: 6, question: "Mid-Size" },
|
||||
{
|
||||
key: 7, question: "Large",
|
||||
actions: [
|
||||
{ text: "SUV", figure: "ElectricalHazard", fill: "blue" },
|
||||
{ text: "Van", figure: "FireHazard", fill: "red" }
|
||||
]
|
||||
},
|
||||
{ key: 8, question: "Maintenance" },
|
||||
{ key: 9, question: "Repairs" },
|
||||
{ key: 10, question: "State Inspection" },
|
||||
{ key: 11, question: "SUV" },
|
||||
{ key: 12, question: "Van" },
|
||||
{ key: 13, category: "Terminal", text: "Susan" },
|
||||
{ key: 14, category: "Terminal", text: "Eric" },
|
||||
{ key: 15, category: "Terminal", text: "Steven" },
|
||||
{ key: 16, category: "Terminal", text: "Tom" },
|
||||
{ key: 17, category: "Terminal", text: "Emily" },
|
||||
{ key: 18, category: "Terminal", text: "Tony" },
|
||||
{ key: 19, category: "Terminal", text: "Ken" },
|
||||
{ key: 20, category: "Terminal", text: "Rachel" }
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: 1, to: 2, answer: 1 },
|
||||
{ from: 1, to: 3, answer: 2 },
|
||||
{ from: 1, to: 4, answer: 3 },
|
||||
{ from: 2, to: 5, answer: 1 },
|
||||
{ from: 2, to: 6, answer: 2 },
|
||||
{ from: 2, to: 7, answer: 3 },
|
||||
{ from: 3, to: 8, answer: 1 },
|
||||
{ from: 3, to: 9, answer: 2 },
|
||||
{ from: 3, to: 10, answer: 3 },
|
||||
{ from: 7, to: 11, answer: 1 },
|
||||
{ from: 7, to: 12, answer: 2 },
|
||||
{ from: 5, to: 13 },
|
||||
{ from: 6, to: 14 },
|
||||
{ from: 11, to: 15 },
|
||||
{ from: 12, to: 16 },
|
||||
{ from: 8, to: 17 },
|
||||
{ from: 9, to: 18 },
|
||||
{ from: 10, to: 19 },
|
||||
{ from: 4, to: 20 }
|
||||
];
|
||||
|
||||
// create the Model with the above data, and assign to the Diagram
|
||||
myDiagram.model = $(go.GraphLinksModel,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray: nodeDataArray,
|
||||
linkDataArray: linkDataArray
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:500px"></div>
|
||||
<p>
|
||||
An <em>IVR tree</em>, or Interactive Voice Response Tree, is typically used by
|
||||
automated answering systems to direct calls to the correct party. This particular example
|
||||
is for a car dealership to route calls.
|
||||
</p>
|
||||
<p>
|
||||
This Interactive Voice Response Tree (IVR tree) has nodes that contain a collapsible list of actions, controlled by a <b>PanelExpanderButton</b>,
|
||||
with a <b>TreeExpanderButton</b> underneath the body. See the <a href="../intro/buttons.html">Intro page on Buttons</a> for more GoJS button information.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>PERT chart</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A PERT chart: a diagram for visualizing and analyzing task dependencies and bottlenecks." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make; // for more concise visual tree definitions
|
||||
|
||||
// colors used, named for easier identification
|
||||
var blue = "#0288D1";
|
||||
var pink = "#B71C1C";
|
||||
var pinkfill = "#F8BBD0";
|
||||
var bluefill = "#B3E5FC";
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
initialAutoScale: go.Diagram.Uniform,
|
||||
layout: $(go.LayeredDigraphLayout)
|
||||
});
|
||||
|
||||
// The node template shows the activity name in the middle as well as
|
||||
// various statistics about the activity, all surrounded by a border.
|
||||
// The border's color is determined by the node data's ".critical" property.
|
||||
// Some information is not available as properties on the node data,
|
||||
// but must be computed -- we use converter functions for that.
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape, "Rectangle", // the border
|
||||
{ fill: "white", strokeWidth: 2 },
|
||||
new go.Binding("fill", "critical", function(b) { return (b ? pinkfill : bluefill); }),
|
||||
new go.Binding("stroke", "critical", function(b) { return (b ? pink : blue); })),
|
||||
$(go.Panel, "Table",
|
||||
{ padding: 0.5 },
|
||||
$(go.RowColumnDefinition, { column: 1, separatorStroke: "black" }),
|
||||
$(go.RowColumnDefinition, { column: 2, separatorStroke: "black" }),
|
||||
$(go.RowColumnDefinition, { row: 1, separatorStroke: "black", background: "white", coversSeparators: true }),
|
||||
$(go.RowColumnDefinition, { row: 2, separatorStroke: "black" }),
|
||||
$(go.TextBlock, // earlyStart
|
||||
new go.Binding("text", "earlyStart"),
|
||||
{ row: 0, column: 0, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "length"),
|
||||
{ row: 0, column: 1, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock, // earlyFinish
|
||||
new go.Binding("text", "",
|
||||
function(d) { return (d.earlyStart + d.length).toFixed(2); }),
|
||||
{ row: 0, column: 2, margin: 5, textAlign: "center" }),
|
||||
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "text"),
|
||||
{
|
||||
row: 1, column: 0, columnSpan: 3, margin: 5,
|
||||
textAlign: "center", font: "bold 14px sans-serif"
|
||||
}),
|
||||
|
||||
$(go.TextBlock, // lateStart
|
||||
new go.Binding("text", "",
|
||||
function(d) { return (d.lateFinish - d.length).toFixed(2); }),
|
||||
{ row: 2, column: 0, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock, // slack
|
||||
new go.Binding("text", "",
|
||||
function(d) { return (d.lateFinish - (d.earlyStart + d.length)).toFixed(2); }),
|
||||
{ row: 2, column: 1, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock, // lateFinish
|
||||
new go.Binding("text", "lateFinish"),
|
||||
{ row: 2, column: 2, margin: 5, textAlign: "center" })
|
||||
) // end Table Panel
|
||||
); // end Node
|
||||
|
||||
// The link data object does not have direct access to both nodes
|
||||
// (although it does have references to their keys: .from and .to).
|
||||
// This conversion function gets the GraphObject that was data-bound as the second argument.
|
||||
// From that we can get the containing Link, and then the Link.fromNode or .toNode,
|
||||
// and then its node data, which has the ".critical" property we need.
|
||||
//
|
||||
// But note that if we were to dynamically change the ".critical" property on a node data,
|
||||
// calling myDiagram.model.updateTargetBindings(nodedata) would only update the color
|
||||
// of the nodes. It would be insufficient to change the appearance of any Links.
|
||||
function linkColorConverter(linkdata, elt) {
|
||||
var link = elt.part;
|
||||
if (!link) return blue;
|
||||
var f = link.fromNode;
|
||||
if (!f || !f.data || !f.data.critical) return blue;
|
||||
var t = link.toNode;
|
||||
if (!t || !t.data || !t.data.critical) return blue;
|
||||
return pink; // when both Link.fromNode.data.critical and Link.toNode.data.critical
|
||||
}
|
||||
|
||||
// The color of a link (including its arrowhead) is red only when both
|
||||
// connected nodes have data that is ".critical"; otherwise it is blue.
|
||||
// This is computed by the binding converter function.
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ toShortLength: 6, toEndSegmentLength: 20 },
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 4 },
|
||||
new go.Binding("stroke", "", linkColorConverter)),
|
||||
$(go.Shape, // arrowhead
|
||||
{ toArrow: "Triangle", stroke: null, scale: 1.5 },
|
||||
new go.Binding("fill", "", linkColorConverter))
|
||||
);
|
||||
|
||||
// here's the data defining the graph
|
||||
var nodeDataArray = [
|
||||
{ key: 1, text: "Start", length: 0, earlyStart: 0, lateFinish: 0, critical: true },
|
||||
{ key: 2, text: "a", length: 4, earlyStart: 0, lateFinish: 4, critical: true },
|
||||
{ key: 3, text: "b", length: 5.33, earlyStart: 0, lateFinish: 9.17, critical: false },
|
||||
{ key: 4, text: "c", length: 5.17, earlyStart: 4, lateFinish: 9.17, critical: true },
|
||||
{ key: 5, text: "d", length: 6.33, earlyStart: 4, lateFinish: 15.01, critical: false },
|
||||
{ key: 6, text: "e", length: 5.17, earlyStart: 9.17, lateFinish: 14.34, critical: true },
|
||||
{ key: 7, text: "f", length: 4.5, earlyStart: 10.33, lateFinish: 19.51, critical: false },
|
||||
{ key: 8, text: "g", length: 5.17, earlyStart: 14.34, lateFinish: 19.51, critical: true },
|
||||
{ key: 9, text: "Finish", length: 0, earlyStart: 19.51, lateFinish: 19.51, critical: true }
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: 1, to: 2 },
|
||||
{ from: 1, to: 3 },
|
||||
{ from: 2, to: 4 },
|
||||
{ from: 2, to: 5 },
|
||||
{ from: 3, to: 6 },
|
||||
{ from: 4, to: 6 },
|
||||
{ from: 5, to: 7 },
|
||||
{ from: 6, to: 8 },
|
||||
{ from: 7, to: 9 },
|
||||
{ from: 8, to: 9 }
|
||||
];
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
|
||||
// create an unbound Part that acts as a "legend" for the diagram
|
||||
myDiagram.add(
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape, "Rectangle", // the border
|
||||
{ fill: bluefill }),
|
||||
$(go.Panel, "Table",
|
||||
$(go.RowColumnDefinition, { column: 1, separatorStroke: "black" }),
|
||||
$(go.RowColumnDefinition, { column: 2, separatorStroke: "black" }),
|
||||
$(go.RowColumnDefinition, { row: 1, separatorStroke: "black", background: bluefill, coversSeparators: true }),
|
||||
$(go.RowColumnDefinition, { row: 2, separatorStroke: "black" }),
|
||||
$(go.TextBlock, "Early Start",
|
||||
{ row: 0, column: 0, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock, "Length",
|
||||
{ row: 0, column: 1, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock, "Early Finish",
|
||||
{ row: 0, column: 2, margin: 5, textAlign: "center" }),
|
||||
|
||||
$(go.TextBlock, "Activity Name",
|
||||
{
|
||||
row: 1, column: 0, columnSpan: 3, margin: 5,
|
||||
textAlign: "center", font: "bold 14px sans-serif"
|
||||
}),
|
||||
|
||||
$(go.TextBlock, "Late Start",
|
||||
{ row: 2, column: 0, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock, "Slack",
|
||||
{ row: 2, column: 1, margin: 5, textAlign: "center" }),
|
||||
$(go.TextBlock, "Late Finish",
|
||||
{ row: 2, column: 2, margin: 5, textAlign: "center" })
|
||||
) // end Table Panel
|
||||
));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
|
||||
<p>
|
||||
This sample demonstrates how to create a simple PERT chart. A PERT chart is a project management tool used to schedule and coordinate tasks within a project.
|
||||
</p>
|
||||
<p>
|
||||
Each node represents an activity and displays several pieces of information about each one.
|
||||
The node template is basically a <a>Panel</a> of type <a>Panel,Table</a> holding several <a>TextBlock</a>s
|
||||
that are data-bound to properties of the Activity, all surrounded by a rectangular border.
|
||||
The lines separating the text are implemented by setting the <a>RowColumnDefinition.separatorStroke</a>
|
||||
for two columns and two rows. The separators are not seen in the middle because the middle row
|
||||
of each node has its <a>RowColumnDefinition.background</a> set to white,
|
||||
and <a>RowColumnDefinition.coversSeparators</a> set to true.
|
||||
</p>
|
||||
<p>
|
||||
The "critical" property on the activity data object controls whether the node is drawn with a red brush or a blue one.
|
||||
There is a special converter that is used to determine the brush used by the links.
|
||||
</p>
|
||||
<p>
|
||||
The light blue legend is implemented by a separate Part implemented in a manner similar to the Node template.
|
||||
However it is not bound to data -- there is no JavaScript object in the model representing the legend.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Absolute positioning within the viewport</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A diagram that does not scroll or zoom and has a fixed area in which to move parts." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
fixedBounds: new go.Rect(0, 0, 500, 300), // document is always 500x300 units
|
||||
allowHorizontalScroll: false, // disallow scrolling or panning
|
||||
allowVerticalScroll: false,
|
||||
allowZoom: false, // disallow zooming
|
||||
"animationManager.isEnabled": false,
|
||||
"undoManager.isEnabled": true,
|
||||
"ModelChanged": function(e) { // just for demonstration purposes,
|
||||
if (e.isTransactionFinished) { // show the model data in the page's TextArea
|
||||
document.getElementById("mySavedModel").textContent = e.model.toJson();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// the background Part showing the fixed bounds of the diagram contents
|
||||
myDiagram.add(
|
||||
$(go.Part,
|
||||
{ layerName: "Grid", position: myDiagram.fixedBounds.position },
|
||||
$(go.Shape, { fill: "oldlace", strokeWidth: 0, desiredSize: myDiagram.fixedBounds.size })
|
||||
));
|
||||
|
||||
// this function is the Node.dragComputation, to limit the movement of the parts
|
||||
// use GRIDPT instead of PT if DraggingTool.isGridSnapEnabled and movement should snap to grid
|
||||
function stayInFixedArea(part, pt, gridpt) {
|
||||
var diagram = part.diagram;
|
||||
if (diagram === null) return pt;
|
||||
// compute the document area without padding
|
||||
var v = diagram.documentBounds.copy();
|
||||
v.subtractMargin(diagram.padding);
|
||||
// get the bounds of the part being dragged
|
||||
var b = part.actualBounds;
|
||||
var loc = part.location;
|
||||
// now limit the location appropriately
|
||||
var x = Math.max(v.x, Math.min(pt.x, v.right - b.width)) + (loc.x - b.x);
|
||||
var y = Math.max(v.y, Math.min(pt.y, v.bottom - b.height)) + (loc.y - b.y);
|
||||
return new go.Point(x, y);
|
||||
}
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ dragComputation: stayInFixedArea },
|
||||
// get the size from the model data
|
||||
new go.Binding("desiredSize", "size", go.Size.parse),
|
||||
// get and set the position in the model data
|
||||
new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// temporarily put selected nodes in Foreground layer
|
||||
new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
|
||||
$(go.Shape, "Rectangle",
|
||||
{ strokeWidth: 0 }, // avoid extra thickness from the stroke
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "color"))
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ "key": "Alpha", "pos": "0 0", "size": "50 50", "color": "lightblue" },
|
||||
{ "key": "Beta", "pos": "276 19", "size": "100 100", "color": "orange" },
|
||||
{ "key": "Gamma", "pos": "44 214", "size": "100 50", "color": "lightgreen" },
|
||||
{ "key": "Delta", "pos": "239 171", "size": "50 100", "color": "pink" }
|
||||
],
|
||||
[
|
||||
{ from: "Alpha", to: "Beta" },
|
||||
{ from: "Alpha", to: "Gamma" },
|
||||
{ from: "Gamma", to: "Delta" },
|
||||
{ from: "Delta", to: "Alpha" }
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="width:100%; height:400px"></div>
|
||||
<p>
|
||||
Absolute positioning within the viewport, with no scrolling (or panning) or zooming allowed.
|
||||
</p>
|
||||
<p>
|
||||
There is a special colored background Part that shows the fixed area where Parts may be.
|
||||
It is in the "Grid" Layer so that it is not selectable and is always behind the regular Parts.
|
||||
</p>
|
||||
<p>
|
||||
Parts may not be dragged outside of the fixed document area of the diagram.
|
||||
This is implemented by a custom <a>Part.dragComputation</a> function.
|
||||
</p>
|
||||
<p>
|
||||
Note that the user may still scroll or zoom the whole page.
|
||||
</p>
|
||||
<p>The model data, automatically updated after each change or undo or redo:</p>
|
||||
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,264 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Add or Remove Columns</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactively adding, resizing, and removing rows and columns of a Table Panel in a GoJS Node." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape, { fill: "white" }),
|
||||
$(go.Panel, "Table",
|
||||
new go.Binding("itemArray", "people"),
|
||||
$(go.RowColumnDefinition,
|
||||
{ row: 0, background: "lightgray" }),
|
||||
$(go.RowColumnDefinition,
|
||||
{ row: 1, separatorStroke: "black" }),
|
||||
// the table headers -- remains even if itemArray is empty
|
||||
$(go.Panel, "TableRow",
|
||||
{ isPanelMain: true },
|
||||
new go.Binding("itemArray", "columnDefinitions"),
|
||||
{
|
||||
itemTemplate: // bound to a column definition object
|
||||
$(go.Panel,
|
||||
new go.Binding("column"),
|
||||
$(go.TextBlock,
|
||||
{ margin: new go.Margin(2, 2, 0, 2), font: "bold 10pt sans-serif" },
|
||||
new go.Binding("text"))
|
||||
)
|
||||
}
|
||||
),
|
||||
{ // the rows for the people
|
||||
defaultAlignment: go.Spot.Left,
|
||||
defaultColumnSeparatorStroke: "black",
|
||||
itemTemplate: // bound to a person/row data object
|
||||
$(go.Panel, "TableRow",
|
||||
// which in turn consists of a collection of cell objects,
|
||||
// held by the "columns" property in an Array
|
||||
new go.Binding("itemArray", "columns"),
|
||||
// you could also have other Bindings here for the whole row
|
||||
{
|
||||
itemTemplate: // bound to a cell object
|
||||
$(go.Panel, // each of which as "attr" and "text" properties
|
||||
{ stretch: go.GraphObject.Fill, alignment: go.Spot.TopLeft },
|
||||
new go.Binding("column", "attr",
|
||||
function(a, elt) { // ELT is this bound item/cell Panel
|
||||
// elt.data will be the cell object
|
||||
// elt.panel.data will be the person/row data object
|
||||
// elt.part.data will be the node data object
|
||||
// "columnDefinitions" is on the node data object, so:
|
||||
var cd = findColumnDefinitionForName(elt.part.data, a);
|
||||
if (cd !== null) return cd.column;
|
||||
throw new Error("unknown column name: " + a);
|
||||
}),
|
||||
// you could also have other Bindings here for this cell
|
||||
$(go.TextBlock, { editable: true },
|
||||
{ margin: new go.Margin(2, 2, 0, 2), wrap: go.TextBlock.None },
|
||||
new go.Binding("text").makeTwoWay())
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray: [
|
||||
{ // first node
|
||||
key: 1,
|
||||
columnDefinitions: [
|
||||
// each column definition needs to specify the column used
|
||||
{ attr: "name", text: "Name", column: 0 },
|
||||
{ attr: "phone", text: "Phone #", column: 1 },
|
||||
{ attr: "office", text: "Office", column: 2 }
|
||||
],
|
||||
people: [ // the table of people
|
||||
// each row is a person with an Array of Objects associating a column name with a text value
|
||||
{ columns: [{ attr: "name", text: "Alice" }, { attr: "phone", text: "2345" }, { attr: "office", text: "C4-E18" }] },
|
||||
{ columns: [{ attr: "name", text: "Bob" }, { attr: "phone", text: "9876" }, { attr: "office", text: "E1-B34" }] },
|
||||
{ columns: [{ attr: "name", text: "Carol" }, { attr: "phone", text: "1111" }, { attr: "office", text: "C4-E23" }] },
|
||||
{ columns: [{ attr: "name", text: "Ted" }, { attr: "phone", text: "2222" }, { attr: "office", text: "C4-E197" }] }
|
||||
]
|
||||
},
|
||||
{ // second node
|
||||
key: 2,
|
||||
columnDefinitions: [
|
||||
{ attr: "name", text: "Name", column: 0 },
|
||||
{ attr: "phone", text: "Phone #", column: 2 }, // note the different order of columns
|
||||
{ attr: "office", text: "Office", column: 1 }
|
||||
],
|
||||
people: [
|
||||
{ columns: [{ attr: "name", text: "Robert" }, { attr: "phone", text: "5656" }, { attr: "office", text: "B1-A27" }] },
|
||||
{ columns: [{ attr: "name", text: "Natalie" }, { attr: "phone", text: "5698" }, { attr: "office", text: "B1-B6" }] }
|
||||
]
|
||||
}
|
||||
],
|
||||
linkDataArray: [
|
||||
{ from: 1, to: 2 }
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Add or remove a person row from the selected node's table of people.
|
||||
|
||||
function insertIntoArray() {
|
||||
var n = myDiagram.selection.first();
|
||||
if (n === null) return;
|
||||
var d = n.data;
|
||||
myDiagram.startTransaction("insertIntoTable");
|
||||
// add item as second in the list, at index #1
|
||||
// of course this new data could be more realistic:
|
||||
myDiagram.model.insertArrayItem(d.people, 1, {
|
||||
columns: [{ attr: "name", text: "Elena" },
|
||||
{ attr: "phone", text: "456" },
|
||||
{ attr: "office", text: "LA" }]
|
||||
});
|
||||
myDiagram.commitTransaction("insertIntoTable");
|
||||
}
|
||||
|
||||
function removeFromArray() {
|
||||
var n = myDiagram.selection.first();
|
||||
if (n === null) return;
|
||||
var d = n.data;
|
||||
myDiagram.startTransaction("removeFromTable");
|
||||
// remove second item of list, at index #1
|
||||
myDiagram.model.removeArrayItem(d.people, 1);
|
||||
myDiagram.commitTransaction("removeFromTable");
|
||||
}
|
||||
|
||||
// add or remove a column from the selected node's table of people
|
||||
|
||||
function findColumnDefinitionForName(nodedata, attrname) {
|
||||
var columns = nodedata.columnDefinitions;
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
if (columns[i].attr === attrname) return columns[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findColumnDefinitionForColumn(nodedata, idx) {
|
||||
var columns = nodedata.columnDefinitions;
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
if (columns[i].column === idx) return columns[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function addColumn(attrname) {
|
||||
var n = myDiagram.selection.first();
|
||||
if (n === null) return;
|
||||
var d = n.data;
|
||||
// if name is not given, find an unused column name
|
||||
if (attrname === undefined || attrname === "") {
|
||||
attrname = "new";
|
||||
var count = 1;
|
||||
while (findColumnDefinitionForName(d, attrname) !== null) {
|
||||
attrname = "new" + (count++).toString();
|
||||
}
|
||||
}
|
||||
// find an unused column #
|
||||
var col = 3;
|
||||
while (findColumnDefinitionForColumn(d, col) !== null) {
|
||||
col++;
|
||||
}
|
||||
myDiagram.startTransaction("addColumn");
|
||||
var model = myDiagram.model;
|
||||
// add a column definition for the node's whole table
|
||||
model.addArrayItem(d.columnDefinitions, {
|
||||
attr: attrname,
|
||||
text: attrname,
|
||||
column: col
|
||||
});
|
||||
// add cell to each person in the node's table of people
|
||||
var people = d.people;
|
||||
for (var j = 0; j < people.length; j++) {
|
||||
var person = people[j];
|
||||
model.addArrayItem(person.columns, {
|
||||
attr: attrname,
|
||||
text: Math.floor(Math.random() * 1000).toString()
|
||||
});
|
||||
}
|
||||
myDiagram.commitTransaction("addColumn");
|
||||
}
|
||||
|
||||
function removeColumn() {
|
||||
var n = myDiagram.selection.first();
|
||||
if (n === null) return;
|
||||
var d = n.data;
|
||||
var coldef = d.columnDefinitions[3]; // get the fourth column
|
||||
if (coldef === undefined) return;
|
||||
var attrname = coldef.attr;
|
||||
myDiagram.startTransaction("removeColumn");
|
||||
var model = myDiagram.model;
|
||||
model.removeArrayItem(d.columnDefinitions, 3);
|
||||
// update columns for each person in this table
|
||||
var people = d.people;
|
||||
for (var j = 0; j < people.length; j++) {
|
||||
var person = people[j];
|
||||
var columns = person.columns;
|
||||
for (var k = 0; k < columns.length; k++) {
|
||||
var cell = columns[k];
|
||||
if (cell.attr === attrname) {
|
||||
// get rid of this attribute cell from the person.columns Array
|
||||
model.removeArrayItem(columns, k);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
myDiagram.commitTransaction("removeColumn");
|
||||
}
|
||||
|
||||
function swapTwoColumns() {
|
||||
myDiagram.startTransaction("swapColumns");
|
||||
var model = myDiagram.model;
|
||||
myDiagram.selection.each(function(n) {
|
||||
if (!(n instanceof go.Node)) return;
|
||||
var d = n.data;
|
||||
var phonedef = findColumnDefinitionForName(d, "phone");
|
||||
if (phonedef === null) return;
|
||||
var phonecolumn = phonedef.column; // remember the column number
|
||||
var officedef = findColumnDefinitionForName(d, "office");
|
||||
if (officedef === null) return;
|
||||
var officecolumn = officedef.column; // and this one too
|
||||
model.setDataProperty(phonedef, "column", officecolumn);
|
||||
model.setDataProperty(officedef, "column", phonecolumn);
|
||||
model.updateTargetBindings(d); // update all bindings, to get the cells right
|
||||
});
|
||||
myDiagram.commitTransaction("swapColumns");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
|
||||
<p>Add a row or Remove the second row of the table held by the selected node:</p>
|
||||
<button onclick="insertIntoArray()">Insert Into Array</button>
|
||||
<button onclick="removeFromArray()">Remove From Array</button>
|
||||
<p>Add a column or Remove the fourth column from the table of the selected node:</p>
|
||||
<button onclick="addColumn()">Add Column</button>
|
||||
<button onclick="removeColumn()">Remove Column</button>
|
||||
<p>Swap the "phone" and "office" columns for each selected node:</p>
|
||||
<button onclick="swapTwoColumns()">Swap Two Columns</button>
|
||||
<p>See also the <a href="../extensions/ColumnResizing.html">Column and Row Resizing Tools</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Adding a Custom Node to a Palette</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="An example of having the user customize a Palette by adding copies of Diagram nodes to the Palette's Model." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<link rel='stylesheet' href='../extensions/DataInspector.css' />
|
||||
<script src="../extensions/DataInspector.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
// initialize main Diagram
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape, "Circle",
|
||||
{
|
||||
fill: "white", stroke: "gray", strokeWidth: 2,
|
||||
portId: "", fromLinkable: true, toLinkable: true,
|
||||
fromLinkableDuplicates: true, toLinkableDuplicates: true,
|
||||
fromLinkableSelfNode: true, toLinkableSelfNode: true
|
||||
},
|
||||
new go.Binding("stroke", "color"),
|
||||
new go.Binding("figure")),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
margin: new go.Margin(5, 5, 3, 5), font: "10pt sans-serif",
|
||||
minSize: new go.Size(16, 16), maxSize: new go.Size(120, NaN),
|
||||
textAlign: "center", editable: true
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
);
|
||||
|
||||
// initialize Palette
|
||||
myPalette =
|
||||
$(go.Palette, "myPaletteDiv",
|
||||
{
|
||||
nodeTemplate: myDiagram.nodeTemplate,
|
||||
contentAlignment: go.Spot.Center,
|
||||
layout:
|
||||
$(go.GridLayout,
|
||||
{ wrappingColumn: 1, cellSize: new go.Size(2, 2) }),
|
||||
"ModelChanged": function(e) { // just for demonstration purposes,
|
||||
if (e.isTransactionFinished) { // show the model data in the page's TextArea
|
||||
document.getElementById("mySavedPaletteModel").textContent = e.model.toJson();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// now add the initial contents of the Palette
|
||||
myPalette.model.nodeDataArray = [
|
||||
{ text: "Circle", color: "blue", figure: "Circle" },
|
||||
{ text: "Square", color: "purple", figure: "Square" },
|
||||
{ text: "Ellipse", color: "orange", figure: "Ellipse" },
|
||||
{ text: "Rectangle", color: "red", figure: "Rectangle" },
|
||||
{ text: "Rounded\nRectangle", color: "green", figure: "RoundedRectangle" },
|
||||
{ text: "Triangle", color: "purple", figure: "Triangle" },
|
||||
];
|
||||
|
||||
// initialize Overview
|
||||
myOverview =
|
||||
$(go.Overview, "myOverviewDiv",
|
||||
{
|
||||
observed: myDiagram,
|
||||
contentAlignment: go.Spot.Center
|
||||
});
|
||||
|
||||
var inspector = new Inspector('myInspectorDiv', myDiagram,
|
||||
{
|
||||
// uncomment this line to only inspect the named properties below instead of all properties on each object:
|
||||
// includesOwnProperties: false,
|
||||
properties: {
|
||||
"text": {},
|
||||
// key would be automatically added for nodes, but we want to declare it read-only also:
|
||||
"key": { readOnly: true, show: Inspector.showIfPresent },
|
||||
// color would be automatically added for nodes, but we want to declare it a color also:
|
||||
"color": { type: 'color' },
|
||||
"figure": {}
|
||||
}
|
||||
});
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
// save a model to and load a model from Json text, displayed below the Diagram
|
||||
function save() {
|
||||
var str = myDiagram.model.toJson();
|
||||
document.getElementById("mySavedModel").value = str;
|
||||
}
|
||||
function load() {
|
||||
var str = document.getElementById("mySavedModel").value;
|
||||
myDiagram.model = go.Model.fromJson(str);
|
||||
}
|
||||
|
||||
function addToPalette() {
|
||||
var node = myDiagram.selection.filter(function(p) { return p instanceof go.Node; }).first();
|
||||
if (node !== null) {
|
||||
myPalette.startTransaction();
|
||||
var item = myPalette.model.copyNodeData(node.data);
|
||||
myPalette.model.addNodeData(item);
|
||||
myPalette.commitTransaction("added item to palette");
|
||||
}
|
||||
}
|
||||
|
||||
// The user cannot delete selected nodes in the Palette with the Delete key or Control-X,
|
||||
// but they can if they do so programmatically.
|
||||
function removeFromPalette() {
|
||||
myPalette.commandHandler.deleteSelection();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="width:100%; white-space:nowrap;">
|
||||
<span style="display: inline-block; vertical-align: top; padding: 2px; width:140px">
|
||||
<div id="myPaletteDiv" style="background-color: whitesmoke; border: solid 1px black; height: 400px"></div>
|
||||
<div id="myOverviewDiv" style="border: solid 1px black; height: 100px"></div>
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: top; padding: 2px; width:500px">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; height: 500px"></div>
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: top; padding: 2px; width:200px">
|
||||
<div id="myInspectorDiv" class="inspector"></div>
|
||||
</span>
|
||||
</div>
|
||||
<p>
|
||||
This sample supports the normal kind of drag-and-drop from a <a>Palette</a> to a <a>Diagram</a>.
|
||||
The Data <a>Inspector</a> allows you to edit the properties of a selected node in the diagram.
|
||||
</p>
|
||||
<p>
|
||||
This sample also supports dynamically adding a copy of a selected node in the diagram to the
|
||||
palette by the "Add To Palette" button.
|
||||
See the current state of the palette's model in the top textarea.
|
||||
The palette is <a>Diagram.isReadOnly</a>, so the user cannot delete selected nodes from the palette.
|
||||
But the "Delete From Palette" button removes any selected nodes from the palette.
|
||||
</p>
|
||||
<div>
|
||||
<button onclick="addToPalette()">Add To Palette</button>
|
||||
<button onclick="removeFromPalette()">Delete From Palette</button>
|
||||
Palette model:
|
||||
</div>
|
||||
<textarea id="mySavedPaletteModel" style="width:100%;height:200px"></textarea>
|
||||
<div>
|
||||
<button id="loadModel" onclick="load()">Load</button>
|
||||
<button id="saveModel" onclick="save()">Save</button>
|
||||
Diagram model:
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:200px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"nodeDataArray": [
|
||||
{ "key": 1, "text": "hello", "figure":"Circle", "color":"green", "location":"0 0" },
|
||||
{ "key": 2, "text": "world", "figure":"Rectangle", "color":"red", "location":"100 0" }
|
||||
],
|
||||
"linkDataArray": [
|
||||
{ "from":1, "to":2 }
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,251 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Selection Adornment Buttons</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="When a diagram node is selected show a selection Adornment holding buttons on which a click invokes a command or a drag starts a tool">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
"linkingTool.isEnabled": false, // invoked explicitly by drawLink function, below
|
||||
"linkingTool.direction": go.LinkingTool.ForwardsOnly, // only draw "from" towards "to"
|
||||
"undoManager.isEnabled": true // enable undo & redo
|
||||
});
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ routing: go.Link.AvoidsNodes, corner: 5 },
|
||||
$(go.Shape, { strokeWidth: 1.5 }),
|
||||
$(go.Shape, { toArrow: "OpenTriangle" })
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{
|
||||
desiredSize: new go.Size(80, 80),
|
||||
// rearrange the link points evenly along the sides of the nodes as links are
|
||||
// drawn or reconnected -- these event handlers only make sense when the fromSpot
|
||||
// and toSpot are Spot.xxxSides
|
||||
linkConnected: function(node, link, port) {
|
||||
if (link.fromNode !== null) link.fromNode.invalidateConnectedLinks();
|
||||
if (link.toNode !== null) link.toNode.invalidateConnectedLinks();
|
||||
},
|
||||
linkDisconnected: function(node, link, port) {
|
||||
if (link.fromNode !== null) link.fromNode.invalidateConnectedLinks();
|
||||
if (link.toNode !== null) link.toNode.invalidateConnectedLinks();
|
||||
},
|
||||
locationSpot: go.Spot.Center
|
||||
},
|
||||
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "SHAPE", // named so that changeColor can modify it
|
||||
strokeWidth: 0, // no border
|
||||
fill: "lightgray", // default fill color
|
||||
portId: "",
|
||||
// use the following property if you want users to draw new links
|
||||
// interactively by dragging from the Shape, and re-enable the LinkingTool
|
||||
// in the initialization of the Diagram
|
||||
//cursor: "pointer",
|
||||
fromSpot: go.Spot.AllSides, fromLinkable: true,
|
||||
fromLinkableDuplicates: true, fromLinkableSelfNode: true,
|
||||
toSpot: go.Spot.AllSides, toLinkable: true,
|
||||
toLinkableDuplicates: true, toLinkableSelfNode: true
|
||||
},
|
||||
new go.Binding("fill", "color").makeTwoWay()),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
name: "TEXTBLOCK", // named so that editText can start editing it
|
||||
margin: 3,
|
||||
// use the following property if you want users to interactively start
|
||||
// editing the text by clicking on it or by F2 if the node is selected:
|
||||
//editable: true,
|
||||
overflow: go.TextBlock.OverflowEllipsis,
|
||||
maxLines: 5
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
);
|
||||
|
||||
// a selected node shows an Adornment that includes both a blue border
|
||||
// and a row of Buttons above the node
|
||||
myDiagram.nodeTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, "Spot",
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
|
||||
$(go.Placeholder)
|
||||
),
|
||||
$(go.Panel, "Horizontal",
|
||||
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
|
||||
$("Button",
|
||||
{ click: editText }, // defined below, to support editing the text of the node
|
||||
$(go.TextBlock, "t",
|
||||
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
|
||||
),
|
||||
$("Button",
|
||||
{ click: changeColor, "_buttonFillOver": "transparent" }, // defined below, to support changing the color of the node
|
||||
new go.Binding("ButtonBorder.fill", "color", nextColor),
|
||||
$(go.Shape,
|
||||
{ fill: null, stroke: null, desiredSize: new go.Size(14, 14) })
|
||||
),
|
||||
$("Button",
|
||||
{ // drawLink is defined below, to support interactively drawing new links
|
||||
click: drawLink, // click on Button and then click on target node
|
||||
actionMove: drawLink // drag from Button to the target node
|
||||
},
|
||||
$(go.Shape,
|
||||
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
|
||||
),
|
||||
$("Button",
|
||||
{
|
||||
actionMove: dragNewNode, // defined below, to support dragging from the button
|
||||
_dragData: { text: "a Node", color: "lightgray" }, // node data to copy
|
||||
click: clickNewNode // defined below, to support a click on the button
|
||||
},
|
||||
$(go.Shape,
|
||||
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
function editText(e, button) {
|
||||
var node = button.part.adornedPart;
|
||||
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
|
||||
}
|
||||
|
||||
// used by nextColor as the list of colors through which we rotate
|
||||
var myColors = ["lightgray", "lightblue", "lightgreen", "yellow", "orange", "pink"];
|
||||
|
||||
// used by both the Button Binding and by the changeColor click function
|
||||
function nextColor(c) {
|
||||
var idx = myColors.indexOf(c);
|
||||
if (idx < 0) return "lightgray";
|
||||
if (idx >= myColors.length - 1) idx = 0;
|
||||
return myColors[idx + 1];
|
||||
}
|
||||
|
||||
function changeColor(e, button) {
|
||||
var node = button.part.adornedPart;
|
||||
var shape = node.findObject("SHAPE");
|
||||
if (shape === null) return;
|
||||
node.diagram.startTransaction("Change color");
|
||||
shape.fill = nextColor(shape.fill);
|
||||
button["_buttonFillNormal"] = nextColor(shape.fill); // update the button too
|
||||
node.diagram.commitTransaction("Change color");
|
||||
}
|
||||
|
||||
function drawLink(e, button) {
|
||||
var node = button.part.adornedPart;
|
||||
var tool = e.diagram.toolManager.linkingTool;
|
||||
tool.startObject = node.port;
|
||||
e.diagram.currentTool = tool;
|
||||
tool.doActivate();
|
||||
}
|
||||
|
||||
// used by both clickNewNode and dragNewNode to create a node and a link
|
||||
// from a given node to the new node
|
||||
function createNodeAndLink(data, fromnode) {
|
||||
var diagram = fromnode.diagram;
|
||||
var model = diagram.model;
|
||||
var nodedata = model.copyNodeData(data);
|
||||
model.addNodeData(nodedata);
|
||||
var newnode = diagram.findNodeForData(nodedata);
|
||||
var linkdata = model.copyLinkData({});
|
||||
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
|
||||
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
|
||||
model.addLinkData(linkdata);
|
||||
diagram.select(newnode);
|
||||
return newnode;
|
||||
}
|
||||
|
||||
// the Button.click event handler, called when the user clicks the "N" button
|
||||
function clickNewNode(e, button) {
|
||||
var data = button._dragData;
|
||||
if (!data) return;
|
||||
e.diagram.startTransaction("Create Node and Link");
|
||||
var fromnode = button.part.adornedPart;
|
||||
var newnode = createNodeAndLink(button._dragData, fromnode);
|
||||
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
|
||||
e.diagram.commitTransaction("Create Node and Link");
|
||||
}
|
||||
|
||||
// the Button.actionMove event handler, called when the user drags within the "N" button
|
||||
function dragNewNode(e, button) {
|
||||
var tool = e.diagram.toolManager.draggingTool;
|
||||
if (tool.isBeyondDragSize()) {
|
||||
var data = button._dragData;
|
||||
if (!data) return;
|
||||
e.diagram.startTransaction("button drag"); // see doDeactivate, below
|
||||
var newnode = createNodeAndLink(data, button.part.adornedPart);
|
||||
newnode.location = e.diagram.lastInput.documentPoint;
|
||||
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
|
||||
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
|
||||
tool.currentPart = newnode;
|
||||
e.diagram.currentTool = tool;
|
||||
tool.doActivate();
|
||||
}
|
||||
}
|
||||
|
||||
// using dragNewNode also requires modifying the standard DraggingTool so that it
|
||||
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
|
||||
// do this by overriding DraggingTool.doDeactivate:
|
||||
var tool = myDiagram.toolManager.draggingTool;
|
||||
tool.doDeactivate = function() {
|
||||
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
|
||||
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
|
||||
tool.diagram.commitTransaction();
|
||||
}
|
||||
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
|
||||
};
|
||||
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 1, text: "Alpha", color: "lightblue", location: "0 0" },
|
||||
{ key: 2, text: "Beta", color: "orange", location: "140 0" },
|
||||
{ key: 3, text: "Gamma", color: "lightgreen", location: "0 140" },
|
||||
{ key: 4, text: "Delta", color: "pink", location: "140 140" }
|
||||
],
|
||||
[
|
||||
{ from: 1, to: 2 }
|
||||
]);
|
||||
|
||||
myDiagram.findNodeForKey(4).isSelected = true;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>
|
||||
The node template uses a custom <a>Part.selectionAdornmentTemplate</a> to
|
||||
add a row of Buttons when the node is selected.
|
||||
Select a node and you will see the Buttons for the node.
|
||||
</p>
|
||||
<p>
|
||||
The first button, "T", when clicked, starts in-place editing of the text.
|
||||
</p>
|
||||
<p>
|
||||
The second button, "C", when clicked, changes the color of the node,
|
||||
rotating through a list of colors.
|
||||
</p>
|
||||
<p>
|
||||
The third button, "L", when clicked or dragged, starts the <a>LinkingTool</a>,
|
||||
drawing a new link starting at the selected node.
|
||||
</p>
|
||||
<p>
|
||||
The fourth button, "N", when clicked, adds a new node and creates a link from
|
||||
the selected node to the new node.
|
||||
Dragging from the fourth button does the same thing as a click but also activates
|
||||
the <a>DraggingTool</a>, allowing the user to drag the new node where they like.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,302 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>All GoJS Samples</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Alphabetical list of all GoJS samples." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="../assets/css/main.css" rel="stylesheet" type="text/css" /> <!-- you don't need to use this -->
|
||||
</head>
|
||||
<body>
|
||||
<!-- When adding a new sample, add it to the samples/all.html list. -->
|
||||
<!-- If you want the sample to show up at samples/index.html, add it to samples/indexList.js and add a screenshot in assets/images/screenshots. -->
|
||||
<!-- Consider adding it to assets/js/goSamples.js, if you want a sample to show up in the left side navigation bar. -->
|
||||
<!-- And don't forget to mention it in changelog.html. -->
|
||||
<h3>Samples and Extensions</h3>
|
||||
<p>
|
||||
Below is a list of every <a href="../index.html">GoJS</a> sample and extension.
|
||||
See the <a href="index.html">samples index</a> for a subset of this list with screenshots and short descriptions.
|
||||
</p>
|
||||
|
||||
<h3>Samples</h3>
|
||||
<p>
|
||||
These are in the <code>samples/</code> directory.
|
||||
Most are stand-alone apps, but a few depend on extensions and/or on common third-party libraries.
|
||||
Remember that the GoJS library does not depend on any other library.
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="absolute.html">absolute.html</a></li>
|
||||
<li><a href="addRemoveColumns.html">addRemoveColumns.html</a></li>
|
||||
<li><a href="addToPalette.html">addToPalette.html</a></li>
|
||||
<li><a href="adornmentButtons.html">adornmentButtons.html</a></li>
|
||||
<li><a href="animatedFocus.html">animatedFocus.html</a></li>
|
||||
<li><a href="arrowheads.html">arrowheads.html</a></li>
|
||||
<li><a href="barCharts.html">barCharts.html</a></li>
|
||||
<li><a href="basic.html">basic.html</a></li>
|
||||
<li><a href="beatPaths.html">beatPaths.html</a></li>
|
||||
<li><a href="belts.html">belts.html</a></li>
|
||||
<li><a href="blockEditor.html">blockEditor.html</a></li>
|
||||
<li><a href="candlestickCharts.html">candlestickCharts.html</a></li>
|
||||
<li><a href="canvases.html">canvases.html</a></li>
|
||||
<li><a href="classHierarchy.html">classHierarchy.html</a></li>
|
||||
<li><a href="cLayout.html">cLayout.html</a></li>
|
||||
<li><a href="comments.html">comments.html</a></li>
|
||||
<li><a href="conceptMap.html">conceptMap.html</a></li>
|
||||
<li><a href="connectionBoxNode.html">connectionBoxNode.html</a></li>
|
||||
<li><a href="constantSize.html">constantSize.html</a></li>
|
||||
<li><a href="contentAlign.html">contentAlign.html</a></li>
|
||||
<li><a href="controlGauges.html">controlGauges.html</a></li>
|
||||
<li><a href="curviness.html">curviness.html</a></li>
|
||||
<li><a href="customAnimations.html">customAnimations.html</a></li>
|
||||
<li><a href="customContextMenu.html">customContextMenu.html</a></li>
|
||||
<li><a href="customExpandCollapse.html">customExpandCollapse.html</a></li>
|
||||
<li><a href="customTextEditingTool.html">customTextEditingTool.html</a></li>
|
||||
<li><a href="dataFlow.html">dataFlow.html</a></li>
|
||||
<li><a href="dataFlowVertical.html">dataFlowVertical.html</a></li>
|
||||
<li><a href="dataVisualization.html">dataVisualization.html</a></li>
|
||||
<li><a href="decisionTree.html">decisionTree.html</a></li>
|
||||
<li><a href="distances.html">distances.html</a></li>
|
||||
<li><a href="DOMTree.html">DOMTree.html</a></li>
|
||||
<li><a href="donutCharts.html">donutCharts.html</a></li>
|
||||
<li><a href="doubleCircle.html">doubleCircle.html</a></li>
|
||||
<li><a href="doubleTree.html">doubleTree.html</a></li>
|
||||
<li><a href="dragDropFields.html">dragDropFields.html</a></li>
|
||||
<li><a href="draggableLink.html">draggableLink.html</a></li>
|
||||
<li><a href="draggablePorts.html">draggablePorts.html</a></li>
|
||||
<li><a href="dragOutFields.html">dragOutFields.html</a></li>
|
||||
<li><a href="dragUnoccupied.html">dragUnoccupied.html</a></li>
|
||||
<li><a href="dynamicPieChart.html">dynamicPieChart.html</a></li>
|
||||
<li><a href="dynamicPorts.html">dynamicPorts.html</a></li>
|
||||
<li><a href="entityRelationship.html">entityRelationship.html</a></li>
|
||||
<li><a href="euler.html">euler.html</a></li>
|
||||
<li><a href="familyTree.html">familyTree.html</a></li>
|
||||
<li><a href="familyTreeJP.html">familyTreeJP.html</a></li>
|
||||
<li><a href="faultTree.html">faultTree.html</a></li>
|
||||
<li><a href="fdLayout.html">fdLayout.html</a></li>
|
||||
<li><a href="flowBuilder.html">flowBuilder.html</a></li>
|
||||
<li><a href="flowchart.html">flowchart.html</a></li>
|
||||
<li><a href="flowgrammer.html">flowgrammer.html</a></li>
|
||||
<li><a href="friendWheel.html">friendWheel.html</a></li>
|
||||
<li><a href="gameOfLife.html">gameOfLife.html</a></li>
|
||||
<li><a href="gantt.html">gantt.html</a></li>
|
||||
<li><a href="genogram.html">genogram.html</a></li>
|
||||
<li><a href="gestureBehavior.html">gestureBehavior.html</a></li>
|
||||
<li><a href="gLayout.html">gLayout.html</a></li>
|
||||
<li><a href="grafcet.html">grafcet.html</a></li>
|
||||
<li><a href="grouping.html">grouping.html</a></li>
|
||||
<li><a href="hoverButtons.html">hoverButtons.html</a></li>
|
||||
<li><a href="htmlDragDrop.html">htmlDragDrop.html</a></li>
|
||||
<li><a href="htmlInteraction.html">htmlInteraction.html</a></li>
|
||||
<li><a href="htmlLightBoxContextMenu.html">htmlLightBoxContextMenu.html</a></li>
|
||||
<li><a href="icons.html">icons.html</a></li>
|
||||
<li><a href="incrementalTree.html">incrementalTree.html</a></li>
|
||||
<li><a href="instrumentGauge.html">instrumentGauge.html</a></li>
|
||||
<li><a href="interactiveForce.html">interactiveForce.html</a></li>
|
||||
<li><a href="IVRtree.html">IVRtree.html</a></li>
|
||||
<li><a href="jQueryDragDrop.html">jQueryDragDrop.html</a></li>
|
||||
<li><a href="kanban.html">kanban.html</a></li>
|
||||
<li><a href="kittenMonitor.html">kittenMonitor.html</a></li>
|
||||
<li><a href="ldLayout.html">ldLayout.html</a></li>
|
||||
<li><a href="leaflet.html">leaflet.html</a></li>
|
||||
<li><a href="linksToLinks.html">linksToLinks.html</a></li>
|
||||
<li><a href="localView.html">localView.html</a></li>
|
||||
<li><a href="logicCircuit.html">logicCircuit.html</a></li>
|
||||
<li><a href="macros.html">macros.html</a></li>
|
||||
<li><a href="magnifier.html">magnifier.html</a></li>
|
||||
<li><a href="mindMap.html">mindMap.html</a></li>
|
||||
<li><a href="minimal.html">minimal.html</a></li>
|
||||
<li><a href="minimalBlob.html">minimalBlob.html</a></li>
|
||||
<li><a href="minimalSvg.html">minimalSvg.html</a></li>
|
||||
<li><a href="multiArrow.html">multiArrow.html</a></li>
|
||||
<li><a href="multiColorLinks.html">multiColorLinks.html</a></li>
|
||||
<li><a href="multiNodePathLinks.html">multiNodePathLinks.html</a></li>
|
||||
<li><a href="navigation.html">navigation.html</a></li>
|
||||
<li><a href="network.html">network.html</a></li>
|
||||
<li><a href="orgChartAssistants.html">orgChartAssistants.html</a></li>
|
||||
<li><a href="orgChartEditor.html">orgChartEditor.html</a></li>
|
||||
<li><a href="orgChartExtras.html">orgChartExtras.html</a></li>
|
||||
<li><a href="orgChartStatic.html">orgChartStatic.html</a></li>
|
||||
<li><a href="pageFlow.html">pageFlow.html</a></li>
|
||||
<li><a href="panelLayout.html">panelLayout.html</a></li>
|
||||
<li><a href="parseTree.html">parseTree.html</a></li>
|
||||
<li><a href="pathAnimation.html">pathAnimation.html</a></li>
|
||||
<li><a href="PERT.html">PERT.html</a></li>
|
||||
<li><a href="pieCharts.html">pieCharts.html</a></li>
|
||||
<li><a href="pinchResizing.html">pinchResizing.html</a></li>
|
||||
<li><a href="pipes.html">pipes.html</a></li>
|
||||
<li><a href="pipeTree.html">pipeTree.html</a></li>
|
||||
<li><a href="planogram.html">planogram.html</a></li>
|
||||
<li><a href="processFlow.html">processFlow.html</a></li>
|
||||
<li><a href="productionEditor.html">productionEditor.html</a></li>
|
||||
<li><a href="productionProcess.html">productionProcess.html</a></li>
|
||||
<li><a href="radial.html">radial.html</a></li>
|
||||
<li><a href="radialAdornment.html">radialAdornment.html</a></li>
|
||||
<li><a href="radialPartition.html">radialPartition.html</a></li>
|
||||
<li><a href="records.html">records.html</a></li>
|
||||
<li><a href="regrouping.html">regrouping.html</a></li>
|
||||
<li><a href="regroupingTreeView.html">regroupingTreeView.html</a></li>
|
||||
<li><a href="relationships.html">relationships.html</a></li>
|
||||
<li><a href="require.html">require.html</a></li>
|
||||
<li><a href="roundedGroups.html">roundedGroups.html</a></li>
|
||||
<li><a href="ruleredDiagram.html">ruleredDiagram.html</a></li>
|
||||
<li><a href="sankey.html">sankey.html</a></li>
|
||||
<li><a href="scrollModes.html">scrollModes.html</a></li>
|
||||
<li><a href="seatingChart.html">seatingChart.html</a></li>
|
||||
<li><a href="selectableFields.html">selectableFields.html</a></li>
|
||||
<li><a href="selectablePorts.html">selectablePorts.html</a></li>
|
||||
<li><a href="sequenceDiagram.html">sequenceDiagram.html</a></li>
|
||||
<li><a href="sequentialFunction.html">sequentialFunction.html</a></li>
|
||||
<li><a href="shapes.html">shapes.html</a></li>
|
||||
<li><a href="sharedStates.html">sharedStates.html</a></li>
|
||||
<li><a href="shopFloorMonitor.html">shopFloorMonitor.html</a></li>
|
||||
<li><a href="singlePage.html">singlePage.html</a></li>
|
||||
<li><a href="spacingZoom.html">spacingZoom.html</a></li>
|
||||
<li><a href="sparklineGraphs.html">sparklineGraphs.html</a></li>
|
||||
<li><a href="spreadsheet.html">spreadsheet.html</a></li>
|
||||
<li><a href="stateChart.html">stateChart.html</a></li>
|
||||
<li><a href="stateChartIncremental.html">stateChartIncremental.html</a></li>
|
||||
<li><a href="svgDataUrl.html">svgDataUrl.html</a></li>
|
||||
<li><a href="swimBands.html">swimBands.html</a></li>
|
||||
<li><a href="swimLanes.html">swimLanes.html</a></li>
|
||||
<li><a href="swimLanesVertical.html">swimLanesVertical.html</a></li>
|
||||
<li><a href="systemDynamics.html">systemDynamics.html</a></li>
|
||||
<li><a href="tabs.html">tabs.html</a></li>
|
||||
<li><a href="taperedLinks.html">taperedLinks.html</a></li>
|
||||
<li><a href="thermometer.html">thermometer.html</a></li>
|
||||
<li><a href="tiger.html">tiger.html</a></li>
|
||||
<li><a href="timeline.html">timeline.html</a></li>
|
||||
<li><a href="tLayout.html">tLayout.html</a></li>
|
||||
<li><a href="tournament.html">tournament.html</a></li>
|
||||
<li><a href="treeLoadAnimation.html">treeLoadAnimation.html</a></li>
|
||||
<li><a href="treeMapper.html">treeMapper.html</a></li>
|
||||
<li><a href="treeView.html">treeView.html</a></li>
|
||||
<li><a href="triStateCheckBoxTree.html">triStateCheckBoxTree.html</a></li>
|
||||
<li><a href="twoDiagrams.html">twoDiagrams.html</a></li>
|
||||
<li><a href="twoHalves.html">twoHalves.html</a></li>
|
||||
<li><a href="umlClass.html">umlClass.html</a></li>
|
||||
<li><a href="updateDemo.html">updateDemo.html</a></li>
|
||||
<li><a href="virtualized.html">virtualized.html</a></li>
|
||||
<li><a href="virtualizedForceLayout.html">virtualizedForceLayout.html</a></li>
|
||||
<li><a href="virtualizedTree.html">virtualizedTree.html</a></li>
|
||||
<li><a href="virtualizedTreeLayout.html">virtualizedTreeLayout.html</a></li>
|
||||
<li><a href="visualTree.html">visualTree.html</a></li>
|
||||
<li><a href="visualTreeGrouping.html">visualTreeGrouping.html</a></li>
|
||||
<li><a href="vue.html">vue.html</a></li>
|
||||
<li><a href="wordcloud.html">wordcloud.html</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Extensions</h3>
|
||||
<p>
|
||||
There are three extension directories.
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>extensions/</code>, loadable in a simple <script> tag</li>
|
||||
<li><code>extensionsTS/</code>, implemented in TypeScript, compiled as a UMD module</li>
|
||||
<li><code>extensionsJSM/</code>, using <code>go.mjs</code>, compiled as an ES6 module</li>
|
||||
</ul>
|
||||
<p>
|
||||
In all cases you should copy the extension code into your own project and make sure that any <code>require</code> or <code>import</code> is adjusted to fit your environment.
|
||||
Subtle errors can occur if your app loads the GoJS library more than once, especially if they are of different versions.
|
||||
</p>
|
||||
|
||||
<h4>Layout Extensions:</h4>
|
||||
<ul>
|
||||
<li><a href="../extensions/Arranging.html">Arranging.html</a>, using <a href="../extensions/ArrangingLayout.js">ArrangingLayout.js</a></li>
|
||||
<li><a href="doubleTree.html">DoubleTree.html</a>, using <a href="../extensions/DoubleTreeLayout.js">DoubleTreeLayout.js</a></li>
|
||||
<li><a href="../extensions/Fishbone.html">Fishbone.html</a>, using <a href="../extensions/FishboneLayout.js">FishboneLayout.js</a></li>
|
||||
<li><a href="../extensions/PackedHierarchy.html">PackedHierarchy.html, using <a href="../extensionsTS/PackedLayout.js">PackedLayout.js</a></li>
|
||||
<li><a href="../extensions/PackedLayout.html">PackedLayout.html</a>, using <a href="../extensionsTS/PackedLayout.js">PackedLayout.js</a></li>
|
||||
<li><a href="../extensions/Parallel.html">Parallel.html</a>, using <a href="../extensions/ParallelLayout.js">ParallelLayout.js</a></li>
|
||||
<li><a href="../extensions/Serpentine.html">Serpentine.html</a>, using <a href="../extensions/SerpentineLayout.js">SerpentineLayout.js</a></li>
|
||||
<li><a href="../extensions/Spiral.html">Spiral.html</a>, using <a href="../extensions/SpiralLayout.js">SpiralLayout.js</a></li>
|
||||
<li><a href="../extensions/SwimLaneLayout.html">SwimLaneLayout.html, using <a href="../extensionsTS/SwimLaneLayout.js">SwimLaneLayout.js</a></li>
|
||||
<li><a href="../extensions/Table.html">Table.html</a>, using <a href="../extensions/TableLayout.js">TableLayout.js</a></li>
|
||||
<li><a href="../extensions/TreeMap.html">TreeMap.html</a>, using <a href="../extensions/TreeMapLayout.js">TreeMapLayout.js</a></li>
|
||||
<li><a href="../extensionsTS/VirtualizedPacked.html">VirtualizedPacked.html</a>, using <a href="../extensionsTS/VirtualizedPackedLayout.js">VirtualizedPackedLayout.js</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Tool Extensions:</h4>
|
||||
<ul>
|
||||
<li><a href="../extensions/ColumnResizing.html">ColumnResizing.html</a>, using <a href="../extensions/ColumnResizingTool.js">ColumnResizingTool.js</a></li>
|
||||
<li><a href="../extensions/CurvedLinkReshaping.html">CurvedLinkReshaping.html</a> using <a href="../extensions/CurvedLinkReshapingTool.js">CurvedLinkReshapingTool.js</a></li>
|
||||
<li><a href="../extensions/DragCreating.html">DragCreating.html</a> using <a href="../extensions/DragCreatingTool.js">DragCreatingTool.js</a></li>
|
||||
<li><a href="../extensions/DragZooming.html">DragZooming.html</a> using <a href="../extensions/DragZoomingTool.js">DragZoomingTool.js</a></li>
|
||||
<li><a href="../extensions/FreehandDrawing.html">FreehandDrawing.html</a> using <a href="../extensions/FreehandDrawingTool.js">FreehandDrawingTool.js</a></li>
|
||||
<li><a href="../extensions/GeometryReshaping.html">GeometryReshaping.html</a> using <a href="../extensions/GeometryReshapingTool.js">GeometryReshapingTool.js</a></li>
|
||||
<li><a href="../extensions/GuidedDragging.html">GuidedDragging.html</a> using <a href="../extensions/GuidedDraggingTool.js">GuidedDraggingTool.js</a></li>
|
||||
<li><a href="../extensions/LinkLabelDragging.html">LinkLabelDragging.html</a> using <a href="../extensions/LinkLabelDraggingTool.js">LinkLabelDraggingTool.js</a></li>
|
||||
<li><a href="../extensions/LinkLabelOnPathDragging.html">LinkLabelOnPathDragging.html</a> using <a href="../extensions/LinkLabelOnPathDraggingTool.js">LinkLabelOnPathDraggingTool.js</a></li>
|
||||
<li><a href="../extensions/LinkShifting.html">LinkShifting.html</a> using <a href="../extensions/LinkShiftingTool.js">LinkShiftingTool.js</a></li>
|
||||
<li><a href="../extensions/NodeLabelDragging.html">NodeLabelDragging.html</a> using <a href="../extensions/NodeLabelDraggingTool.js">NodeLabelDraggingTool.js</a></li>
|
||||
<li><a href="../extensions/NonRealtimeDragging.html">NonRealtimeDragging.html</a> using <a href="../extensions/NonRealtimeDraggingTool.js">NonRealtimeDraggingTool.js</a></li>
|
||||
<li><a href="../extensions/OrthogonalLinkReshaping.html">OrthogonalLinkReshaping.html</a> using <a href="../extensions/OrthogonalLinkReshapingTool.js">OrthogonalLinkReshapingTool.js</a></li>
|
||||
<li><a href="../extensions/OverviewResizing.html">OverviewResizing.html</a> using <a href="../extensions/OverviewResizingTool.js">OverviewResizingTool.js</a></li>
|
||||
<li><a href="../extensions/PolygonDrawing.html">PolygonDrawing.html</a> using <a href="../extensions/PolygonDrawingTool.js">PolygonDrawingTool.js</a></li>
|
||||
<li><a href="../extensions/PolylineLinking.html">PolylineLinking.html</a> using <a href="../extensions/PolylineLinkingTool.js">PolylineLinkingTool.js</a></li>
|
||||
<li><a href="../extensions/PortShifting.html">PortShifting.html</a> using <a href="../extensions/PortShiftingTool.js">PortShiftingTool.js</a></li>
|
||||
<li><a href="../extensions/RealtimeDragSelecting.html">RealtimeDragSelecting.html</a> using <a href="../extensions/RealtimeDragSelectingTool.js">RealtimeDragSelectingTool.js</a></li>
|
||||
<li><a href="../extensions/Rescaling.html">Rescaling.html</a> using <a href="../extensions/RescalingTool.js">RescalingTool.js</a></li>
|
||||
<li><a href="../extensions/ResizeMultiple.html">ResizeMultiple.html</a> using <a href="../extensions/ResizeMultipleTool.js">ResizeMultipleTool.js</a></li>
|
||||
<li><a href="../extensions/RotateMultiple.html">RotateMultiple.html</a> using <a href="../extensions/RotateMultipleTool.js">RotateMultipleTool.js</a></li>
|
||||
<li><a href="../extensions/SectorReshaping.html">SectorReshaping.html</a> using <a href="../extensions/SectorReshapingTool.js">SectorReshapingTool.js</a></li>
|
||||
<li><a href="../extensions/SnapLinkReshaping.html">SnapLinkReshaping.html</a> using <a href="../extensions/SnapLinkReshapingTool.js">SnapLinkReshapingTool.js</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>CommandHandler Extensions:</h4>
|
||||
<ul>
|
||||
<li><a href="../extensions/DrawCommandHandler.html">DrawCommandHandler.html</a> using <a href="../extensions/DrawCommandHandler.js">DrawCommandHandler.js</a></li>
|
||||
<li><a href="../extensions/LocalStorageCommandHandler.html">LocalStorageCommandHandler.html</a> using <a href="../extensions/LocalStorageCommandHandler.js">LocalStorageCommandHandler.js</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Builder and Link Extensions:</h4>
|
||||
<ul>
|
||||
<li><a href="../extensions/BalloonLink.html">BalloonLink.html</a> using <a href="../extensions/BalloonLink.js">BalloonLink.js</a></li>
|
||||
<li><a href="../extensions/CheckBoxes.html">CheckBoxes.html</a> using predefined "CheckBox"s shown in <a href="../extensions/Buttons.js">Buttons.js</a></li>
|
||||
<li><a href="../extensions/Dimensioning.html">Dimensioning.html</a> using <a href="../extensions/DimensioningLink.js">DimensioningLink.js</a></li>
|
||||
<li><a href="../extensions/Hyperlink.html">Hyperlink.html</a> using <a href="../extensions/HyperlinkText.js">HyperlinkText.js</a></li>
|
||||
<li><a href="../extensions/ParallelRoute.html">ParallelRoute.html</a> using <a href="../extensions/ParallelRouteLink.js">ParallelRouteLink.js</a></li>
|
||||
<li><a href="../extensions/ScrollingTable.html">ScrollingTable.html</a> using <a href="../extensions/ScrollingTable.js">ScrollingTable.js</a></li>
|
||||
<li><a href="../extensions/TextEditor.html">TextEditor.html</a> using <a href="../extensions/TextEditor.js">TextEditor.js</a> <a href="../extensions/TextEditorRadioButtons.js">TextEditorRadioButtons.js</a> and <a href="../extensions/TextEditorSelectBox.js">TextEditorSelectBox.js</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Miscellaneous Extensions:</h4>
|
||||
<ul>
|
||||
<li><a href="../extensions/DataInspector.html">DataInspector.html</a> using <a href="../extensions/DataInspector.js">DataInspector.js</a></li>
|
||||
<li><a href="../extensions/Robot.html">Robot.html</a> using <a href="../extensions/Robot.js">Robot.js</a></li>
|
||||
<li><a href="../extensions/ZoomSlider.html">ZoomSlider.html</a> using <a href="../extensions/ZoomSlider.js">ZoomSlider.js</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Storage Extensions:</h4>
|
||||
<ul>
|
||||
<li><a href="../projects/storage/samples/GoCloudStorageManager.html">GoCloudStorageManager.html</a> using <a href="../projects/storage/lib/gcs.js">gcs.js</a>; sources at <a href="../projects/storage/src/GoCloudStorageManager.ts">../projects/storage/src/GoCloudStorageManager.ts</a></li>
|
||||
<!--
|
||||
<li><a href="../projects/storage/samples/GoDropBox.html">GoDropBox.html</a> source at: <a href="../projects/storage/src/GoDropBox.ts">../projects/storage/src/GoDropBox.ts</a></li>
|
||||
<li><a href="../projects/storage/samples/GoGoogleDrive.html">GoGoogleDrive.html</a> source at: <a href="../projects/storage/src/GoGoogleDrive.ts">../projects/storage/src/GoGoogleDrive.ts</a></li>
|
||||
<li><a href="../projects/storage/samples/GoOneDrive.html">GoOneDrive.html</a> source at: <a href="../projects/storage/src/GoOneDrive.ts">../projects/storage/src/GoOneDrive.ts</a></li>
|
||||
<li><a href="../projects/storage/samples/GoLocalStorage.html">GoLocalStorage.html</a> source at: <a href="../projects/storage/src/GoLocalStorage.ts">../projects/storage/src/GoLocalStorage.ts</a></li>
|
||||
-->
|
||||
</ul>
|
||||
|
||||
<h4>Projects:</h4>
|
||||
<ul>
|
||||
<li><a href="../projects/floorplannerTS/index.html">new Floor Planner (TypeScript)</a>, in the <code>projects/floorplannerTS/</code> folder</li>
|
||||
<li><a href="../projects/floorplanner/FloorPlanner.html">old Floor Planner (JavaScript)</a>, in the <code>projects/floorplanner/</code> folder</li>
|
||||
<li><a href="../projects/bpmn/BPMN.html">BPMN Editor</a>, in the <code>projects/bpmn/</code> folder</li>
|
||||
<li><a href="../projects/pdf/minimalPDF.html">PDF generator</a>, in the <code>projects/pdf</code> folder</li>
|
||||
<li>See more at <a href="../projects/index.html">Projects</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Predefined, built-in functionality:</h4>
|
||||
<ul>
|
||||
<li><a href="../extensions/Arrowheads.js">Arrowheads.js</a> all shown by <a href="arrowheads.html">arrowheads.html</a></li>
|
||||
<li><a href="../extensions/Buttons.js">Buttons.js</a></li>
|
||||
<li><a href="../extensions/Figures.js">Figures.js</a> all shown by <a href="shapes.html">shapes.html</a></li>
|
||||
<li><a href="../extensions/Templates.js">Templates.js</a></li>
|
||||
<li><a href="../extensions/TextEditor.js">TextEditor.js</a></li>
|
||||
</ul>
|
||||
<p>
|
||||
See the <a href="index.html">samples index</a> for a subset of this list with screenshots and short descriptions.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Drawing Attention to a Node</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="When focussing on a node, scroll with animation to it and show a magnified image of it shrinking in place." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
// allow some empty space to appear when scrolled to the edge of the document
|
||||
scrollMargin: 200,
|
||||
// the layout does not really matter for this sample
|
||||
layout: $(go.GridLayout, { wrappingWidth: 4000 }),
|
||||
"InitialLayoutCompleted": function(e) {
|
||||
// wait until initial layout and initial animation are finished,
|
||||
// then select the node and scroll to it with its own animation
|
||||
var node = null; // you might choose a particular node in your app
|
||||
setTimeout(function() { focusOnNode(node); },
|
||||
e.diagram.animationManager.duration);
|
||||
}
|
||||
});
|
||||
|
||||
// the templates do not really matter for this sample
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ width: 120, height: 60 },
|
||||
$(go.Shape,
|
||||
new go.Binding("fill")),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text"))
|
||||
);
|
||||
|
||||
// create enough nodes so that only part of the document will fit in the viewport
|
||||
var arr = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var color = go.Brush.randomColor();
|
||||
arr.push({ text: color, fill: color });
|
||||
}
|
||||
myDiagram.model = new go.GraphLinksModel(arr);
|
||||
}
|
||||
|
||||
function focusOnNode(node) { // node is optional
|
||||
// If no node is given, choose a node at random, and select it.
|
||||
if (!node) {
|
||||
var arr = myDiagram.model.nodeDataArray;
|
||||
var data = arr[Math.floor(Math.random() * arr.length)];
|
||||
node = myDiagram.findNodeForData(data);
|
||||
}
|
||||
if (!node) return;
|
||||
myDiagram.select(node);
|
||||
|
||||
// Set up an Animation that shows the node significantly larger than normal
|
||||
// and then scales it back down to normal.
|
||||
// This intentionally does not operate on the selected node itself,
|
||||
// but on a temporary copy of it, so that the node and the model are unaffected.
|
||||
var focus1 = node.copy();
|
||||
focus1.layerName = "Tool";
|
||||
focus1.isInDocumentBounds = false;
|
||||
focus1.locationSpot = go.Spot.Center;
|
||||
focus1.location = node.actualBounds.center;
|
||||
// Figure out how large to scale it initially; assume maximum is one third of the viewport size
|
||||
var w = Math.max(node.actualBounds.width, 1);
|
||||
var h = Math.max(node.actualBounds.height, 1);
|
||||
var viewscale = Math.max(myDiagram.viewportBounds.width/w, myDiagram.viewportBounds.height/h) / 3;
|
||||
// Now create the Animation showing the temporary node scaled initially at VIEWSCALE
|
||||
var anim = new go.Animation();
|
||||
anim.addTemporaryPart(focus1, myDiagram);
|
||||
anim.add(focus1, "scale", viewscale, 1.0); // and animating down to scale 1.0
|
||||
// This animation occurs concurrently with the scrolling animation.
|
||||
anim.duration = myDiagram.animationManager.duration + 1000;
|
||||
anim.start();
|
||||
// Meanwhile, make sure that the node is in the viewport, so the user can see it
|
||||
myDiagram.commandHandler.scrollToPart(node);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>
|
||||
Click on this button to select a node at random, scroll to it, and animate a copy of it -- all to
|
||||
draw attention to it.<br/>
|
||||
<button onclick="focusOnNode()">Focus on random Node</button>
|
||||
</p>
|
||||
<p>
|
||||
This calls <a>CommandHandler.scrollToPart</a>, which conducts an animation to scroll
|
||||
the viewport to where the node is. Note that if the node is close to the edge of the document,
|
||||
the viewport cannot be scrolled so that the node is nearer to the center of the viewport unless you
|
||||
increase the <a>Diagram.scrollMargin</a>.
|
||||
</p>
|
||||
<p>
|
||||
This also creates an <a>Animation</a> that operates on a temporary copy of the selected node,
|
||||
making it appear much larger but animating the scale so that it appears to shrink to be
|
||||
the selected node where it is in the diagram.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,168 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>GoJS Arrowheads</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Show all of the predefined kinds of arrowheads for links, which can be used by setting Shape.toArrow or Shape.fromArrow." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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
|
||||
|
||||
var myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
isReadOnly: true, // don't allow move or delete
|
||||
layout: $(go.CircularLayout,
|
||||
{
|
||||
radius: 100, // minimum radius
|
||||
spacing: 0, // circular nodes will touch each other
|
||||
nodeDiameterFormula: go.CircularLayout.Circular, // assume nodes are circular
|
||||
startAngle: 270 // first node will be at top
|
||||
}),
|
||||
// define a DiagramEvent listener
|
||||
"LayoutCompleted": function(e) {
|
||||
// now that the CircularLayout has finished, we know where its center is
|
||||
var cntr = myDiagram.findNodeForKey("Center");
|
||||
cntr.location = myDiagram.layout.actualCenter;
|
||||
}
|
||||
});
|
||||
|
||||
// construct a shared radial gradient brush
|
||||
var radBrush = $(go.Brush, "Radial", { 0: "#550266", 1: "#80418C" });
|
||||
|
||||
// these are the nodes that are in a circle
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node,
|
||||
$(go.Shape, "Circle",
|
||||
{
|
||||
desiredSize: new go.Size(28, 28),
|
||||
fill: radBrush, strokeWidth: 0, stroke: null
|
||||
}), // no outline
|
||||
{
|
||||
locationSpot: go.Spot.Center,
|
||||
click: showArrowInfo, // defined below
|
||||
toolTip: // define a tooltip for each link that displays its information
|
||||
$("ToolTip",
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "", infoString).ofObject())
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
// use a special template for the center node
|
||||
myDiagram.nodeTemplateMap.add("Center",
|
||||
$(go.Node, "Spot",
|
||||
{
|
||||
selectable: false,
|
||||
isLayoutPositioned: false, // the Diagram.layout will not position this node
|
||||
locationSpot: go.Spot.Center
|
||||
},
|
||||
$(go.Shape, "Circle",
|
||||
{ fill: radBrush, strokeWidth: 0, stroke: null, desiredSize: new go.Size(200, 200) }), // no outline
|
||||
$(go.TextBlock, "Arrowheads",
|
||||
{ margin: 1, stroke: "white", font: "bold 14px sans-serif" })
|
||||
));
|
||||
|
||||
// all Links have both "toArrow" and "fromArrow" Shapes,
|
||||
// where both arrow properties are data bound
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ routing: go.Link.Normal },
|
||||
$(go.Shape, // the link shape
|
||||
// the first element is assumed to be main element: as if isPanelMain were true
|
||||
{ stroke: "gray", strokeWidth: 2 }),
|
||||
$(go.Shape, // the "from" arrowhead
|
||||
new go.Binding("fromArrow", "fromArrow"),
|
||||
{ scale: 2, fill: "#D4B52C" }),
|
||||
$(go.Shape, // the "to" arrowhead
|
||||
new go.Binding("toArrow", "toArrow"),
|
||||
{ scale: 2, fill: "#D4B52C" }),
|
||||
{
|
||||
click: showArrowInfo,
|
||||
toolTip: // define a tooltip for each link that displays its information
|
||||
$("ToolTip",
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "", infoString).ofObject())
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
// collect all of the predefined arrowhead names
|
||||
var arrowheads = go.Shape.getArrowheadGeometries().toKeySet().toArray();
|
||||
if (arrowheads.length % 2 === 1) arrowheads.push(""); // make sure there's an even number
|
||||
|
||||
// create all of the link data, two arrowheads per link
|
||||
var linkdata = [];
|
||||
var i = 0;
|
||||
for (var j = 0; j < arrowheads.length; j = j + 2) {
|
||||
linkdata.push({ from: "Center", to: i++, toArrow: arrowheads[j], fromArrow: arrowheads[j + 1] });
|
||||
}
|
||||
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{ // this gets copied automatically when there's a link data reference to a new node key
|
||||
// and is then added to the nodeDataArray
|
||||
archetypeNodeData: {},
|
||||
// the node array starts with just the special Center node
|
||||
nodeDataArray: [{ category: "Center", key: "Center" }],
|
||||
// the link array was created above
|
||||
linkDataArray: linkdata
|
||||
});
|
||||
}
|
||||
|
||||
// a conversion function used to get arrowhead information for a Part
|
||||
function infoString(obj) {
|
||||
var part = obj.part;
|
||||
if (part instanceof go.Adornment) part = part.adornedPart;
|
||||
var msg = "";
|
||||
if (part instanceof go.Link) {
|
||||
var link = part;
|
||||
msg = "toArrow: " + link.data.toArrow + ";\nfromArrow: " + link.data.fromArrow;
|
||||
} else if (part instanceof go.Node) {
|
||||
var node = part;
|
||||
var link = node.linksConnected.first();
|
||||
msg = "toArrow: " + link.data.toArrow + ";\nfromArrow: " + link.data.fromArrow;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
// a GraphObject.click event handler to show arrowhead information
|
||||
function showArrowInfo(e, obj) {
|
||||
var msg = infoString(obj);
|
||||
if (msg) {
|
||||
var status = document.getElementById("myArrowheadInfo");
|
||||
if (status) status.textContent = msg;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<!-- The DIV for the Diagram needs an explicit size or else we won't see anything.
|
||||
Also add a border to help see the edges. -->
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:600px; height:500px"></div>
|
||||
<div id="myArrowheadInfo" style="color:red"></div>
|
||||
<p>
|
||||
This sample displays all predefined GoJS arrowheads.
|
||||
Select or hover over a Node or its Link to see the names of the arrowheads on the Link.
|
||||
</p>
|
||||
<p>
|
||||
Each Link shows two arrowheads.
|
||||
The Link template has a Shape whose <a>Shape.toArrow</a> property is bound to an arrowhead name.
|
||||
A different Shape in the template has its <a>Shape.fromArrow</a> property bound to a different arrowhead name.
|
||||
Each arrowhead has been scaled up to make it more easily visible.
|
||||
</p>
|
||||
<p>
|
||||
See the definitions of all these arrowheads in the file: <a href="../extensions/Arrowheads.js" target="_blank">Arrowheads.js</a>.
|
||||
</p>
|
||||
<p>
|
||||
For predefined shape geometries, see the <a href="shapes.html">Shapes</a> sample.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bar Charts</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="GoJS nodes containing simple bar charts." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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");
|
||||
|
||||
// the template for each item in a node's array of item data
|
||||
var itemTempl =
|
||||
$(go.Panel, "TableColumn",
|
||||
$(go.Shape,
|
||||
{ row: 0, alignment: go.Spot.Bottom },
|
||||
{ fill: "slateblue", stroke: null, width: 40 },
|
||||
new go.Binding("height", "val"),
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ row: 1 },
|
||||
new go.Binding("text")),
|
||||
{
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "val"))
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape,
|
||||
{ fill: "white" }),
|
||||
$(go.Panel, "Vertical",
|
||||
$(go.Panel, "Table",
|
||||
{ margin: 6, itemTemplate: itemTempl },
|
||||
new go.Binding("itemArray", "items")),
|
||||
$(go.TextBlock,
|
||||
{ font: "bold 12pt sans-serif" },
|
||||
new go.Binding("text"))
|
||||
)
|
||||
);
|
||||
|
||||
var nodeDataArray = [
|
||||
{
|
||||
key: 1,
|
||||
text: "Before",
|
||||
items: [{ text: "first", val: 50 },
|
||||
{ text: "second", val: 70 },
|
||||
{ text: "third", val: 60 },
|
||||
{ text: "fourth", val: 80 }]
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
text: "After",
|
||||
items: [{ text: "first", val: 50 },
|
||||
{ text: "second", val: 70 },
|
||||
{ text: "third", val: 75, color: "red" },
|
||||
{ text: "fourth", val: 80 }]
|
||||
}
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: 1, to: 2 }
|
||||
];
|
||||
myDiagram.model = $(go.GraphLinksModel,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray: nodeDataArray,
|
||||
linkDataArray: linkDataArray
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
||||
<p>
|
||||
Each node contains a Table Panel whose <a>Panel.itemArray</a> is data bound to the "items" property which holds an Array of data objects.
|
||||
That Table Panel has an <a>Panel.itemTemplate</a> which creates a bar (a rectangular Shape) and a TextBlock label for each item.
|
||||
Each bar also has a tooltip showing the value.
|
||||
</p>
|
||||
<p>
|
||||
For more sophisticated charts within nodes, see the <a href="canvases.html">Canvas Charts</a> sample.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,282 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Basic GoJS Sample</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive GoJS diagram demonstrating creating new nodes and links, reconnecting links, grouping and ungrouping, and context menus and tooltips for nodes, for links, and for the diagram background." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
// allow double-click in background to create a new node
|
||||
"clickCreatingTool.archetypeNodeData": { text: "Node", color: "white" },
|
||||
|
||||
// allow Ctrl-G to call groupSelection()
|
||||
"commandHandler.archetypeGroupData": { text: "Group", isGroup: true, color: "blue" },
|
||||
|
||||
// enable undo & redo
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// Define the appearance and behavior for Nodes:
|
||||
|
||||
// First, define the shared context menu for all Nodes, Links, and Groups.
|
||||
|
||||
// To simplify this code we define a function for creating a context menu button:
|
||||
function makeButton(text, action, visiblePredicate) {
|
||||
return $("ContextMenuButton",
|
||||
$(go.TextBlock, text),
|
||||
{ click: action },
|
||||
// don't bother with binding GraphObject.visible if there's no predicate
|
||||
visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.diagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
|
||||
}
|
||||
|
||||
// a context menu is an Adornment with a bunch of buttons in them
|
||||
var partContextMenu =
|
||||
$("ContextMenu",
|
||||
makeButton("Properties",
|
||||
function(e, obj) { // OBJ is this Button
|
||||
var contextmenu = obj.part; // the Button is in the context menu Adornment
|
||||
var part = contextmenu.adornedPart; // the adornedPart is the Part that the context menu adorns
|
||||
// now can do something with PART, or with its data, or with the Adornment (the context menu)
|
||||
if (part instanceof go.Link) alert(linkInfo(part.data));
|
||||
else if (part instanceof go.Group) alert(groupInfo(contextmenu));
|
||||
else alert(nodeInfo(part.data));
|
||||
}),
|
||||
makeButton("Cut",
|
||||
function(e, obj) { e.diagram.commandHandler.cutSelection(); },
|
||||
function(o) { return o.diagram.commandHandler.canCutSelection(); }),
|
||||
makeButton("Copy",
|
||||
function(e, obj) { e.diagram.commandHandler.copySelection(); },
|
||||
function(o) { return o.diagram.commandHandler.canCopySelection(); }),
|
||||
makeButton("Paste",
|
||||
function(e, obj) { e.diagram.commandHandler.pasteSelection(e.diagram.toolManager.contextMenuTool.mouseDownPoint); },
|
||||
function(o) { return o.diagram.commandHandler.canPasteSelection(o.diagram.toolManager.contextMenuTool.mouseDownPoint); }),
|
||||
makeButton("Delete",
|
||||
function(e, obj) { e.diagram.commandHandler.deleteSelection(); },
|
||||
function(o) { return o.diagram.commandHandler.canDeleteSelection(); }),
|
||||
makeButton("Undo",
|
||||
function(e, obj) { e.diagram.commandHandler.undo(); },
|
||||
function(o) { return o.diagram.commandHandler.canUndo(); }),
|
||||
makeButton("Redo",
|
||||
function(e, obj) { e.diagram.commandHandler.redo(); },
|
||||
function(o) { return o.diagram.commandHandler.canRedo(); }),
|
||||
makeButton("Group",
|
||||
function(e, obj) { e.diagram.commandHandler.groupSelection(); },
|
||||
function(o) { return o.diagram.commandHandler.canGroupSelection(); }),
|
||||
makeButton("Ungroup",
|
||||
function(e, obj) { e.diagram.commandHandler.ungroupSelection(); },
|
||||
function(o) { return o.diagram.commandHandler.canUngroupSelection(); })
|
||||
);
|
||||
|
||||
function nodeInfo(d) { // Tooltip info for a node data object
|
||||
var str = "Node " + d.key + ": " + d.text + "\n";
|
||||
if (d.group)
|
||||
str += "member of " + d.group;
|
||||
else
|
||||
str += "top-level node";
|
||||
return str;
|
||||
}
|
||||
|
||||
// These nodes have text surrounded by a rounded rectangle
|
||||
// whose fill color is bound to the node data.
|
||||
// The user can drag a node by dragging its TextBlock label.
|
||||
// Dragging from the Shape will start drawing a new link.
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
{
|
||||
fill: "white", // the default fill, if there is no data bound value
|
||||
portId: "", cursor: "pointer", // the Shape is the port, not the whole Node
|
||||
// allow all kinds of links from and to this port
|
||||
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
|
||||
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true
|
||||
},
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "bold 14px sans-serif",
|
||||
stroke: '#333',
|
||||
margin: 6, // make some extra space for the shape around the text
|
||||
isMultiline: false, // don't allow newlines in text
|
||||
editable: true // allow in-place editing by user
|
||||
},
|
||||
new go.Binding("text", "text").makeTwoWay()), // the label shows the node data's text
|
||||
{ // this tooltip Adornment is shared by all nodes
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
$(go.TextBlock, { margin: 4 }, // the tooltip shows the result of calling nodeInfo(data)
|
||||
new go.Binding("text", "", nodeInfo))
|
||||
),
|
||||
// this context menu Adornment is shared by all nodes
|
||||
contextMenu: partContextMenu
|
||||
}
|
||||
);
|
||||
|
||||
// Define the appearance and behavior for Links:
|
||||
|
||||
function linkInfo(d) { // Tooltip info for a link data object
|
||||
return "Link:\nfrom " + d.from + " to " + d.to;
|
||||
}
|
||||
|
||||
// The link shape and arrowhead have their stroke brush data bound to the "color" property
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ toShortLength: 3, relinkableFrom: true, relinkableTo: true }, // allow the user to relink existing links
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 2 },
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Shape,
|
||||
{ toArrow: "Standard", stroke: null },
|
||||
new go.Binding("fill", "color")),
|
||||
{ // this tooltip Adornment is shared by all links
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
$(go.TextBlock, { margin: 4 }, // the tooltip shows the result of calling linkInfo(data)
|
||||
new go.Binding("text", "", linkInfo))
|
||||
),
|
||||
// the same context menu Adornment is shared by all links
|
||||
contextMenu: partContextMenu
|
||||
}
|
||||
);
|
||||
|
||||
// Define the appearance and behavior for Groups:
|
||||
|
||||
function groupInfo(adornment) { // takes the tooltip or context menu, not a group node data object
|
||||
var g = adornment.adornedPart; // get the Group that the tooltip adorns
|
||||
var mems = g.memberParts.count;
|
||||
var links = 0;
|
||||
g.memberParts.each(function(part) {
|
||||
if (part instanceof go.Link) links++;
|
||||
});
|
||||
return "Group " + g.data.key + ": " + g.data.text + "\n" + mems + " members including " + links + " links";
|
||||
}
|
||||
|
||||
// Groups consist of a title in the color given by the group node data
|
||||
// above a translucent gray rectangle surrounding the member parts
|
||||
myDiagram.groupTemplate =
|
||||
$(go.Group, "Vertical",
|
||||
{
|
||||
selectionObjectName: "PANEL", // selection handle goes around shape, not label
|
||||
ungroupable: true // enable Ctrl-Shift-G to ungroup a selected Group
|
||||
},
|
||||
$(go.TextBlock,
|
||||
{
|
||||
//alignment: go.Spot.Right,
|
||||
font: "bold 19px sans-serif",
|
||||
isMultiline: false, // don't allow newlines in text
|
||||
editable: true // allow in-place editing by user
|
||||
},
|
||||
new go.Binding("text", "text").makeTwoWay(),
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "PANEL" },
|
||||
$(go.Shape, "Rectangle", // the rectangular shape around the members
|
||||
{
|
||||
fill: "rgba(128,128,128,0.2)", stroke: "gray", strokeWidth: 3,
|
||||
portId: "", cursor: "pointer", // the Shape is the port, not the whole Node
|
||||
// allow all kinds of links from and to this port
|
||||
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
|
||||
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true
|
||||
}),
|
||||
$(go.Placeholder, { margin: 10, background: "transparent" }) // represents where the members are
|
||||
),
|
||||
{ // this tooltip Adornment is shared by all groups
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
// bind to tooltip, not to Group.data, to allow access to Group properties
|
||||
new go.Binding("text", "", groupInfo).ofObject())
|
||||
),
|
||||
// the same context menu Adornment is shared by all groups
|
||||
contextMenu: partContextMenu
|
||||
}
|
||||
);
|
||||
|
||||
// Define the behavior for the Diagram background:
|
||||
|
||||
function diagramInfo(model) { // Tooltip info for the diagram's model
|
||||
return "Model:\n" + model.nodeDataArray.length + " nodes, " + model.linkDataArray.length + " links";
|
||||
}
|
||||
|
||||
// provide a tooltip for the background of the Diagram, when not over any Part
|
||||
myDiagram.toolTip =
|
||||
$("ToolTip",
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "", diagramInfo))
|
||||
);
|
||||
|
||||
// provide a context menu for the background of the Diagram, when not over any Part
|
||||
myDiagram.contextMenu =
|
||||
$("ContextMenu",
|
||||
makeButton("Paste",
|
||||
function(e, obj) { e.diagram.commandHandler.pasteSelection(e.diagram.toolManager.contextMenuTool.mouseDownPoint); },
|
||||
function(o) { return o.diagram.commandHandler.canPasteSelection(o.diagram.toolManager.contextMenuTool.mouseDownPoint); }),
|
||||
makeButton("Undo",
|
||||
function(e, obj) { e.diagram.commandHandler.undo(); },
|
||||
function(o) { return o.diagram.commandHandler.canUndo(); }),
|
||||
makeButton("Redo",
|
||||
function(e, obj) { e.diagram.commandHandler.redo(); },
|
||||
function(o) { return o.diagram.commandHandler.canRedo(); })
|
||||
);
|
||||
|
||||
// Create the Diagram's Model:
|
||||
var nodeDataArray = [
|
||||
{ key: 1, text: "Alpha", color: "lightblue" },
|
||||
{ key: 2, text: "Beta", color: "orange" },
|
||||
{ key: 3, text: "Gamma", color: "lightgreen", group: 5 },
|
||||
{ key: 4, text: "Delta", color: "pink", group: 5 },
|
||||
{ key: 5, text: "Epsilon", color: "green", isGroup: true }
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: 1, to: 2, color: "blue" },
|
||||
{ from: 2, to: 2 },
|
||||
{ from: 3, to: 4, color: "green" },
|
||||
{ from: 3, to: 1, color: "purple" }
|
||||
];
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
<p>
|
||||
This sample demonstrates tooltips and context menus for all parts and for the diagram background,
|
||||
as well as several other powerful diagram editing abilities.
|
||||
</p>
|
||||
<p>
|
||||
Unlike the <a href="minimal.html">Minimal</a> sample, this sample has templates for Links and for Groups,
|
||||
plus tooltips and context menus for Nodes, for Links, for Groups, and for the Diagram.
|
||||
</p>
|
||||
<p>This sample has all of the functionality of the Minimal sample, but additionally allows the user to:</p>
|
||||
<ul>
|
||||
<li>create new nodes: double-click in the background of the diagram</li>
|
||||
<li>edit text: select the node and then click on the text, or select the node and press F2</li>
|
||||
<li>draw new links: drag from the inner edge of the node's or the group's shape</li>
|
||||
<li>reconnect existing links: select the link and then drag the diamond-shaped handle at either end of the link</li>
|
||||
<li>group nodes and links: select some nodes and links and then type Ctrl-G (or invoke via context menu)</li>
|
||||
<li>ungroup an existing group: select a group and then type Ctrl-Shift-G (or invoke via context menu)</li>
|
||||
</ul>
|
||||
<p>
|
||||
GoJS contains many other possible commands, which can be invoked by either mouse/keyboard/touch or programatically.
|
||||
<a href="../intro/commands.html">See an overview of possible commands here.</a>
|
||||
On a Mac, use CMD instead of Ctrl.
|
||||
</p>
|
||||
<p>
|
||||
On touch devices, hold your finger stationary to bring up a context menu.
|
||||
The default context menu supports most of the standard commands that are enabled at that time for that object.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Beat Paths</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A precedence diagram showing a hierarchical relationship between nodes, using archetypeNodeData, LayeredDigraphLayout, and Bezier curved links." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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
|
||||
|
||||
// Must name or refer to the DIV HTML element
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{ // automatically scale the diagram to fit the viewport's size
|
||||
initialAutoScale: go.Diagram.Uniform,
|
||||
// disable user copying of parts
|
||||
allowCopy: false,
|
||||
// position all of the nodes and route all of the links
|
||||
layout:
|
||||
$(go.LayeredDigraphLayout,
|
||||
{
|
||||
direction: 90,
|
||||
layerSpacing: 10,
|
||||
columnSpacing: 15,
|
||||
setsPortSpots: false
|
||||
})
|
||||
});
|
||||
|
||||
// replace the default Node template in the nodeTemplateMap
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Vertical", // the whole node panel
|
||||
$(go.TextBlock, // the text label
|
||||
new go.Binding("text", "key")),
|
||||
$(go.Picture, // the icon showing the logo
|
||||
// You should set the desiredSize (or width and height)
|
||||
// whenever you know what size the Picture should be.
|
||||
{ desiredSize: new go.Size(75, 50) },
|
||||
new go.Binding("source", "key", convertKeyImage))
|
||||
);
|
||||
|
||||
// replace the default Link template in the linkTemplateMap
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ curve: go.Link.Bezier, toShortLength: 2 },
|
||||
$(go.Shape, // the link shape
|
||||
{ strokeWidth: 1.5 }),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: "Standard", stroke: null })
|
||||
);
|
||||
|
||||
// the array of link data objects: the relationships between the nodes
|
||||
var linkDataArray = [
|
||||
{ from: "CAR", to: "ARI" },
|
||||
{ from: "ARI", to: "CIN" },
|
||||
{ from: "ARI", to: "GB" },
|
||||
{ from: "DEN", to: "GB" },
|
||||
{ from: "DEN", to: "CIN" },
|
||||
{ from: "DEN", to: "NE" },
|
||||
{ from: "GB", to: "WAS" },
|
||||
{ from: "WAS", to: "STL" },
|
||||
{ from: "CIN", to: "STL" },
|
||||
{ from: "STL", to: "SEA" },
|
||||
{ from: "SEA", to: "SF" },
|
||||
{ from: "SEA", to: "MIN" },
|
||||
{ from: "NE", to: "NYG" },
|
||||
{ from: "NE", to: "KC" },
|
||||
{ from: "MIN", to: "DET" },
|
||||
{ from: "MIN", to: "KC" },
|
||||
{ from: "KC", to: "HOU" },
|
||||
{ from: "KC", to: "BUF" },
|
||||
{ from: "KC", to: "BAL" },
|
||||
{ from: "KC", to: "OAK" },
|
||||
{ from: "BUF", to: "NYJ" },
|
||||
{ from: "BAL", to: "PIT" },
|
||||
{ from: "DET", to: "NO" },
|
||||
{ from: "DET", to: "PHI" },
|
||||
{ from: "DET", to: "CHI" },
|
||||
{ from: "HOU", to: "JAC" },
|
||||
{ from: "HOU", to: "TEN" },
|
||||
{ from: "PIT", to: "IND" },
|
||||
{ from: "PIT", to: "SD" },
|
||||
{ from: "OAK", to: "NYJ" },
|
||||
{ from: "OAK", to: "SD" },
|
||||
{ from: "NO", to: "ATL" },
|
||||
{ from: "NO", to: "NYG" },
|
||||
{ from: "PHI", to: "NYG" },
|
||||
{ from: "CHI", to: "TB" },
|
||||
{ from: "NYJ", to: "IND" },
|
||||
{ from: "NYJ", to: "CLE" },
|
||||
{ from: "IND", to: "TB" },
|
||||
{ from: "TB", to: "ATL" },
|
||||
{ from: "SD", to: "CLE" },
|
||||
{ from: "ATL", to: "DAL" },
|
||||
{ from: "ATL", to: "JAC" },
|
||||
{ from: "CLE", to: "TEN" },
|
||||
{ from: "DAL", to: "MIA" },
|
||||
{ from: "MIA", to: "TEN" }
|
||||
];
|
||||
|
||||
// create the model and assign it to the Diagram
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{ // automatically create node data objects for each "from" or "to" reference
|
||||
// (set this property before setting the linkDataArray)
|
||||
archetypeNodeData: {},
|
||||
// process all of the link relationship data
|
||||
linkDataArray: linkDataArray
|
||||
});
|
||||
}
|
||||
|
||||
function convertKeyImage(key) {
|
||||
if (!key) key = "NE";
|
||||
return "https://www.nwoods.com/go/beatpaths/" + key + "_logo-75x50.png";
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; margin: 10px; height: 700px"></div>
|
||||
<p>
|
||||
This sample demonstrates reading JSON data describing the relative rankings of NFL teams
|
||||
during the 2015 season and generating a diagram from that data.
|
||||
The ranking information came from beatgraphs.com.
|
||||
</p>
|
||||
<p>
|
||||
The JSON data is basically just a list of relationships.
|
||||
Unlike most model data, there are no elements describing the nodes --
|
||||
the node definitions are implicit in the references from the links.
|
||||
Hence the <a>Diagram.model</a> has <a>GraphLinksModel.archetypeNodeData</a> set to a JavaScript object.
|
||||
</p>
|
||||
<p>
|
||||
The node template uses the <b>convertKeyImage</b> function to convert the team name
|
||||
into a URI referring to an image on our web site.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,332 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Belts</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Belts & gears: chains, pulleys, tensioners, conveyor belts, rollers." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"InitialLayoutCompleted": function(e) {
|
||||
updateBelts(); // changes the bounds of the "Belt"s
|
||||
e.diagram.alignDocument(go.Spot.Center, go.Spot.Center);
|
||||
},
|
||||
"animationManager.isEnabled": false,
|
||||
"undoManager.isEnabled": true,
|
||||
allowCopy: false,
|
||||
allowDelete: false,
|
||||
"draggingTool.moveParts": function(parts, offset, check) {
|
||||
go.DraggingTool.prototype.moveParts.call(this, parts, offset, check);
|
||||
updateBelts(); // this is inefficient if there are a lot of belts
|
||||
}
|
||||
});
|
||||
|
||||
function commonStyle() {
|
||||
return [
|
||||
new go.Binding("location", "xy", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{
|
||||
locationSpot: go.Spot.Center, locationObjectName: "GUIDE",
|
||||
selectionAdorned: false,
|
||||
dragComputation: function(node, newloc, snaploc) {
|
||||
// don't allow rollers or drums to overlap each other
|
||||
var oldloc = node.location;
|
||||
var noderad = node.findObject("GUIDE").actualBounds.width/2;
|
||||
var ok = true;
|
||||
var it = node.diagram.nodes.iterator;
|
||||
while (it.next()) {
|
||||
var n = it.value;
|
||||
if (n === node || n.category === "Belt") continue;
|
||||
var dist2 = newloc.distanceSquaredPoint(n.location);
|
||||
var rad = n.findObject("GUIDE").actualBounds.width/2;
|
||||
if (dist2 < (noderad+rad)*(noderad+rad)) { ok = false; break; }
|
||||
}
|
||||
return ok ? newloc : oldloc;
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Roller",
|
||||
$(go.Node, "Spot", commonStyle(),
|
||||
$(go.Shape, "Circle",
|
||||
{ name: "GUIDE", fill: "lightgray", strokeWidth: 0, width: 20, height: 20 },
|
||||
new go.Binding("width", "diameter").makeTwoWay(),
|
||||
new go.Binding("height", "diameter")),
|
||||
$(go.TextBlock,
|
||||
{ font: "6pt sans-serif", stroke: "black" },
|
||||
new go.Binding("text", "key"))
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Drum",
|
||||
$(go.Node, "Spot", commonStyle(),
|
||||
$(go.Shape, "Circle",
|
||||
{ name: "GUIDE", fill: "lightgray", stroke: "gray", width: 80, height: 80 },
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ font: "6pt sans-serif", stroke: "darkblue" },
|
||||
new go.Binding("text", "key"))
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Belt",
|
||||
$(go.Node,
|
||||
{ selectionAdorned: false, layerName: "Foreground", copyable: false, movable: false },
|
||||
$(go.Shape,
|
||||
{ name: "BELT", fill: null, stroke: "gray", strokeWidth: 2, strokeDashArray: [4, 2] },
|
||||
new go.Binding("stroke", "color"))
|
||||
));
|
||||
|
||||
load();
|
||||
} // end init
|
||||
|
||||
function updateBelts(coll) {
|
||||
if (!coll) coll = myDiagram.nodes;
|
||||
myDiagram.startTransaction();
|
||||
coll.each(updateBelt);
|
||||
myDiagram.commitTransaction("updated belts");
|
||||
}
|
||||
|
||||
function updateBelt(node) {
|
||||
if (node.category !== "Belt") return;
|
||||
var belt = node.findObject("BELT");
|
||||
var diagram = node.diagram;
|
||||
var guideinfos = node.data.guides;
|
||||
if (!Array.isArray(guideinfos)) throw new Error("data.guides is not an Array for Belt node: " + node.data.key);
|
||||
|
||||
// gather basic information about each guide node
|
||||
var guides = []; // holds Objects with handy information
|
||||
for (var i = 0; i < guideinfos.length; i++) {
|
||||
var info = guideinfos[i];
|
||||
var guidenode = diagram.findNodeForKey(info.k);
|
||||
if (guidenode !== null && guidenode.location.isReal()) {
|
||||
var loc = guidenode.location;
|
||||
var cyl = guidenode.findObject("GUIDE");
|
||||
var radius = (cyl !== null) ? cyl.measuredBounds.width / 2 : 10;
|
||||
if (guides.length > 0) {
|
||||
var prevguide = guides[guides.length - 1];
|
||||
var prevloc = prevguide.location;
|
||||
var prevradius = prevguide.radius;
|
||||
var dist = Math.sqrt(prevloc.distanceSquaredPoint(loc));
|
||||
if (dist < Math.abs(prevradius-radius)) { // one is completely inside the other
|
||||
if (prevradius > radius) {
|
||||
continue; // skip this smaller guide
|
||||
} else {
|
||||
guides.pop(); // skip the previous guide, which was smaller
|
||||
}
|
||||
}
|
||||
}
|
||||
guides.push({
|
||||
node: guidenode,
|
||||
location: guidenode.location.copy(),
|
||||
radius: radius + belt.strokeWidth/2,
|
||||
outside: !!info.outside,
|
||||
from: null, // these Points will be computed by computeContacts
|
||||
to: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// handle some degenerate cases
|
||||
if (guides.length < 2) {
|
||||
if (guides.length === 1) {
|
||||
node.location = guides[0].location;
|
||||
}
|
||||
if (belt !== null) {
|
||||
belt.geometry = new go.Geometry(go.Geometry.Ellipse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// compute the contact points
|
||||
// assume guides are listed in clockwise order
|
||||
for (var i = 0; i < guides.length; i++) {
|
||||
var guide = guides[i];
|
||||
var next = guides[(i + 1) % guides.length];
|
||||
computeContacts(guide, next);
|
||||
}
|
||||
|
||||
// skip any guides that should not contact the Belt, because they're cannot touch the path
|
||||
var i = 0;
|
||||
while (guides.length > 2 && i < guides.length) {
|
||||
var guide = guides[i];
|
||||
var next = guides[(i + 1) % guides.length];
|
||||
var follow = guides[(i + 2) % guides.length];
|
||||
// is NEXT on the wrong side of the line from GUIDE to FOLLOW?
|
||||
var wrongside = comparePointWithLine(guide.from.x, guide.from.y, follow.to.x, follow.to.y, next.to.x, next.to.y) < 0;
|
||||
if (next.outside) wrongside = !wrongside;
|
||||
if (wrongside) {
|
||||
computeContacts(guide, follow);
|
||||
// get rid of NEXT
|
||||
if (i + 1 < guides.length) {
|
||||
guides.splice(i + 1, 1);
|
||||
} else {
|
||||
guides.splice(0, 1);
|
||||
}
|
||||
// now also need to check whether GUIDE has become on the wrong side!
|
||||
if (i > 0) i--;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// construct the Geometry for the belt Shape
|
||||
var geo = new go.Geometry();
|
||||
var fig = null;
|
||||
for (var i = 0; i < guides.length; i++) {
|
||||
var guide = guides[i];
|
||||
var next = guides[(i + 1) % guides.length];
|
||||
if (fig === null) {
|
||||
fig = new go.PathFigure(guide.from.x, guide.from.y, true);
|
||||
geo.add(fig);
|
||||
}
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, next.to.x, next.to.y));
|
||||
var startang = next.location.directionPoint(next.to);
|
||||
var endang = next.location.directionPoint(next.from);
|
||||
var sweep = (endang > startang) ? endang-startang : (360 - startang) + endang;
|
||||
if (next.outside) { // go counter-clockwise
|
||||
fig.add(new go.PathSegment(go.PathSegment.Arc, startang, sweep - 360, next.location.x, next.location.y, next.radius, next.radius));
|
||||
} else { // positive sweep angle
|
||||
fig.add(new go.PathSegment(go.PathSegment.Arc, startang, sweep, next.location.x, next.location.y, next.radius, next.radius));
|
||||
}
|
||||
}
|
||||
|
||||
// update the Belt's Shape.geometry
|
||||
if (belt !== null) {
|
||||
var pos = geo.normalize();
|
||||
belt.geometry = geo;
|
||||
// account for the thickness of the belt shape's stroke
|
||||
node.position = new go.Point(-pos.x - belt.strokeWidth / 2, -pos.y - belt.strokeWidth / 2);
|
||||
node.ensureBounds();
|
||||
}
|
||||
} // end updateBelt
|
||||
|
||||
function comparePointWithLine(a1x, a1y, a2x, a2y, p1x, p1y) {
|
||||
var x2 = a2x - a1x;
|
||||
var y2 = a2y - a1y;
|
||||
var px = p1x - a1x;
|
||||
var py = p1y - a1y;
|
||||
var ccw = px * y2 - py * x2;
|
||||
if (ccw === 0) {
|
||||
ccw = px * x2 + py * y2;
|
||||
if (ccw > 0) {
|
||||
px -= x2;
|
||||
py -= y2;
|
||||
ccw = px * x2 + py * y2;
|
||||
if (ccw < 0) ccw = 0;
|
||||
}
|
||||
}
|
||||
return (ccw < 0) ? -1 : ((ccw > 0) ? 1 : 0);
|
||||
}
|
||||
|
||||
function computeContacts(guideA, guideB) {
|
||||
var locA = guideA.location;
|
||||
var x1 = locA.x;
|
||||
var y1 = locA.y;
|
||||
var r1 = guideA.radius;
|
||||
var locB = guideB.location;
|
||||
var x2 = locB.x;
|
||||
var y2 = locB.y;
|
||||
var r2 = guideB.radius;
|
||||
|
||||
// this assumes that belts only go clockwise
|
||||
var g = Math.atan2(y2 - y1, x2 - x1);
|
||||
var d = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
|
||||
|
||||
var bb = ((guideA.outside === guideB.outside) ? (r2 - r1) : (r2 + r1)) / d;
|
||||
if (bb < -1) bb = -1; else if (bb > 1) bb = 1;
|
||||
var b = Math.asin(bb);
|
||||
|
||||
if (guideB.outside) {
|
||||
if (guideA.outside) { // both outside
|
||||
var a = Math.PI / 2 - b - g;
|
||||
var cosa = Math.cos(a);
|
||||
var sina = Math.sin(a);
|
||||
guideA.from = new go.Point(x1 - r1 * cosa, y1 + r1 * sina);
|
||||
guideB.to = new go.Point(x2 - r2 * cosa, y2 + r2 * sina);
|
||||
} else { // inside A, outside B
|
||||
var a = Math.PI / 2 - Math.abs(b) - g;
|
||||
var cosa = Math.cos(a);
|
||||
var sina = Math.sin(a);
|
||||
guideA.from = new go.Point(x1 + r1 * cosa, y1 - r1 * sina);
|
||||
guideB.to = new go.Point(x2 - r2 * cosa, y2 + r2 * sina);
|
||||
}
|
||||
} else {
|
||||
if (guideA.outside) { // outside A, inside B
|
||||
var a = Math.abs(b) - Math.PI / 2 - g;
|
||||
var cosa = Math.cos(a);
|
||||
var sina = Math.sin(a);
|
||||
guideA.from = new go.Point(x1 + r1 * cosa, y1 - r1 * sina);
|
||||
guideB.to = new go.Point(x2 - r2 * cosa, y2 + r2 * sina);
|
||||
} else { // both inside
|
||||
var a = Math.PI / 2 + b - g;
|
||||
var cosa = Math.cos(a);
|
||||
var sina = Math.sin(a);
|
||||
guideA.from = new go.Point(x1 + r1 * cosa, y1 - r1 * sina);
|
||||
guideB.to = new go.Point(x2 + r2 * cosa, y2 - r2 * sina);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the diagram's model in JSON format that the user may edit
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
|
||||
var beltAnimation = null;
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
|
||||
// Animate the flow in the pipes
|
||||
if (beltAnimation) beltAnimation.stop();
|
||||
var animation = new go.Animation();
|
||||
animation.easing = go.Animation.EaseLinear;
|
||||
myDiagram.nodes.each(function(node) {
|
||||
if (node.category !== "Belt") return;
|
||||
animation.add(node.findObject("BELT"), "strokeDashOffset", 36, 0)
|
||||
});
|
||||
// Run indefinitely
|
||||
animation.runCount = Infinity;
|
||||
animation.start();
|
||||
beltAnimation = animation;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"nodeDataArray": [
|
||||
{"key":"P111", "category":"Roller", "xy":"450 610"},
|
||||
{"key":"P112", "category":"Roller", "xy":"400 660"},
|
||||
{"key":"P113", "category":"Roller", "xy":"350 725"},
|
||||
{"key":"P114", "category":"Roller", "xy":"305 800"},
|
||||
{"key":"P115", "category":"Roller", "xy":"280 705"},
|
||||
{"key":"P116", "category":"Roller", "xy":"200 720"},
|
||||
{"key":"P117", "category":"Roller", "xy":"200 620"},
|
||||
{"key":"D1", "category":"Drum", "xy":"300 540"},
|
||||
{"key":"D2", "category":"Drum", "xy":"300 622"},
|
||||
{"key":"B1", "category":"Belt", "color":"blue",
|
||||
"guides":[ {"k":"D2"},{"k":"P111"},{"k":"P112", "outside":true},{"k":"P113", "outside":true},{"k":"P114"},{"k":"P115", "outside":true},{"k":"P116"},{"k":"P117"} ]},
|
||||
{"key":"P211", "category":"Roller", "xy":"100 750"},
|
||||
{"key":"P212", "category":"Roller", "xy":"150 800"},
|
||||
{"key":"B2", "category":"Belt", "color":"green",
|
||||
"guides":[ {"k":"P211"},{"k":"P116"},{"k":"P212"} ]}
|
||||
],
|
||||
"linkDataArray": []}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,589 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Simple Block Editor</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A simple block diagram editor that includes context menus for changing shapes and colors." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<script src="../extensions/DrawCommandHandler.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
padding: 20, // extra space when scrolled all the way
|
||||
grid: $(go.Panel, "Grid", // a simple 10x10 grid
|
||||
$(go.Shape, "LineH", { stroke: "lightgray", strokeWidth: 0.5 }),
|
||||
$(go.Shape, "LineV", { stroke: "lightgray", strokeWidth: 0.5 })
|
||||
),
|
||||
"draggingTool.isGridSnapEnabled": true,
|
||||
handlesDragDropForTopLevelParts: true,
|
||||
mouseDrop: function(e) {
|
||||
// when the selection is dropped in the diagram's background,
|
||||
// make sure the selected Parts no longer belong to any Group
|
||||
var ok = e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true);
|
||||
if (!ok) e.diagram.currentTool.doCancel();
|
||||
},
|
||||
commandHandler: $(DrawCommandHandler), // support offset copy-and-paste
|
||||
"clickCreatingTool.archetypeNodeData": { text: "NEW NODE" }, // create a new node by double-clicking in background
|
||||
"PartCreated": function(e) {
|
||||
var node = e.subject; // the newly inserted Node -- now need to snap its location to the grid
|
||||
node.location = node.location.copy().snapToGridPoint(e.diagram.grid.gridOrigin, e.diagram.grid.gridCellSize);
|
||||
setTimeout(function() { // and have the user start editing its text
|
||||
e.diagram.commandHandler.editTextBlock();
|
||||
}, 20);
|
||||
},
|
||||
"commandHandler.archetypeGroupData": { isGroup: true, text: "NEW GROUP" },
|
||||
"SelectionGrouped": function(e) {
|
||||
var group = e.subject;
|
||||
setTimeout(function() { // and have the user start editing its text
|
||||
e.diagram.commandHandler.editTextBlock();
|
||||
})
|
||||
},
|
||||
"LinkRelinked": function(e) {
|
||||
// re-spread the connections of other links connected with both old and new nodes
|
||||
var oldnode = e.parameter.part;
|
||||
oldnode.invalidateConnectedLinks();
|
||||
var link = e.subject;
|
||||
if (e.diagram.toolManager.linkingTool.isForwards) {
|
||||
link.toNode.invalidateConnectedLinks();
|
||||
} else {
|
||||
link.fromNode.invalidateConnectedLinks();
|
||||
}
|
||||
},
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
|
||||
// Node template
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{
|
||||
locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
|
||||
desiredSize: new go.Size(120, 60), minSize: new go.Size(40, 40),
|
||||
resizable: true, resizeCellSize: new go.Size(20, 20)
|
||||
},
|
||||
// these Bindings are TwoWay because the DraggingTool and ResizingTool modify the target properties
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
|
||||
$(go.Shape,
|
||||
{ // the border
|
||||
name: "SHAPE", fill: "white",
|
||||
portId: "", cursor: "pointer",
|
||||
fromLinkable: true, toLinkable: true,
|
||||
fromLinkableDuplicates: true, toLinkableDuplicates: true,
|
||||
fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides
|
||||
},
|
||||
new go.Binding("figure"),
|
||||
new go.Binding("fill"),
|
||||
new go.Binding("stroke", "color"),
|
||||
new go.Binding("strokeWidth", "thickness"),
|
||||
new go.Binding("strokeDashArray", "dash")),
|
||||
// this Shape prevents mouse events from reaching the middle of the port
|
||||
$(go.Shape, { width: 100, height: 40, strokeWidth: 0, fill: "transparent" }),
|
||||
$(go.TextBlock,
|
||||
{ margin: 1, textAlign: "center", overflow: go.TextBlock.OverflowEllipsis, editable: true },
|
||||
// this Binding is TwoWay due to the user editing the text with the TextEditingTool
|
||||
new go.Binding("text").makeTwoWay(),
|
||||
new go.Binding("stroke", "color"))
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate.toolTip =
|
||||
$("ToolTip", // show some detailed information
|
||||
$(go.Panel, "Vertical",
|
||||
{ maxSize: new go.Size(200, NaN) }, // limit width but not height
|
||||
$(go.TextBlock,
|
||||
{ font: "bold 10pt sans-serif", textAlign: "center" },
|
||||
new go.Binding("text")),
|
||||
$(go.TextBlock,
|
||||
{ font: "10pt sans-serif", textAlign: "center" },
|
||||
new go.Binding("text", "details"))
|
||||
)
|
||||
);
|
||||
|
||||
// Node selection adornment
|
||||
// Include four large triangular buttons so that the user can easily make a copy
|
||||
// of the node, move it to be in that direction relative to the original node,
|
||||
// and add a link to the new node.
|
||||
|
||||
function makeArrowButton(spot, fig) {
|
||||
var maker = function(e, shape) {
|
||||
e.handled = true;
|
||||
e.diagram.model.commit(function(m) {
|
||||
var selnode = shape.part.adornedPart;
|
||||
// create a new node in the direction of the spot
|
||||
var p = new go.Point().setRectSpot(selnode.actualBounds, spot);
|
||||
p.subtract(selnode.location);
|
||||
p.scale(2, 2);
|
||||
p.x += Math.sign(p.x) * 60;
|
||||
p.y += Math.sign(p.y) * 60;
|
||||
p.add(selnode.location);
|
||||
p.snapToGridPoint(e.diagram.grid.gridOrigin, e.diagram.grid.gridCellSize);
|
||||
// make the new node a copy of the selected node
|
||||
var nodedata = m.copyNodeData(selnode.data);
|
||||
// add to same group as selected node
|
||||
m.setGroupKeyForNodeData(nodedata, m.getGroupKeyForNodeData(selnode.data));
|
||||
m.addNodeData(nodedata); // add to model
|
||||
// create a link from the selected node to the new node
|
||||
var linkdata = { from: selnode.key, to: m.getKeyForNodeData(nodedata) };
|
||||
m.addLinkData(linkdata); // add to model
|
||||
// move the new node to the computed location, select it, and start to edit it
|
||||
var newnode = e.diagram.findNodeForData(nodedata);
|
||||
newnode.location = p;
|
||||
e.diagram.select(newnode);
|
||||
setTimeout(function() {
|
||||
e.diagram.commandHandler.editTextBlock();
|
||||
}, 20);
|
||||
});
|
||||
};
|
||||
return $(go.Shape,
|
||||
{
|
||||
figure: fig,
|
||||
alignment: spot, alignmentFocus: spot.opposite(),
|
||||
width: (spot.equals(go.Spot.Top) || spot.equals(go.Spot.Bottom)) ? 36 : 18,
|
||||
height: (spot.equals(go.Spot.Top) || spot.equals(go.Spot.Bottom)) ? 18 : 36,
|
||||
fill: "orange", strokeWidth: 0,
|
||||
isActionable: true, // needed because it's in an Adornment
|
||||
click: maker, contextClick: maker
|
||||
});
|
||||
}
|
||||
|
||||
// create a button that brings up the context menu
|
||||
function CMButton(options) {
|
||||
return $(go.Shape,
|
||||
{
|
||||
fill: "orange", stroke: "gray", background: "transparent",
|
||||
geometryString: "F1 M0 0 M0 4h4v4h-4z M6 4h4v4h-4z M12 4h4v4h-4z M0 12",
|
||||
isActionable: true, cursor: "context-menu",
|
||||
click: function(e, shape) {
|
||||
e.diagram.commandHandler.showContextMenu(shape.part.adornedPart);
|
||||
}
|
||||
},
|
||||
options || {});
|
||||
}
|
||||
|
||||
myDiagram.nodeTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, "Spot",
|
||||
$(go.Placeholder, { padding: 10 }),
|
||||
makeArrowButton(go.Spot.Top, "TriangleUp"),
|
||||
makeArrowButton(go.Spot.Left, "TriangleLeft"),
|
||||
makeArrowButton(go.Spot.Right, "TriangleRight"),
|
||||
makeArrowButton(go.Spot.Bottom, "TriangleDown"),
|
||||
CMButton({ alignment: new go.Spot(0.75, 0) })
|
||||
);
|
||||
|
||||
// Common context menu button definitions
|
||||
|
||||
// All buttons in context menu work on both click and contextClick,
|
||||
// in case the user context-clicks on the button.
|
||||
// All buttons modify the node data, not the Node, so the Bindings need not be TwoWay.
|
||||
|
||||
// A button-defining helper function that returns a click event handler.
|
||||
// PROPNAME is the name of the data property that should be set to the given VALUE.
|
||||
function ClickFunction(propname, value) {
|
||||
return function(e, obj) {
|
||||
e.handled = true; // don't let the click bubble up
|
||||
e.diagram.model.commit(function(m) {
|
||||
m.set(obj.part.adornedPart.data, propname, value);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Create a context menu button for setting a data property with a color value.
|
||||
function ColorButton(color, propname) {
|
||||
if (!propname) propname = "color";
|
||||
return $(go.Shape,
|
||||
{
|
||||
width: 16, height: 16, stroke: "lightgray", fill: color,
|
||||
margin: 1, background: "transparent",
|
||||
mouseEnter: function(e, shape) { shape.stroke = "dodgerblue"; },
|
||||
mouseLeave: function(e, shape) { shape.stroke = "lightgray"; },
|
||||
click: ClickFunction(propname, color), contextClick: ClickFunction(propname, color)
|
||||
});
|
||||
}
|
||||
|
||||
function LightFillButtons() { // used by multiple context menus
|
||||
return [
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
ColorButton("white", "fill"), ColorButton("beige", "fill"), ColorButton("aliceblue", "fill"), ColorButton("lightyellow", "fill")
|
||||
)
|
||||
),
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
ColorButton("lightgray", "fill"), ColorButton("lightgreen", "fill"), ColorButton("lightblue", "fill"), ColorButton("pink", "fill")
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
function DarkColorButtons() { // used by multiple context menus
|
||||
return [
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
ColorButton("black"), ColorButton("green"), ColorButton("blue"), ColorButton("red")
|
||||
)
|
||||
),
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
ColorButton("brown"), ColorButton("magenta"), ColorButton("purple"), ColorButton("orange")
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
// Create a context menu button for setting a data property with a stroke width value.
|
||||
function ThicknessButton(sw, propname) {
|
||||
if (!propname) propname = "thickness";
|
||||
return $(go.Shape, "LineH",
|
||||
{
|
||||
width: 16, height: 16, strokeWidth: sw,
|
||||
margin: 1, background: "transparent",
|
||||
mouseEnter: function(e, shape) { shape.background = "dodgerblue"; },
|
||||
mouseLeave: function(e, shape) { shape.background = "transparent"; },
|
||||
click: ClickFunction(propname, sw), contextClick: ClickFunction(propname, sw)
|
||||
});
|
||||
}
|
||||
|
||||
// Create a context menu button for setting a data property with a stroke dash Array value.
|
||||
function DashButton(dash, propname) {
|
||||
if (!propname) propname = "dash";
|
||||
return $(go.Shape, "LineH",
|
||||
{
|
||||
width: 24, height: 16, strokeWidth: 2,
|
||||
strokeDashArray: dash,
|
||||
margin: 1, background: "transparent",
|
||||
mouseEnter: function(e, shape) { shape.background = "dodgerblue"; },
|
||||
mouseLeave: function(e, shape) { shape.background = "transparent"; },
|
||||
click: ClickFunction(propname, dash), contextClick: ClickFunction(propname, dash)
|
||||
});
|
||||
}
|
||||
|
||||
function StrokeOptionsButtons() { // used by multiple context menus
|
||||
return [
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
ThicknessButton(1), ThicknessButton(2), ThicknessButton(3), ThicknessButton(4)
|
||||
)
|
||||
),
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
DashButton(null), DashButton([2, 4]), DashButton([4, 4])
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
// Node context menu
|
||||
|
||||
function FigureButton(fig, propname) {
|
||||
if (!propname) propname = "figure";
|
||||
return $(go.Shape,
|
||||
{
|
||||
width: 32, height: 32, scale: 0.5, fill: "lightgray", figure: fig,
|
||||
margin: 1, background: "transparent",
|
||||
mouseEnter: function(e, shape) { shape.fill = "dodgerblue"; },
|
||||
mouseLeave: function(e, shape) { shape.fill = "lightgray"; },
|
||||
click: ClickFunction(propname, fig), contextClick: ClickFunction(propname, fig)
|
||||
});
|
||||
}
|
||||
|
||||
myDiagram.nodeTemplate.contextMenu =
|
||||
$("ContextMenu",
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
FigureButton("Rectangle"), FigureButton("RoundedRectangle"), FigureButton("Ellipse"), FigureButton("Diamond")
|
||||
)
|
||||
),
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
FigureButton("Parallelogram2"), FigureButton("ManualOperation"), FigureButton("Procedure"), FigureButton("Cylinder1")
|
||||
)
|
||||
),
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
FigureButton("Terminator"), FigureButton("CreateRequest"), FigureButton("Document"), FigureButton("TriangleDown")
|
||||
)
|
||||
),
|
||||
LightFillButtons(),
|
||||
DarkColorButtons(),
|
||||
StrokeOptionsButtons()
|
||||
);
|
||||
|
||||
|
||||
// Group template
|
||||
|
||||
myDiagram.groupTemplate =
|
||||
$(go.Group, "Spot",
|
||||
{
|
||||
layerName: "Background",
|
||||
ungroupable: true,
|
||||
locationSpot: go.Spot.Center,
|
||||
selectionObjectName: "BODY",
|
||||
computesBoundsAfterDrag: true, // allow dragging out of a Group that uses a Placeholder
|
||||
handlesDragDropForMembers: true, // don't need to define handlers on Nodes and Links
|
||||
mouseDrop: function(e, grp) { // add dropped nodes as members of the group
|
||||
var ok = grp.addMembers(grp.diagram.selection, true);
|
||||
if (!ok) grp.diagram.currentTool.doCancel();
|
||||
},
|
||||
avoidable: false
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "BODY" },
|
||||
$(go.Shape,
|
||||
{
|
||||
parameter1: 10,
|
||||
fill: "white", strokeWidth: 2,
|
||||
portId: "", cursor: "pointer",
|
||||
fromLinkable: true, toLinkable: true,
|
||||
fromLinkableDuplicates: true, toLinkableDuplicates: true,
|
||||
fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides
|
||||
},
|
||||
new go.Binding("fill"),
|
||||
new go.Binding("stroke", "color"),
|
||||
new go.Binding("strokeWidth", "thickness"),
|
||||
new go.Binding("strokeDashArray", "dash")),
|
||||
$(go.Placeholder,
|
||||
{ background: "transparent", margin: 10 })
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom,
|
||||
font: "bold 12pt sans-serif", editable: true
|
||||
},
|
||||
new go.Binding("text"),
|
||||
new go.Binding("stroke", "color"))
|
||||
);
|
||||
|
||||
myDiagram.groupTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, "Spot",
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
|
||||
$(go.Placeholder, { margin: 1.5 })
|
||||
),
|
||||
CMButton({ alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomRight })
|
||||
);
|
||||
|
||||
myDiagram.groupTemplate.contextMenu =
|
||||
$("ContextMenu",
|
||||
LightFillButtons(),
|
||||
DarkColorButtons(),
|
||||
StrokeOptionsButtons()
|
||||
);
|
||||
|
||||
|
||||
// Link template
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
layerName: "Foreground",
|
||||
routing: go.Link.AvoidsNodes, corner: 10,
|
||||
toShortLength: 4, // assume arrowhead at "to" end, need to avoid bad appearance when path is thick
|
||||
relinkableFrom: true, relinkableTo: true,
|
||||
reshapable: true, resegmentable: true
|
||||
},
|
||||
new go.Binding("fromSpot", "fromSpot", go.Spot.parse),
|
||||
new go.Binding("toSpot", "toSpot", go.Spot.parse),
|
||||
new go.Binding("fromShortLength", "dir", function(dir) { return dir === 2 ? 4 : 0; }),
|
||||
new go.Binding("toShortLength", "dir", function(dir) { return dir >= 1 ? 4 : 0; }),
|
||||
new go.Binding("points").makeTwoWay(), // TwoWay due to user reshaping with LinkReshapingTool
|
||||
$(go.Shape, { strokeWidth: 2 },
|
||||
new go.Binding("stroke", "color"),
|
||||
new go.Binding("strokeWidth", "thickness"),
|
||||
new go.Binding("strokeDashArray", "dash")),
|
||||
$(go.Shape, { fromArrow: "Backward", strokeWidth: 0, scale: 4/3, visible: false },
|
||||
new go.Binding("visible", "dir", function(dir) { return dir === 2; }),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("scale", "thickness", function(t) { return (2+t)/3; })),
|
||||
$(go.Shape, { toArrow: "Standard", strokeWidth: 0, scale: 4/3 },
|
||||
new go.Binding("visible", "dir", function(dir) { return dir >= 1; }),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("scale", "thickness", function(t) { return (2+t)/3; })),
|
||||
$(go.TextBlock,
|
||||
{ alignmentFocus: new go.Spot(0, 1, -4, 0), editable: true },
|
||||
new go.Binding("text").makeTwoWay(), // TwoWay due to user editing with TextEditingTool
|
||||
new go.Binding("stroke", "color"))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, // use a special selection Adornment that does not obscure the link path itself
|
||||
$(go.Shape,
|
||||
{ // this uses a pathPattern with a gap in it, in order to avoid drawing on top of the link path Shape
|
||||
isPanelMain: true,
|
||||
stroke: "transparent", strokeWidth: 6,
|
||||
pathPattern: makeAdornmentPathPattern(2) // == thickness or strokeWidth
|
||||
},
|
||||
new go.Binding("pathPattern", "thickness", makeAdornmentPathPattern)),
|
||||
CMButton({ alignmentFocus: new go.Spot(0, 0, -6, -4) })
|
||||
);
|
||||
|
||||
function makeAdornmentPathPattern(w) {
|
||||
return $(go.Shape,
|
||||
{
|
||||
stroke: "dodgerblue", strokeWidth: 2, strokeCap: "square",
|
||||
geometryString: "M0 0 M4 2 H3 M4 " + (w+4).toString() + " H3"
|
||||
});
|
||||
}
|
||||
|
||||
// Link context menu
|
||||
// All buttons in context menu work on both click and contextClick,
|
||||
// in case the user context-clicks on the button.
|
||||
// All buttons modify the link data, not the Link, so the Bindings need not be TwoWay.
|
||||
|
||||
function ArrowButton(num) {
|
||||
var geo = "M0 0 M16 16 M0 8 L16 8 M12 11 L16 8 L12 5";
|
||||
if (num === 0) {
|
||||
geo = "M0 0 M16 16 M0 8 L16 8";
|
||||
} else if (num === 2) {
|
||||
geo = "M0 0 M16 16 M0 8 L16 8 M12 11 L16 8 L12 5 M4 11 L0 8 L4 5";
|
||||
}
|
||||
return $(go.Shape,
|
||||
{
|
||||
geometryString: geo,
|
||||
margin: 2, background: "transparent",
|
||||
mouseEnter: function(e, shape) { shape.background = "dodgerblue"; },
|
||||
mouseLeave: function(e, shape) { shape.background = "transparent"; },
|
||||
click: ClickFunction("dir", num), contextClick: ClickFunction("dir", num)
|
||||
});
|
||||
}
|
||||
|
||||
function AllSidesButton(to) {
|
||||
var setter = function(e, shape) {
|
||||
e.handled = true;
|
||||
e.diagram.model.commit(function(m) {
|
||||
var link = shape.part.adornedPart;
|
||||
m.set(link.data, (to ? "toSpot" : "fromSpot"), go.Spot.stringify(go.Spot.AllSides));
|
||||
// re-spread the connections of other links connected with the node
|
||||
(to ? link.toNode : link.fromNode).invalidateConnectedLinks();
|
||||
});
|
||||
};
|
||||
return $(go.Shape,
|
||||
{
|
||||
width: 12, height: 12, fill: "transparent",
|
||||
mouseEnter: function(e, shape) { shape.background = "dodgerblue"; },
|
||||
mouseLeave: function(e, shape) { shape.background = "transparent"; },
|
||||
click: setter, contextClick: setter
|
||||
});
|
||||
}
|
||||
|
||||
function SpotButton(spot, to) {
|
||||
var ang = 0;
|
||||
var side = go.Spot.RightSide;
|
||||
if (spot.equals(go.Spot.Top)) { ang = 270; side = go.Spot.TopSide; }
|
||||
else if (spot.equals(go.Spot.Left)) { ang = 180; side = go.Spot.LeftSide; }
|
||||
else if (spot.equals(go.Spot.Bottom)) { ang = 90; side = go.Spot.BottomSide; }
|
||||
if (!to) ang -= 180;
|
||||
var setter = function(e, shape) {
|
||||
e.handled = true;
|
||||
e.diagram.model.commit(function(m) {
|
||||
var link = shape.part.adornedPart;
|
||||
m.set(link.data, (to ? "toSpot" : "fromSpot"), go.Spot.stringify(side));
|
||||
// re-spread the connections of other links connected with the node
|
||||
(to ? link.toNode : link.fromNode).invalidateConnectedLinks();
|
||||
});
|
||||
};
|
||||
return $(go.Shape,
|
||||
{
|
||||
alignment: spot, alignmentFocus: spot.opposite(),
|
||||
geometryString: "M0 0 M12 12 M12 6 L1 6 L4 4 M1 6 L4 8",
|
||||
angle: ang,
|
||||
background: "transparent",
|
||||
mouseEnter: function(e, shape) { shape.background = "dodgerblue"; },
|
||||
mouseLeave: function(e, shape) { shape.background = "transparent"; },
|
||||
click: setter, contextClick: setter
|
||||
});
|
||||
}
|
||||
|
||||
myDiagram.linkTemplate.contextMenu =
|
||||
$("ContextMenu",
|
||||
DarkColorButtons(),
|
||||
StrokeOptionsButtons(),
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
ArrowButton(0), ArrowButton(1), ArrowButton(2)
|
||||
)
|
||||
),
|
||||
$("ContextMenuButton",
|
||||
$(go.Panel, "Horizontal",
|
||||
$(go.Panel, "Spot",
|
||||
AllSidesButton(false),
|
||||
SpotButton(go.Spot.Top, false), SpotButton(go.Spot.Left, false), SpotButton(go.Spot.Right, false), SpotButton(go.Spot.Bottom, false)
|
||||
),
|
||||
$(go.Panel, "Spot",
|
||||
{ margin: new go.Margin(0, 0, 0, 2) },
|
||||
AllSidesButton(true),
|
||||
SpotButton(go.Spot.Top, true), SpotButton(go.Spot.Left, true), SpotButton(go.Spot.Right, true), SpotButton(go.Spot.Bottom, true)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
// save a model to and load a model from JSON-formatted text, displayed below the Diagram
|
||||
function save() {
|
||||
var str = myDiagram.model.toJson();
|
||||
document.getElementById("mySavedModel").value = str;
|
||||
}
|
||||
function load() {
|
||||
var str = document.getElementById("mySavedModel").value;
|
||||
myDiagram.model = go.Model.fromJson(str);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>
|
||||
Double-click in the background to create a new node.
|
||||
Create groups by selecting nodes and invoking Ctrl-G; Ctrl-Shift-G to ungroup a selected group.
|
||||
A selected node will have four orange triangles that when clicked will automatically copy the node and link to it.
|
||||
Use the context menu to change the shape, color, thickness, and dashed-ness.
|
||||
</p>
|
||||
<p>
|
||||
Links can be drawn by dragging from the side of each node.
|
||||
A selected link can be reconnected by dragging an end handle.
|
||||
Use the context menu to change the color, thickness, dashed-ness, and which side the link should connect with.
|
||||
</p>
|
||||
<div id="buttons">
|
||||
<button id="loadModel" onclick="load()">Load</button>
|
||||
<button id="saveModel" onclick="save()">Save</button>
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "GraphLinksModel",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "loc":"0 0", "text":"Alpha", "details":"some information about Alpha and its importance"},
|
||||
{"key":2, "loc":"170 0", "text":"Beta", "color":"blue", "thickness":2, "figure":"Procedure"},
|
||||
{"key":3, "loc":"0 100", "text":"Gamma", "color":"green", "figure":"Cylinder1"},
|
||||
{"key":4, "loc":"80 180", "text":"Delta", "color":"red", "figure":"Terminator", "size":"80 40"},
|
||||
{"key":5, "loc":"350 -50", "text":"Zeta", "group":7, "color":"blue", "figure":"CreateRequest"},
|
||||
{"key":6, "loc":"350 50", "text":"Eta", "group":7, "figure":"Document", "fill":"lightyellow"},
|
||||
{"key":7, "isGroup":true, "text":"Theta", "color":"green", "fill":"lightgreen"},
|
||||
{"key":8, "loc":"520 50", "text":"Iota", "fill":"pink"}
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":1, "to":2, "dash":[ 6,3 ], "thickness":4},
|
||||
{"from":1, "to":3, "dash":[ 2,4 ], "color":"green", "text":"label"},
|
||||
{"from":3, "to":4, "color":"red", "text":"a red label", "fromSpot":"RightSide"},
|
||||
{"from":2, "to":1},
|
||||
{"from":5, "to":6, "text":"in a group"},
|
||||
{"from":2, "to":7},
|
||||
{"from":6, "to":8, "dir":0},
|
||||
{"from":6, "to":8, "dir":1},
|
||||
{"from":6, "to":8, "dir":2}
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,293 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Circular Layout</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive demonstration of circular layout features by the CircularLayout class." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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.CircularLayout)
|
||||
// other properties are set by the layout function, defined below
|
||||
});
|
||||
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Spot",
|
||||
// make sure the Node.location is different from the Node.position
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("text", "text"), // for sorting
|
||||
$(go.Shape, "Ellipse", // the default value for the Shape.figure property
|
||||
{
|
||||
fill: "lightgray",
|
||||
stroke: null,
|
||||
desiredSize: new go.Size(30, 30)
|
||||
},
|
||||
new go.Binding("figure", "figure"),
|
||||
new go.Binding("fill", "fill"),
|
||||
new go.Binding("desiredSize", "size")),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "text"))
|
||||
);
|
||||
|
||||
// define the Link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ selectable: false },
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 3, stroke: "#333" }));
|
||||
|
||||
// generate a circle using the default values
|
||||
rebuildGraph();
|
||||
}
|
||||
|
||||
function rebuildGraph() {
|
||||
var numNodes = document.getElementById("numNodes").value;
|
||||
numNodes = parseInt(numNodes, 10);
|
||||
if (isNaN(numNodes)) numNodes = 16;
|
||||
|
||||
var width = document.getElementById("width").value;
|
||||
width = parseFloat(width, 10);
|
||||
|
||||
var height = document.getElementById("height").value;
|
||||
height = parseFloat(height, 10);
|
||||
|
||||
var randSizes = document.getElementById("randSizes").checked;
|
||||
|
||||
var circ = document.getElementById("circ").checked;
|
||||
|
||||
var cyclic = document.getElementById("cyclic").checked;
|
||||
|
||||
var minLinks = document.getElementById("minLinks").value;
|
||||
minLinks = parseInt(minLinks, 10);
|
||||
|
||||
var maxLinks = document.getElementById("maxLinks").value;
|
||||
maxLinks = parseInt(maxLinks, 10);
|
||||
|
||||
generateCircle(numNodes, width, height, minLinks, maxLinks, randSizes, circ, cyclic);
|
||||
}
|
||||
|
||||
function generateCircle(numNodes, width, height, minLinks, maxLinks, randSizes, circ, cyclic) {
|
||||
myDiagram.startTransaction("generateCircle");
|
||||
// replace the diagram's model's nodeDataArray
|
||||
generateNodes(numNodes, width, height, randSizes, circ);
|
||||
// replace the diagram's model's linkDataArray
|
||||
generateLinks(minLinks, maxLinks, cyclic);
|
||||
// force a diagram layout
|
||||
layout();
|
||||
myDiagram.commitTransaction("generateCircle");
|
||||
}
|
||||
|
||||
function generateNodes(numNodes, width, height, randSizes, circ) {
|
||||
var nodeArray = [];
|
||||
for (var i = 0; i < numNodes; i++) {
|
||||
var size;
|
||||
if (randSizes) {
|
||||
size = new go.Size(Math.floor(Math.random() * (65 - width + 1)) + width, Math.floor(Math.random() * (65 - height + 1)) + height);
|
||||
} else {
|
||||
size = new go.Size(width, height);
|
||||
}
|
||||
|
||||
if (circ) size.height = size.width;
|
||||
|
||||
var figure = "Rectangle";
|
||||
if (circ) figure = "Ellipse";
|
||||
|
||||
nodeArray.push({
|
||||
key: i,
|
||||
text: i.toString(),
|
||||
figure: figure,
|
||||
fill: go.Brush.randomColor(),
|
||||
size: size
|
||||
});
|
||||
}
|
||||
|
||||
// randomize the data, to help demonstrate sorting
|
||||
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;
|
||||
}
|
||||
|
||||
// set the nodeDataArray to this array of objects
|
||||
myDiagram.model.nodeDataArray = nodeArray;
|
||||
}
|
||||
|
||||
function generateLinks(min, max, cyclic) {
|
||||
if (myDiagram.nodes.count < 2) return;
|
||||
var linkArray = [];
|
||||
var nit = myDiagram.nodes;
|
||||
var nodes = new go.List(/*go.Node*/);
|
||||
nodes.addAll(nit);
|
||||
var num = nodes.length;
|
||||
if (cyclic) {
|
||||
for (var i = 0; i < num; i++) {
|
||||
if (i >= num - 1) {
|
||||
linkArray.push({ from: i, to: 0 });
|
||||
} else {
|
||||
linkArray.push({ from: i, to: i + 1 });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isNaN(min) || min < 0) min = 0;
|
||||
if (isNaN(max) || max < min) max = min;
|
||||
for (var i = 0; i < num; i++) {
|
||||
var next = nodes.get(i);
|
||||
var children = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
for (var j = 1; j <= children; j++) {
|
||||
var to = nodes.get(Math.floor(Math.random() * num));
|
||||
// get keys from the Node.text strings
|
||||
var nextKey = parseInt(next.text, 10);
|
||||
var toKey = parseInt(to.text, 10);
|
||||
if (nextKey !== toKey) {
|
||||
linkArray.push({ from: nextKey, to: toKey });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
myDiagram.model.linkDataArray = linkArray;
|
||||
}
|
||||
|
||||
// Update the layout from the controls, and then perform the layout again
|
||||
function layout() {
|
||||
myDiagram.startTransaction("change Layout");
|
||||
var lay = myDiagram.layout;
|
||||
|
||||
var radius = document.getElementById("radius").value;
|
||||
if (radius !== "NaN") radius = parseFloat(radius, 10);
|
||||
else radius = NaN;
|
||||
lay.radius = radius;
|
||||
|
||||
var aspectRatio = document.getElementById("aspectRatio").value;
|
||||
aspectRatio = parseFloat(aspectRatio, 10);
|
||||
lay.aspectRatio = aspectRatio;
|
||||
|
||||
var startAngle = document.getElementById("startAngle").value;
|
||||
startAngle = parseFloat(startAngle, 10);
|
||||
lay.startAngle = startAngle;
|
||||
|
||||
var sweepAngle = document.getElementById("sweepAngle").value;
|
||||
sweepAngle = parseFloat(sweepAngle, 10);
|
||||
lay.sweepAngle = sweepAngle;
|
||||
|
||||
var spacing = document.getElementById("spacing").value;
|
||||
spacing = parseFloat(spacing, 10);
|
||||
lay.spacing = spacing;
|
||||
|
||||
var arrangement = document.getElementById("arrangement").value;
|
||||
if (arrangement === "ConstantDistance") lay.arrangement = go.CircularLayout.ConstantDistance;
|
||||
else if (arrangement === "ConstantAngle") lay.arrangement = go.CircularLayout.ConstantAngle;
|
||||
else if (arrangement === "ConstantSpacing") lay.arrangement = go.CircularLayout.ConstantSpacing;
|
||||
else if (arrangement === "Packed") lay.arrangement = go.CircularLayout.Packed;
|
||||
|
||||
var diamFormula = getRadioValue("diamFormula");
|
||||
if (diamFormula === "Pythagorean") lay.nodeDiameterFormula = go.CircularLayout.Pythagorean;
|
||||
else if (diamFormula === "Circular") lay.nodeDiameterFormula = go.CircularLayout.Circular;
|
||||
|
||||
var direction = document.getElementById("direction").value;
|
||||
if (direction === "Clockwise") lay.direction = go.CircularLayout.Clockwise;
|
||||
else if (direction === "Counterclockwise") lay.direction = go.CircularLayout.Counterclockwise;
|
||||
else if (direction === "BidirectionalLeft") lay.direction = go.CircularLayout.BidirectionalLeft;
|
||||
else if (direction === "BidirectionalRight") lay.direction = go.CircularLayout.BidirectionalRight;
|
||||
|
||||
var sorting = document.getElementById("sorting").value;
|
||||
if (sorting === "Forwards") lay.sorting = go.CircularLayout.Forwards;
|
||||
else if (sorting === "Reverse") lay.sorting = go.CircularLayout.Reverse;
|
||||
else if (sorting === "Ascending") lay.sorting = go.CircularLayout.Ascending;
|
||||
else if (sorting === "Descending") lay.sorting = go.CircularLayout.Descending;
|
||||
else if (sorting === "Optimized") lay.sorting = go.CircularLayout.Optimized;
|
||||
|
||||
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>New Graph</b><br />
|
||||
# of nodes: <input type="text" size="3" id="numNodes" value="16" /><br />
|
||||
Node Size: <input type="text" size="3" id="width" value="25" /><input type="text" size="3" id="height" value="25" /><br />
|
||||
Random Sizes: <input type="checkbox" id="randSizes" checked="checked" /> (<= 65) <br />
|
||||
Circular Nodes: <input type="checkbox" id="circ" /><br />
|
||||
Graph is simple ring: <input type="checkbox" id="cyclic" /><br />
|
||||
Min Links from Node: <input type="text" size="2" id="minLinks" value="1" /><br />
|
||||
Max Links from Node: <input type="text" size="2" id="maxLinks" value="2" /><br />
|
||||
<button type="button" onclick="rebuildGraph()">Generate Circle</button>
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: top; padding: 5px">
|
||||
<b>CircularLayout Properties</b><br />
|
||||
Radius:
|
||||
<input type="text" size="3" id="radius" value="NaN" onchange="layout()" />
|
||||
(along X axis; NaN or > 0)
|
||||
<br />
|
||||
Aspect Ratio:
|
||||
<input type="text" size="2" id="aspectRatio" value="1" onchange="layout()" />
|
||||
(1 is circular; > 0)
|
||||
<br />
|
||||
Start Angle:
|
||||
<input type="text" size="3" id="startAngle" value="0" onchange="layout()" />
|
||||
(angle at first element)
|
||||
<br />
|
||||
Sweep Angle:
|
||||
<input type="text" size="3" id="sweepAngle" value="360" onchange="layout()" />
|
||||
(degrees occupied; >= 1, <= 360)
|
||||
<br />
|
||||
Spacing:
|
||||
<input type="text" size="2" id="spacing" value="6" onchange="layout()" />
|
||||
(actual spacing also depends on radius)
|
||||
<br />
|
||||
Arrangement:
|
||||
<select name="arrangement" id="arrangement" onchange="layout()">
|
||||
<option value="ConstantDistance">ConstantDistance</option>
|
||||
<option value="ConstantAngle">ConstantAngle</option>
|
||||
<option value="ConstantSpacing" selected="selected">ConstantSpacing</option>
|
||||
<option value="Packed">Packed</option>
|
||||
</select>
|
||||
<br />
|
||||
Node Diameter:
|
||||
<input type="radio" name="diamFormula" onclick="layout()" value="Pythagorean" checked="checked" /> Pythagorean
|
||||
<input type="radio" name="diamFormula" onclick="layout()" value="Circular" /> Circular<br />
|
||||
Direction:
|
||||
<select name="direction" id="direction" onchange="layout()">
|
||||
<option value="Clockwise" selected="selected">Clockwise</option>
|
||||
<option value="Counterclockwise">Counterclockwise</option>
|
||||
<option value="BidirectionalLeft">BidirectionalLeft</option>
|
||||
<option value="BidirectionalRight">BidirectionalRight</option>
|
||||
</select>
|
||||
<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>
|
||||
<option value="Optimized">Optimized</option>
|
||||
</select>
|
||||
(use "Optimized" to reduce the number of link crossings)
|
||||
</span>
|
||||
</div>
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; background: white; width: 100%; height: 500px;"></div>
|
||||
<p>
|
||||
For information on <b>CircularLayout</b> and its properties, see the <a>CircularLayout</a> documentation page.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Candlestick or Range Charts</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="GoJS nodes containing simple range charts." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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");
|
||||
|
||||
// the template for each attribute in a node's array of item data
|
||||
var itemTempl =
|
||||
$(go.Panel, "TableRow",
|
||||
$(go.TextBlock,
|
||||
{ column: 0 },
|
||||
new go.Binding("text")),
|
||||
$(go.Shape,
|
||||
{ column: 1, alignment: go.Spot.Left },
|
||||
{ fill: "slateblue", stroke: "darkblue" },
|
||||
new go.Binding("geometry", "", produceRange)));
|
||||
|
||||
function produceRange(d) {
|
||||
var h = 12; // total height for the markers
|
||||
var w = 3; // half width for the median marker
|
||||
// using constructors is more efficient than calling go.GraphObject.make:
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(d.min, h / 2, false)
|
||||
.add(new go.PathSegment(go.PathSegment.Line, d.max, h / 2)))
|
||||
.add(new go.PathFigure(d.min, 0, false)
|
||||
.add(new go.PathSegment(go.PathSegment.Line, d.min, h)))
|
||||
.add(new go.PathFigure(d.max, 0, false)
|
||||
.add(new go.PathSegment(go.PathSegment.Line, d.max, h)))
|
||||
.add(new go.PathFigure(d.val - w, 0)
|
||||
.add(new go.PathSegment(go.PathSegment.Line, d.val + w, 0))
|
||||
.add(new go.PathSegment(go.PathSegment.Line, d.val + w, h))
|
||||
.add(new go.PathSegment(go.PathSegment.Line, d.val - w, h).close()));
|
||||
}
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape,
|
||||
{ fill: "white" }),
|
||||
$(go.Panel, "Table",
|
||||
{
|
||||
margin: 6,
|
||||
itemTemplate: itemTempl
|
||||
},
|
||||
new go.Binding("itemArray", "items")));
|
||||
|
||||
var nodeDataArray = [
|
||||
{
|
||||
items: [{ text: "first", min: 10, val: 50, max: 60 },
|
||||
{ text: "second", min: 20, val: 70, max: 90 },
|
||||
{ text: "third", min: 40, val: 60, max: 110 },
|
||||
{ text: "fourth", min: 50, val: 80, max: 130 }]
|
||||
}
|
||||
];
|
||||
myDiagram.model = $(go.GraphLinksModel,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray: nodeDataArray
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
||||
</div>
|
||||
<p>
|
||||
For more sophisticated charts within nodes, see the <a href="canvases.html">Canvas Charts</a> sample.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Various Charts in GoJS Nodes</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A diagram where each node contains a chart rendered by Chart.js." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.bundle.min.js"></script>
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
layout: $(go.TreeLayout)
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Vertical",
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: "transparent" },
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Picture,
|
||||
{ width: 300, height: 150, portId: "" },
|
||||
new go.Binding("element", "datasets", makeLineChart))
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 },
|
||||
new go.Binding("text"))
|
||||
);
|
||||
|
||||
// This Binding conversion function creates a Canvas element for a Picture
|
||||
// that has a rendering of a line chart drawn by Chart.js.
|
||||
function makeLineChart(datasets, picture) {
|
||||
var canvases = document.getElementById("myCanvases");
|
||||
|
||||
canv = document.createElement("canvas");
|
||||
canv.width = canv.style.width = "600px";
|
||||
canv.height = canv.style.height = "300px";
|
||||
|
||||
// apparently Chart.js expects the Canvas to be in a DIV
|
||||
var div = document.createElement("div");
|
||||
div.style.position = "absolute";
|
||||
div.appendChild(canv);
|
||||
// add the DIV/Canvas to the DOM, temporarily
|
||||
canvases.appendChild(div);
|
||||
|
||||
var config = { // Chart.js configuration, including the DATASETS data from the model data
|
||||
type: "line",
|
||||
data: {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: datasets
|
||||
},
|
||||
options: {
|
||||
animation: {
|
||||
onProgress: function() { picture.redraw(); },
|
||||
onComplete: function() {
|
||||
var canvases = document.getElementById("myCanvases");
|
||||
if (canvases) { // remove the Canvas that was in the DOM for rendering
|
||||
canvases.removeChild(div);
|
||||
}
|
||||
picture.redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(canv, config);
|
||||
|
||||
return canv;
|
||||
}
|
||||
|
||||
myDiagram.model = $(go.GraphLinksModel,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray:
|
||||
[
|
||||
{
|
||||
key: 1, text: "Alpha",
|
||||
datasets: [{
|
||||
label: "Random data",
|
||||
borderColor: "black",
|
||||
data: makeRandomPoints(8, 10)
|
||||
}]
|
||||
},
|
||||
{
|
||||
key: 2, text: "Beta",
|
||||
datasets: [{
|
||||
label: "First dataset",
|
||||
fill: false,
|
||||
backgroundColor: "red",
|
||||
borderColor: "red",
|
||||
data: makeRandomPoints(8)
|
||||
}, {
|
||||
label: "Second dataset",
|
||||
fill: false,
|
||||
backgroundColor: "blue",
|
||||
borderColor: "blue",
|
||||
data: makeRandomPoints(8)
|
||||
}]
|
||||
},
|
||||
{
|
||||
key: 3, text: "Gamma", color: "green",
|
||||
datasets: [{
|
||||
label: "some data",
|
||||
fill: false,
|
||||
backgroundColor: "green",
|
||||
borderColor: "green",
|
||||
data: makeRandomPoints()
|
||||
}]
|
||||
}
|
||||
],
|
||||
linkDataArray: [
|
||||
{ from: 1, to: 2 },
|
||||
{ from: 1, to: 3 }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function makeRandomPoints(num, range) {
|
||||
if (!num) num = 20;
|
||||
if (!range) range = 100;
|
||||
var pts = [];
|
||||
for (var i = 0; i < num; i++) {
|
||||
pts.push(Math.random() * range);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
function addNode() {
|
||||
myDiagram.model.commit(function(m) {
|
||||
var firstnode = myDiagram.nodes.first();
|
||||
var color = go.Brush.darken(go.Brush.randomColor());
|
||||
var data = {
|
||||
text: "Node " + (myDiagram.nodes.count + 1),
|
||||
color: color,
|
||||
datasets: [{
|
||||
label: "some data",
|
||||
fill: false,
|
||||
backgroundColor: color,
|
||||
borderColor: color,
|
||||
data: makeRandomPoints()
|
||||
}]
|
||||
};
|
||||
m.addNodeData(data);
|
||||
if (firstnode) {
|
||||
m.addLinkData({ from: firstnode.key, to: m.getKeyForNodeData(data) });
|
||||
// new node starts off at same location as the parent node
|
||||
var newnode = myDiagram.findNodeForData(data);
|
||||
if (newnode) newnode.location = firstnode.location;
|
||||
}
|
||||
}, "added chart node");
|
||||
}
|
||||
|
||||
function modifyNodes() {
|
||||
myDiagram.commit(function(diag) {
|
||||
diag.selection.each(function(node) {
|
||||
var oldset = node.data.datasets;
|
||||
if (!oldset) return; // if it's a link, there's no datasets property
|
||||
diag.model.set(node.data, "datasets",
|
||||
[{
|
||||
label: oldset[0].label,
|
||||
fill: false,
|
||||
backgroundColor: oldset[0].backgroundColor,
|
||||
borderColor: oldset[0].borderColor,
|
||||
data: makeRandomPoints()
|
||||
}]);
|
||||
});
|
||||
}, "modified selected nodes");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<button onclick="addNode()">Add Chart</button>
|
||||
<button onclick="modifyNodes()">Modify Charts of Selected Nodes</button>
|
||||
<p>
|
||||
This app demonstrates using a popular charting library, <a href="https://chartjs.org">Chart.js</a>,
|
||||
for rendering charts within nodes.
|
||||
</p>
|
||||
<p>
|
||||
The data for each chart is stored on the node data in the model. In this case the <code>datasets</code>
|
||||
property value has the same properties that are expected by the Chart.js configuration, but you could
|
||||
organize the data however you want.
|
||||
</p>
|
||||
<p>
|
||||
The <code>makeLineChart</code> conversion function is used by a <a>Binding</a> on <a>Picture.element</a>
|
||||
to generate a Canvas element that can be shown in the node. Most of the implementation of that function
|
||||
is specific to Chart.js. The rendering requires the Canvas to be in the HTML DOM. To avoid accumulating
|
||||
resources, the configuration of the chart defines an <code>onComplete</code> event handler to
|
||||
remove the Canvas element from the DOM. That allows any future removal of the Node from the Diagram not
|
||||
to leave an unused Canvas element behind.
|
||||
</p>
|
||||
<!-- myCanvases is used by makeLineChart, but need not be seen by the user -->
|
||||
<div id="myCanvases" style="position:absolute; top:0px; left:0px; width:0px; height:0px; opacity:0"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>GoJS Class Hierarchy Tree</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="The JavaScript class hierarchy defined by the GoJS library, arranged as a tree." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/HyperlinkText.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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
|
||||
|
||||
var diagram = $(go.Diagram, "myDiagramDiv", // id of DIV
|
||||
{ // Automatically lay out the diagram as a tree;
|
||||
// separate trees are arranged vertically above each other.
|
||||
layout: $(go.TreeLayout, { nodeSpacing: 3 })
|
||||
});
|
||||
|
||||
// Define a node template showing class names.
|
||||
// Clicking on the node opens up the documentation for that class.
|
||||
diagram.nodeTemplate =
|
||||
$(go.Node,
|
||||
$("HyperlinkText",
|
||||
// compute the URL to open for the documentation
|
||||
function(node) { return "../api/symbols/" + node.data.key + ".html"; },
|
||||
// define the visuals for the hyperlink, basically the whole node:
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: "#1F4963", stroke: null }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "bold 13px Helvetica, bold Arial, sans-serif",
|
||||
stroke: "white", margin: 3
|
||||
},
|
||||
new go.Binding("text", "key"))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Define a trivial link template with no arrowhead
|
||||
diagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
curve: go.Link.Bezier,
|
||||
toEndSegmentLength: 30, fromEndSegmentLength: 30
|
||||
},
|
||||
$(go.Shape, { strokeWidth: 1.5 }) // the link shape, with the default black stroke
|
||||
);
|
||||
|
||||
// Collect all of the data for the model of the class hierarchy
|
||||
var nodeDataArray = [];
|
||||
|
||||
// Iterate over all of the classes in "go"
|
||||
for (k in go) {
|
||||
var cls = go[k];
|
||||
if (!cls) continue;
|
||||
var proto = cls.prototype;
|
||||
if (!proto) continue;
|
||||
if (k === 'EnumValue' || k === 'TextBlockMetrics') continue; // undocumented classes
|
||||
proto.constructor.className = k; // remember name
|
||||
// find base class constructor
|
||||
var base = Object.getPrototypeOf(proto).constructor;
|
||||
if (base === Object) { // "root" node?
|
||||
nodeDataArray.push({ key: k });
|
||||
} else {
|
||||
// add a node for this class and a tree-parent reference to the base class name
|
||||
nodeDataArray.push({ key: k, parent: base.className });
|
||||
}
|
||||
}
|
||||
|
||||
// Create the model for the hierarchy diagram
|
||||
diagram.model = new go.TreeModel(nodeDataArray);
|
||||
|
||||
// Now collect all node data that are singletons
|
||||
var singlesArray = []; // for classes that don't inherit from another class
|
||||
diagram.nodes.each(function(node) {
|
||||
if (node.linksConnected.count === 0) {
|
||||
singlesArray.push(node.data);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the unconnected class nodes from the main Diagram
|
||||
diagram.model.removeNodeDataCollection(singlesArray);
|
||||
|
||||
// Display the unconnected classes in a separate Diagram
|
||||
var singletons =
|
||||
$(go.Diagram, "mySingletons",
|
||||
{
|
||||
nodeTemplate: diagram.nodeTemplate, // share the node template with the main Diagram
|
||||
layout:
|
||||
$(go.GridLayout,
|
||||
{
|
||||
wrappingColumn: 1, // put the unconnected nodes in a column
|
||||
spacing: new go.Size(3, 3)
|
||||
}),
|
||||
model: new go.Model(singlesArray) // use a separate model
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<div id="myDiagramDiv" style="flex-grow: 1; height: 725px; margin-right: 4px; border: solid 1px black"></div>
|
||||
<div id="mySingletons" style="width: 160px; background-color: whitesmoke; border: solid 1px black"></div>
|
||||
</div>
|
||||
<p>The JavaScript class hierarchy defined by the GoJS library, laid out by a <a>TreeLayout</a>.
|
||||
Classes that do not have any inheritance relationship are shown at the right.</p>
|
||||
<p>Because the node template uses a "HyperlinkText", clicking on a node will open the API reference for that class in a new window.</p>
|
||||
<p>For more uses of the Tree Layout, see the <a href="DOMTree.html">DOM Tree</a> and <a href="visualTree.html">Visual Tree</a> samples.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,136 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Comments</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A tree-structured diagram annotated with balloon comments, automatically laid out." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/BalloonLink.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
layout: $(go.TreeLayout,
|
||||
{
|
||||
angle: 90,
|
||||
setsPortSpot: false,
|
||||
setsChildPortSpot: false
|
||||
}),
|
||||
"undoManager.isEnabled": true,
|
||||
// When a Node is deleted by the user, also delete all of its Comment Nodes.
|
||||
// When a Comment Link is deleted, also delete the corresponding Comment Node.
|
||||
"SelectionDeleting": function(e) {
|
||||
var parts = e.subject; // the collection of Parts to be deleted, the Diagram.selection
|
||||
// iterate over a copy of this collection,
|
||||
// because we may add to the collection by selecting more Parts
|
||||
parts.copy().each(function(p) {
|
||||
if (p instanceof go.Node) {
|
||||
var node = p;
|
||||
node.findNodesConnected().each(function(n) {
|
||||
// remove every Comment Node that is connected with this node
|
||||
if (n.category === "Comment") {
|
||||
n.isSelected = true; // include in normal deletion process
|
||||
}
|
||||
});
|
||||
} else if (p instanceof go.Link && p.category === "Comment") {
|
||||
var comlink = p; // a "Comment" Link
|
||||
var comnode = comlink.fromNode;
|
||||
// remove the Comment Node that is associated with this Comment Link,
|
||||
if (comnode.category === "Comment") {
|
||||
comnode.isSelected = true; // include in normal deletion process
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$("Node", "Auto",
|
||||
$("Shape",
|
||||
{ fill: "white" },
|
||||
new go.Binding("fill", "color")),
|
||||
$("TextBlock",
|
||||
{ margin: 6 },
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$("Link",
|
||||
$("Shape",
|
||||
{ strokeWidth: 1.5 }),
|
||||
$("Shape",
|
||||
{ toArrow: "Standard", stroke: null })
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Comment",
|
||||
$(go.Node, // this needs to act as a rectangular shape for BalloonLink,
|
||||
{ background: "transparent" }, // which can be accomplished by setting the background.
|
||||
$(go.TextBlock,
|
||||
{ stroke: "brown", margin: 3 },
|
||||
new go.Binding("text"))
|
||||
));
|
||||
|
||||
myDiagram.linkTemplateMap.add("Comment",
|
||||
// if the BalloonLink class has been loaded from the Extensions directory, use it
|
||||
$((typeof BalloonLink === "function" ? BalloonLink : go.Link),
|
||||
$(go.Shape, // the Shape.geometry will be computed to surround the comment node and
|
||||
// point all the way to the commented node
|
||||
{ stroke: "brown", strokeWidth: 1, fill: "lightyellow" })
|
||||
));
|
||||
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{
|
||||
nodeDataArray: [
|
||||
{ key: "Alpha", color: "orange" },
|
||||
{ key: "Beta", color: "lightgreen" },
|
||||
{ key: "Gamma", color: "lightgreen" },
|
||||
{ key: "Delta", color: "pink" },
|
||||
{ key: "A comment", text: "comment\nabout Alpha", category: "Comment" },
|
||||
{ key: "B comment", text: "comment\nabout Beta", category: "Comment" },
|
||||
{ key: "G comment", text: "comment about Gamma", category: "Comment" }
|
||||
],
|
||||
linkDataArray: [
|
||||
{ from: "Alpha", to: "Beta" },
|
||||
{ from: "Alpha", to: "Gamma" },
|
||||
{ from: "Alpha", to: "Delta" },
|
||||
{ from: "A comment", to: "Alpha", category: "Comment" },
|
||||
{ from: "B comment", to: "Beta", category: "Comment" },
|
||||
{ from: "G comment", to: "Gamma", category: "Comment" }
|
||||
]
|
||||
});
|
||||
|
||||
// show the model in JSON format
|
||||
document.getElementById("savedModel").textContent = myDiagram.model.toJson();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px;"></div>
|
||||
<p>
|
||||
<b>GoJS</b> supports the notion of "Comment"s.
|
||||
A "Comment" is a node that is linked with another node but is positioned by some layouts to go along with that other node,
|
||||
rather than be laid out like a regular node and link.
|
||||
</p>
|
||||
<p>
|
||||
In this sample there are three "Comment" nodes, connected with regular nodes by three "Comment" links.
|
||||
Node and link data are marked as "Comment"s by specifying "Comment" as the category.
|
||||
But the "Comment" nodes and links have a different default template, and thus a different appearance, than regular nodes and links.
|
||||
You can specify your own templates for "Comment" nodes and "Comment" links.
|
||||
The "Comment" link template defined here uses the <code>BalloonLink</code> class defined in <a href="../extensions/BalloonLink.js">BalloonLink.js</a> in the Extensions directory.
|
||||
</p>
|
||||
<div style="display: inline">
|
||||
Initial Diagram.model saved in JSON format:<br />
|
||||
<pre id="savedModel"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Concept Map</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A concept map diagram implemented with labeled links and ForceDirectedLayout." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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 name or refer to the DIV HTML element
|
||||
{
|
||||
initialAutoScale: go.Diagram.Uniform, // an initial automatic zoom-to-fit
|
||||
contentAlignment: go.Spot.Center, // align document to the center of the viewport
|
||||
layout:
|
||||
$(go.ForceDirectedLayout, // automatically spread nodes apart
|
||||
{ maxIterations: 200, defaultSpringLength: 30, defaultElectricalCharge: 100 })
|
||||
});
|
||||
|
||||
// define each Node's appearance
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto", // the whole node panel
|
||||
{ locationSpot: go.Spot.Center },
|
||||
// define the node's outer shape, which will surround the TextBlock
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }), stroke: "black" }),
|
||||
$(go.TextBlock,
|
||||
{ font: "bold 10pt helvetica, bold arial, sans-serif", margin: 4 },
|
||||
new go.Binding("text", "text"))
|
||||
);
|
||||
|
||||
// replace the default Link template in the linkTemplateMap
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
$(go.Shape, // the link shape
|
||||
{ stroke: "black" }),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: "standard", stroke: null }),
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, // the label background, which becomes transparent around the edges
|
||||
{
|
||||
fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
|
||||
stroke: null
|
||||
}),
|
||||
$(go.TextBlock, // the label text
|
||||
{
|
||||
textAlign: "center",
|
||||
font: "10pt helvetica, arial, sans-serif",
|
||||
stroke: "#555555",
|
||||
margin: 4
|
||||
},
|
||||
new go.Binding("text", "text"))
|
||||
)
|
||||
);
|
||||
|
||||
// create the model for the concept map
|
||||
var nodeDataArray = [
|
||||
{ key: 1, text: "Concept Maps" },
|
||||
{ key: 2, text: "Organized Knowledge" },
|
||||
{ key: 3, text: "Context Dependent" },
|
||||
{ key: 4, text: "Concepts" },
|
||||
{ key: 5, text: "Propositions" },
|
||||
{ key: 6, text: "Associated Feelings or Affect" },
|
||||
{ key: 7, text: "Perceived Regularities" },
|
||||
{ key: 8, text: "Labeled" },
|
||||
{ key: 9, text: "Hierarchically Structured" },
|
||||
{ key: 10, text: "Effective Teaching" },
|
||||
{ key: 11, text: "Crosslinks" },
|
||||
{ key: 12, text: "Effective Learning" },
|
||||
{ key: 13, text: "Events (Happenings)" },
|
||||
{ key: 14, text: "Objects (Things)" },
|
||||
{ key: 15, text: "Symbols" },
|
||||
{ key: 16, text: "Words" },
|
||||
{ key: 17, text: "Creativity" },
|
||||
{ key: 18, text: "Interrelationships" },
|
||||
{ key: 19, text: "Infants" },
|
||||
{ key: 20, text: "Different Map Segments" }
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: 1, to: 2, text: "represent" },
|
||||
{ from: 2, to: 3, text: "is" },
|
||||
{ from: 2, to: 4, text: "is" },
|
||||
{ from: 2, to: 5, text: "is" },
|
||||
{ from: 2, to: 6, text: "includes" },
|
||||
{ from: 2, to: 10, text: "necessary\nfor" },
|
||||
{ from: 2, to: 12, text: "necessary\nfor" },
|
||||
{ from: 4, to: 5, text: "combine\nto form" },
|
||||
{ from: 4, to: 6, text: "include" },
|
||||
{ from: 4, to: 7, text: "are" },
|
||||
{ from: 4, to: 8, text: "are" },
|
||||
{ from: 4, to: 9, text: "are" },
|
||||
{ from: 5, to: 9, text: "are" },
|
||||
{ from: 5, to: 11, text: "may be" },
|
||||
{ from: 7, to: 13, text: "in" },
|
||||
{ from: 7, to: 14, text: "in" },
|
||||
{ from: 7, to: 19, text: "begin\nwith" },
|
||||
{ from: 8, to: 15, text: "with" },
|
||||
{ from: 8, to: 16, text: "with" },
|
||||
{ from: 9, to: 17, text: "aids" },
|
||||
{ from: 11, to: 18, text: "show" },
|
||||
{ from: 12, to: 19, text: "begins\nwith" },
|
||||
{ from: 17, to: 18, text: "needed\nto see" },
|
||||
{ from: 18, to: 20, text: "between" }
|
||||
];
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: whitesmoke; border: solid 1px black; width: 100%; height: 700px"></div>
|
||||
<p>
|
||||
A concept map sample depicting various suggested relationships between different ideas.
|
||||
See also the <a href="interactiveForce.html">Interactive Force</a> sample that uses the exact same data
|
||||
but a different node template and an interactive <a>ForceDirectedLayout</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,192 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Connection Box Nodes</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Support link connections within a node as well as between nodes.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
initialContentAlignment: go.Spot.Center, // for v1.*
|
||||
initialScale: 1.4,
|
||||
"undoManager.isEnabled": true,
|
||||
"linkingTool.direction": go.LinkingTool.ForwardsOnly,
|
||||
"ModelChanged": function(e) { // just for demonstration purposes,
|
||||
if (e.isTransactionFinished) { // show the model data in the page's TextArea
|
||||
document.getElementById("mySavedModel").textContent = e.model.toJson();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Table",
|
||||
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{
|
||||
selectionObjectName: "BODY",
|
||||
linkValidation: function(fromnode, fromport, tonode, toport, link) {
|
||||
if (!fromport || !toport) return false;
|
||||
if (fromnode === tonode) {
|
||||
// inside a node must go from an input port to an output port
|
||||
return fromport.portId[0] === "i" && toport.portId[0] === "o";
|
||||
} else {
|
||||
// between nodes the port colors must match
|
||||
if (fromport.fill !== toport.fill) return false;
|
||||
// between nodes must go from an output port to an input port
|
||||
return fromport.portId[0] === "o" && toport.portId[0] === "i";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
$(go.RowColumnDefinition, { column: 1, width: 70 }),
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "BODY",
|
||||
row: 0, rowSpan: 99, column: 0, columnSpan: 3, stretch: go.GraphObject.Fill,
|
||||
fill: "gray", strokeWidth: 0, margin: new go.Margin(0, 8)
|
||||
},
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ row: 0, columnSpan: 3, margin: new go.Margin(4, 2, 2, 2) },
|
||||
new go.Binding("text")),
|
||||
$(go.TextBlock,
|
||||
{ row: 2, columnSpan: 3, margin: new go.Margin(4, 2, 2, 2) },
|
||||
new go.Binding("text", "text2")),
|
||||
|
||||
$(go.Panel, "Table",
|
||||
new go.Binding("itemArray", "inPorts"),
|
||||
{
|
||||
row: 1, column: 0,
|
||||
defaultSeparatorPadding: new go.Margin(4, 0),
|
||||
itemTemplate: // input ports
|
||||
$(go.Panel, "TableRow",
|
||||
{ background: "white" },
|
||||
$(go.Shape,
|
||||
{
|
||||
width: 6, height: 6, strokeWidth: 0, margin: new go.Margin(2, 6, 2, 0),
|
||||
fromSpot: go.Spot.Right, toSpot: go.Spot.Left,
|
||||
fromLinkable: true, toLinkable: true,
|
||||
fromLinkableSelfNode: true, toLinkableSelfNode: true, cursor: "pointer"
|
||||
},
|
||||
new go.Binding("portId", "row", function(r) { return "i" + r; }).ofObject(),
|
||||
new go.Binding("fill", "", convertToColor))
|
||||
)
|
||||
}
|
||||
),
|
||||
$(go.Shape,
|
||||
{
|
||||
row: 1, column: 1, fill: "white", strokeWidth: 0,
|
||||
stretch: go.GraphObject.Fill
|
||||
}),
|
||||
$(go.Panel, "Table",
|
||||
new go.Binding("itemArray", "outPorts"),
|
||||
{
|
||||
row: 1, column: 2,
|
||||
defaultSeparatorPadding: new go.Margin(4, 0),
|
||||
itemTemplate: // output ports
|
||||
$(go.Panel, "TableRow",
|
||||
{ background: "white" },
|
||||
$(go.Shape,
|
||||
{
|
||||
width: 6, height: 6, strokeWidth: 0, margin: new go.Margin(2, 0, 2, 6),
|
||||
fromSpot: go.Spot.Right, toSpot: go.Spot.Left,
|
||||
fromLinkable: true, toLinkable: true,
|
||||
fromLinkableSelfNode: true, toLinkableSelfNode: true, cursor: "pointer"
|
||||
},
|
||||
new go.Binding("portId", "row", function(r) { return "o" + r; }).ofObject(),
|
||||
new go.Binding("fill", "", convertToColor))
|
||||
)
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
function convertToColor(n) {
|
||||
switch (n) {
|
||||
case "r": return "brown";
|
||||
case "g": return "olivedrab";
|
||||
case "b": return "cornflowerblue";
|
||||
default: return "black";
|
||||
}
|
||||
}
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ relinkableFrom: true, relinkableTo: true },
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 2 },
|
||||
new go.Binding("stroke", "fromPort", function(p) {
|
||||
return p.fill;
|
||||
}).ofObject())
|
||||
);
|
||||
|
||||
myDiagram.model = $(go.GraphLinksModel, {
|
||||
linkFromPortIdProperty: "fpid",
|
||||
linkToPortIdProperty: "tpid",
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray:
|
||||
[
|
||||
{
|
||||
key: 1, text: "Alpha",
|
||||
location: "0 0",
|
||||
inPorts: ["r", "r", "r", "g", "b"],
|
||||
outPorts: ["r", "g", "g", "b", "b"]
|
||||
},
|
||||
{
|
||||
key: 2, text: "Beta",
|
||||
location: "200 -80",
|
||||
inPorts: ["r", "r", "g", "g", "b"],
|
||||
outPorts: ["r", "r", "g", "g", "b"]
|
||||
},
|
||||
{
|
||||
key: 3, text: "Gamma",
|
||||
location: "200 80",
|
||||
inPorts: ["r", "r", "g", "g", "b"],
|
||||
outPorts: ["r", "r", "g", "g", "b"]
|
||||
}
|
||||
],
|
||||
linkDataArray:
|
||||
[
|
||||
{ from: 1, fpid: "i0", to: 1, tpid: "o1" },
|
||||
{ from: 1, fpid: "i1", to: 1, tpid: "o4" },
|
||||
{ from: 1, fpid: "i2", to: 1, tpid: "o0" },
|
||||
{ from: 1, fpid: "i3", to: 1, tpid: "o3" },
|
||||
{ from: 1, fpid: "i4", to: 1, tpid: "o2" },
|
||||
|
||||
{ from: 1, fpid: "o0", to: 2, tpid: "i0" },
|
||||
{ from: 1, fpid: "o2", to: 2, tpid: "i2" },
|
||||
{ from: 1, fpid: "o2", to: 3, tpid: "i2" },
|
||||
{ from: 1, fpid: "o3", to: 2, tpid: "i4" },
|
||||
{ from: 1, fpid: "o3", to: 3, tpid: "i4" },
|
||||
|
||||
{ from: 2, fpid: "i0", to: 2, tpid: "o1" },
|
||||
{ from: 2, fpid: "i2", to: 2, tpid: "o0" },
|
||||
{ from: 2, fpid: "i4", to: 2, tpid: "o2" },
|
||||
{ from: 3, fpid: "i2", to: 3, tpid: "o3" },
|
||||
{ from: 3, fpid: "i1", to: 3, tpid: "o1" }
|
||||
]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>
|
||||
You can draw new links between ports, both between nodes and within a node.
|
||||
Between nodes, the link validation predicate requires new link connections to connect ports of the same color.
|
||||
However, within a node, links may connect ports of different colors.
|
||||
</p>
|
||||
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Kitten Monitor</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A variation of a monitoring diagram where the objects of interest maintain a constant size while the user zooms in and out on the map." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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",
|
||||
{
|
||||
initialContentAlignment: go.Spot.TopLeft,
|
||||
isReadOnly: true, // allow selection but not moving or copying or deleting
|
||||
"toolManager.hoverDelay": 100, // how quickly tooltips are shown
|
||||
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom // mouse wheel zooms instead of scrolls
|
||||
});
|
||||
|
||||
// the background image, a floor plan
|
||||
myDiagram.add(
|
||||
$(go.Part, // this Part is not bound to any model data
|
||||
{
|
||||
layerName: "Background", position: new go.Point(0, 0),
|
||||
selectable: false, pickable: false
|
||||
},
|
||||
$(go.Picture, "https://upload.wikimedia.org/wikipedia/commons/9/9a/Sample_Floorplan.jpg")
|
||||
));
|
||||
|
||||
// the template for each kitten, for now just a colored circle
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node,
|
||||
new go.Binding("location", "loc"), // specified by data
|
||||
{ locationSpot: go.Spot.Center }, // at center of node
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 12, height: 12, stroke: null },
|
||||
new go.Binding("fill", "color")), // also specified by data
|
||||
{ // this tooltip shows the name and picture of the kitten
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
$(go.Panel, "Vertical",
|
||||
$(go.Picture,
|
||||
new go.Binding("source", "src", function(s) { return "images/" + s + ".png"; })),
|
||||
$(go.TextBlock, { margin: 3 },
|
||||
new go.Binding("text", "key"))
|
||||
)
|
||||
) // end Adornment
|
||||
}
|
||||
);
|
||||
|
||||
// pretend there are four kittens
|
||||
myDiagram.model.nodeDataArray = [
|
||||
{ key: "Alonzo", src: "50x40", loc: new go.Point(220, 130), color: "blue" },
|
||||
{ key: "Coricopat", src: "55x55", loc: new go.Point(420, 250), color: "green" },
|
||||
{ key: "Garfield", src: "60x90", loc: new go.Point(640, 450), color: "red" },
|
||||
{ key: "Demeter", src: "80x50", loc: new go.Point(140, 350), color: "purple" }
|
||||
];
|
||||
|
||||
// This code keeps all nodes at a constant size in the viewport,
|
||||
// by adjusting for any scaling done by zooming in or out.
|
||||
// This code ignores simple Parts;
|
||||
// Links will automatically be rerouted as Nodes change size.
|
||||
var origscale = NaN;
|
||||
myDiagram.addDiagramListener("InitialLayoutCompleted", function(e) { origscale = myDiagram.scale; });
|
||||
myDiagram.addDiagramListener("ViewportBoundsChanged", function(e) {
|
||||
if (isNaN(origscale)) return;
|
||||
var newscale = myDiagram.scale;
|
||||
if (e.subject.scale === newscale) return; // optimization: don't scale Nodes when just scrolling/panning
|
||||
myDiagram.skipsUndoManager = true;
|
||||
myDiagram.startTransaction("scale Nodes");
|
||||
myDiagram.nodes.each(function(node) {
|
||||
node.scale = origscale / newscale;
|
||||
});
|
||||
myDiagram.commitTransaction("scale Nodes");
|
||||
myDiagram.skipsUndoManager = false;
|
||||
});
|
||||
|
||||
// simulate some real-time position monitoring, once every 2 seconds
|
||||
function randomMovement() {
|
||||
var model = myDiagram.model;
|
||||
model.startTransaction("update locations");
|
||||
var arr = model.nodeDataArray;
|
||||
var picture = myDiagram.parts.first();
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var data = arr[i];
|
||||
var pt = data.loc;
|
||||
var x = pt.x + 20 * Math.random() - 10;
|
||||
var y = pt.y + 20 * Math.random() - 10;
|
||||
// make sure the kittens stay inside the house
|
||||
var b = picture.actualBounds;
|
||||
if (x < b.x || x > b.right) x = pt.x;
|
||||
if (y < b.y || y > b.bottom) y = pt.y;
|
||||
model.setDataProperty(data, "loc", new go.Point(x, y));
|
||||
}
|
||||
model.commitTransaction("update locations");
|
||||
}
|
||||
function loop() {
|
||||
setTimeout(function() { randomMovement(); loop(); }, 2000);
|
||||
}
|
||||
loop(); // start the simulation
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>The tooltip for each kitten shows its name and photo.</p>
|
||||
<p>When you zoom in or out the effective size of each Node is kept constant by changing its <a>GraphObject.scale</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,163 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Content Alignment examples</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="An interactive GoJS Diagram demonstrating viewports and document bounds and alignment and scaling." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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
|
||||
{
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
for (var i = 0; i < 15; i++) {
|
||||
myDiagram.add( // add an unbound Node to the diagram at a random position
|
||||
$(go.Node,
|
||||
{ position: new go.Point(Math.random() * 251, Math.random() * 251) },
|
||||
$(go.Shape, "Circle",
|
||||
{ fill: go.Brush.randomColor(150), strokeWidth: 2, desiredSize: new go.Size(30, 30) })
|
||||
));
|
||||
}
|
||||
|
||||
// automatically update information in the panel
|
||||
myDiagram.addDiagramListener("DocumentBoundsChanged", updateDOM);
|
||||
myDiagram.addDiagramListener("ViewportBoundsChanged", updateDOM);
|
||||
|
||||
var myPosX = document.getElementById("positionX");
|
||||
var myPosY = document.getElementById("positionY");
|
||||
var myScale = document.getElementById("scale");
|
||||
var myDocBounds = document.getElementById("docBounds");
|
||||
|
||||
function updateDOM(e) {
|
||||
var d = e.diagram;
|
||||
var pos = d.position;
|
||||
myPosX.value = Math.round(pos.x, 2);
|
||||
myPosY.value = Math.round(pos.y, 2);
|
||||
myScale.value = d.scale;
|
||||
var b = d.documentBounds;
|
||||
myDocBounds.textContent = b.x.toFixed(2) + ", " + b.y.toFixed(2) + " " + b.width.toFixed(2) + " x " + b.height.toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
// occurs when one of the contentAlign radio buttons is clicked
|
||||
function changeContentAlign(spot) {
|
||||
myDiagram.startTransaction("");
|
||||
myDiagram.contentAlignment = go.Spot[spot];
|
||||
myDiagram.commitTransaction("");
|
||||
}
|
||||
|
||||
function changePosition(posx, posy) {
|
||||
myDiagram.startTransaction("");
|
||||
var x = parseInt(posx);
|
||||
var y = parseInt(posy);
|
||||
myDiagram.position = new go.Point(x, y);
|
||||
myDiagram.commitTransaction("");
|
||||
}
|
||||
|
||||
function changeScale(scale) {
|
||||
var scale = parseFloat(scale);
|
||||
if (scale > 0) {
|
||||
myDiagram.startTransaction("");
|
||||
myDiagram.scale = scale;
|
||||
myDiagram.commitTransaction("");
|
||||
}
|
||||
}
|
||||
|
||||
function changeFixedBounds(fx, fy, fw, fh) {
|
||||
myDiagram.startTransaction("");
|
||||
var x = parseFloat(fx);
|
||||
var y = parseFloat(fy);
|
||||
var h = parseFloat(fw);
|
||||
var w = parseFloat(fh);
|
||||
myDiagram.fixedBounds = new go.Rect(x, y, Math.max(1, w), Math.max(1, h));
|
||||
myDiagram.commitTransaction("");
|
||||
}
|
||||
|
||||
function changePadding(pt, pr, pb, pl) {
|
||||
myDiagram.startTransaction("");
|
||||
var t = parseFloat(pt);
|
||||
var r = parseFloat(pr);
|
||||
var b = parseFloat(pb);
|
||||
var l = parseFloat(pl);
|
||||
myDiagram.padding = new go.Margin(t, r, b, l);
|
||||
myDiagram.commitTransaction("");
|
||||
}
|
||||
|
||||
function changeAutoScale(scaleType) {
|
||||
myDiagram.startTransaction("");
|
||||
myDiagram.autoScale = go.Diagram[scaleType];
|
||||
myDiagram.commitTransaction("");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="width:100%; white-space: nowrap">
|
||||
<div style="display: inline-block; vertical-align: top; width:50%">
|
||||
<div id="myDiagramDiv" style="height: 400px; background: whitesmoke; border: solid 1px black"></div>
|
||||
</div>
|
||||
|
||||
<div style="display: inline-block; vertical-align: top; width:50%">
|
||||
<div style="border: solid 1px black; padding:5px">
|
||||
Diagram.documentBounds: <div id="docBounds" style="display: inline-block"></div>
|
||||
|
||||
<p>Diagram.contentAlignment:<br />
|
||||
<input type="radio" name="contentAlign" onclick="changeContentAlign(this.id)" id="None" checked/><label for="None">None</label>
|
||||
<input type="radio" name="contentAlign" onclick="changeContentAlign(this.id)" id="Center"/><label for="Center">Center</label><br/>
|
||||
<input type="radio" name="contentAlign" onclick="changeContentAlign(this.id)" id="Left"/><label for="Left">Left</label>
|
||||
<input type="radio" name="contentAlign" onclick="changeContentAlign(this.id)" id="Right"/><label for="Right">Right</label><br/>
|
||||
<input type="radio" name="contentAlign" onclick="changeContentAlign(this.id)" id="Top"/><label for="Top">Top</label>
|
||||
<input type="radio" name="contentAlign" onclick="changeContentAlign(this.id)" id="Bottom"/><label for="Bottom">Bottom</label><br/>
|
||||
</p>
|
||||
<p>Diagram.position:<br />
|
||||
<input type="text" size="3" id="positionX" value="NaN"/>
|
||||
<input type="text" size="3" id="positionY" value="NaN"/>
|
||||
<input type="button" onclick="changePosition(positionX.value, positionY.value)" value="Change"/>
|
||||
</p>
|
||||
|
||||
<p>Diagram.scale:<br />
|
||||
<input type="text" size="3" id="scale" value="1"/>
|
||||
<input type="button" onclick="changeScale(scale.value)" value="Change"/>
|
||||
</p>
|
||||
|
||||
<p>Diagram.fixedBounds (x, y, width, height):<br />
|
||||
<input type="text" size="3" id="fixedX" value="NaN"/>
|
||||
<input type="text" size="3" id="fixedY" value="NaN"/>
|
||||
<input type="text" size="3" id="fixedW" value="NaN"/>
|
||||
<input type="text" size="3" id="fixedH" value="NaN"/>
|
||||
<input type="button" onclick="changeFixedBounds(fixedX.value, fixedY.value, fixedW.value, fixedH.value)" value="Set"/>
|
||||
</p>
|
||||
|
||||
<p>Diagram.padding (top, right, bottom, left):<br />
|
||||
<input type="text" size="3" id="padT" value="5"/>
|
||||
<input type="text" size="3" id="padR" value="5"/>
|
||||
<input type="text" size="3" id="padB" value="5"/>
|
||||
<input type="text" size="3" id="padL" value="5"/>
|
||||
<input type="button" onclick="changePadding(padT.value, padR.value, padB.value, padL.value)" value="Set"/>
|
||||
</p>
|
||||
|
||||
<p>Diagram.autoScale:<br />
|
||||
<input type="radio" name="autoScale" onclick="changeAutoScale(this.value)" id="DiagramNone" value="None" checked /><label for="DiagramNone">Diagram.None</label><br/>
|
||||
<input type="radio" name="autoScale" onclick="changeAutoScale(this.id)" id="Uniform" /><label for="Uniform">Diagram.Uniform</label><br/>
|
||||
<input type="radio" name="autoScale" onclick="changeAutoScale(this.id)" id="UniformToFill" /><label for="UniformToFill">Diagram.UniformToFill</label><br/>
|
||||
(but no greater than CommandHandler.defaultScale)
|
||||
</p>
|
||||
<input type="button" onclick="myDiagram.commandHandler.zoomToFit()" value="Zoom to Fit"/>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
A Diagram's <a>Diagram.contentAlignment</a> property determines how parts are positioned when the
|
||||
<a>Diagram.viewportBounds</a> width or height is different than the <a>Diagram.documentBounds</a> width or height.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,471 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Instruments: Gauges and Meters</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Gauges and Meters that allow the user to control the values that those instruments show" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don"t need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram = $(go.Diagram, "myDiagramDiv",
|
||||
{ "undoManager.isEnabled": true });
|
||||
|
||||
// These properties are what change an object from being a value indicator,
|
||||
// such as a needle or a bar or a thumb of a slider, to being a controller
|
||||
// that the user can drag to change the value of the instrument.
|
||||
// This assumes that the scale (a "Graduated" Panel) is named "SCALE".
|
||||
// The alwaysVisible parameter determines whether the object's visibility
|
||||
// is controlled by the "SCALE"'s Panel.isEnabled property.
|
||||
function sliderActions(alwaysVisible) {
|
||||
return [
|
||||
{
|
||||
isActionable: true,
|
||||
actionDown: function(e, obj) {
|
||||
obj._dragging = true;
|
||||
obj._original = obj.part.data.value;
|
||||
},
|
||||
actionMove: function(e, obj) {
|
||||
if (!obj._dragging) return;
|
||||
var scale = obj.part.findObject("SCALE");
|
||||
var pt = e.diagram.lastInput.documentPoint;
|
||||
var loc = scale.getLocalPoint(pt);
|
||||
var val = Math.round(scale.graduatedValueForPoint(loc));
|
||||
// just set the data.value temporarily, not recorded in UndoManager
|
||||
e.diagram.model.commit(function(m) {
|
||||
m.set(obj.part.data, "value", val);
|
||||
}, null); // null means skipsUndoManager
|
||||
},
|
||||
actionUp: function(e, obj) {
|
||||
if (!obj._dragging) return;
|
||||
obj._dragging = false;
|
||||
var scale = obj.part.findObject("SCALE");
|
||||
var pt = e.diagram.lastInput.documentPoint;
|
||||
var loc = scale.getLocalPoint(pt);
|
||||
var val = Math.round(scale.graduatedValueForPoint(loc));
|
||||
e.diagram.model.commit(function(m) {
|
||||
m.set(obj.part.data, "value", obj._original);
|
||||
}, null); // null means skipsUndoManager
|
||||
// now set the data.value for real
|
||||
e.diagram.model.commit(function(m) {
|
||||
m.set(obj.part.data, "value", val);
|
||||
}, "dragged slider");
|
||||
},
|
||||
actionCancel: function(e, obj) {
|
||||
obj._dragging = false;
|
||||
e.diagram.model.commit(function(m) {
|
||||
m.set(obj.part.data, "value", obj._original);
|
||||
}, null); // null means skipsUndoManager
|
||||
}
|
||||
},
|
||||
(alwaysVisible ? {} : new go.Binding("visible", "isEnabled").ofObject("SCALE")),
|
||||
new go.Binding("cursor", "isEnabled", function(e) { return e ? "pointer" : ""; }).ofObject("SCALE")
|
||||
];
|
||||
}
|
||||
|
||||
// These helper functions simplify the node templates
|
||||
|
||||
function commonScaleBindings() {
|
||||
return [
|
||||
new go.Binding("graduatedMin", "min"),
|
||||
new go.Binding("graduatedMax", "max"),
|
||||
new go.Binding("graduatedTickUnit", "unit"),
|
||||
new go.Binding("isEnabled", "editable")
|
||||
];
|
||||
}
|
||||
|
||||
function commonSlider(vert) {
|
||||
return $(go.Shape, "RoundedRectangle",
|
||||
{
|
||||
name: "SLIDER",
|
||||
fill: "white",
|
||||
desiredSize: (vert ? new go.Size(20, 6) : new go.Size(6, 20)),
|
||||
alignment: (vert ? go.Spot.Top : go.Spot.Right)
|
||||
},
|
||||
sliderActions(false)
|
||||
);
|
||||
}
|
||||
|
||||
function commonNodeStyle() {
|
||||
return [
|
||||
{ locationSpot: go.Spot.Center },
|
||||
{ fromSpot: go.Spot.BottomRightSides, toSpot: go.Spot.TopLeftSides },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Horizontal",
|
||||
$(go.Node, "Auto", commonNodeStyle(),
|
||||
// {
|
||||
// resizable: true,
|
||||
// resizeObjectName: "PATH",
|
||||
// resizeAdornmentTemplate:
|
||||
// $(go.Adornment, "Spot",
|
||||
// $(go.Placeholder),
|
||||
// $(go.Shape, { fill: "dodgerblue", width: 8, height: 8, alignment: go.Spot.Right, cursor: "e-resize" }))
|
||||
// },
|
||||
$(go.Shape,
|
||||
{ fill: "lightgray", stroke: "gray" }),
|
||||
$(go.Panel, "Table",
|
||||
{ margin: 1, stretch: go.GraphObject.Fill },
|
||||
// header information
|
||||
$(go.TextBlock,
|
||||
{ row: 0, font: "bold 10pt sans-serif" },
|
||||
new go.Binding("text")),
|
||||
$(go.Panel, "Spot",
|
||||
{ row: 1 },
|
||||
$(go.Panel, "Graduated",
|
||||
{ name: "SCALE", margin: new go.Margin(0, 6), graduatedTickUnit: 10, isEnabled: false },
|
||||
commonScaleBindings(),
|
||||
$(go.Shape, { geometryString: "M0 0 H200", height: 0, name: "PATH" }),
|
||||
$(go.Shape, { geometryString: "M0 0 V16", alignmentFocus: go.Spot.Center, stroke: "gray" }),
|
||||
$(go.Shape, { geometryString: "M0 0 V20", alignmentFocus: go.Spot.Center, interval: 5, strokeWidth: 1.5 })
|
||||
),
|
||||
$(go.Panel, "Spot",
|
||||
{ alignment: go.Spot.Left, alignmentFocus: go.Spot.Left, alignmentFocusName: "BAR" },
|
||||
// the indicator (a bar)
|
||||
$(go.Shape,
|
||||
{ name: "BAR", fill: "red", strokeWidth: 0, height: 8 },
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("desiredSize", "value", function(v, shp) {
|
||||
var scale = shp.part.findObject("SCALE");
|
||||
var path = scale.findMainElement();
|
||||
var len = (v-scale.graduatedMin) / (scale.graduatedMax-scale.graduatedMin) * path.geometry.bounds.width;
|
||||
return new go.Size(len, 10);
|
||||
})),
|
||||
commonSlider(false)
|
||||
)
|
||||
),
|
||||
// state information
|
||||
$(go.TextBlock, "0",
|
||||
{ row: 2, alignment: go.Spot.Left },
|
||||
new go.Binding("text", "min")),
|
||||
$(go.TextBlock, "100",
|
||||
{ row: 2, alignment: go.Spot.Right },
|
||||
new go.Binding("text", "max")),
|
||||
$(go.TextBlock,
|
||||
{ row: 2, background: "white", font: "bold 10pt sans-serif", isMultiline: false, editable: true },
|
||||
new go.Binding("text", "value", function(v) { return v.toString(); }).makeTwoWay(function(s) { return parseFloat(s); }))
|
||||
)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Vertical",
|
||||
$(go.Node, "Auto", commonNodeStyle(),
|
||||
// {
|
||||
// resizable: true,
|
||||
// resizeObjectName: "PATH",
|
||||
// resizeAdornmentTemplate:
|
||||
// $(go.Adornment, "Spot",
|
||||
// $(go.Placeholder),
|
||||
// $(go.Shape, { fill: "dodgerblue", width: 8, height: 8, alignment: go.Spot.Top, cursor: "n-resize" }))
|
||||
// },
|
||||
$(go.Shape,
|
||||
{ fill: "lightgray", stroke: "gray" }),
|
||||
$(go.Panel, "Table",
|
||||
{ margin: 1, stretch: go.GraphObject.Fill },
|
||||
// header information
|
||||
$(go.TextBlock,
|
||||
{ row: 0, font: "bold 10pt sans-serif" },
|
||||
new go.Binding("text")),
|
||||
$(go.Panel, "Spot",
|
||||
{ row: 1 },
|
||||
$(go.Panel, "Graduated",
|
||||
{ name: "SCALE", margin: new go.Margin(6, 0), graduatedTickUnit: 10, isEnabled: false },
|
||||
commonScaleBindings(),
|
||||
// NOTE: path goes upward!
|
||||
$(go.Shape, { geometryString: "M0 0 V-200", width: 0, name: "PATH" }),
|
||||
$(go.Shape, { geometryString: "M0 0 V16", alignmentFocus: go.Spot.Center, stroke: "gray" }),
|
||||
$(go.Shape, { geometryString: "M0 0 V20", alignmentFocus: go.Spot.Center, interval: 5, strokeWidth: 1.5 })
|
||||
),
|
||||
$(go.Panel, "Spot",
|
||||
{ alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Bottom, alignmentFocusName: "BAR" },
|
||||
// the indicator (a bar)
|
||||
$(go.Shape,
|
||||
{ name: "BAR", fill: "red", strokeWidth: 0, height: 8 },
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("desiredSize", "value", function(v, shp) {
|
||||
var scale = shp.part.findObject("SCALE");
|
||||
var path = scale.findMainElement();
|
||||
var len = (v-scale.graduatedMin) / (scale.graduatedMax-scale.graduatedMin) * path.geometry.bounds.height;
|
||||
return new go.Size(10, len);
|
||||
})),
|
||||
commonSlider(true)
|
||||
)
|
||||
),
|
||||
// state information
|
||||
$(go.TextBlock, "0",
|
||||
{ row: 2, alignment: go.Spot.Left },
|
||||
new go.Binding("text", "min")),
|
||||
$(go.TextBlock, "100",
|
||||
{ row: 2, alignment: go.Spot.Right },
|
||||
new go.Binding("text", "max")),
|
||||
$(go.TextBlock,
|
||||
{ row: 2, background: "white", font: "bold 10pt sans-serif", isMultiline: false, editable: true },
|
||||
new go.Binding("text", "value", function(v) { return v.toString(); }).makeTwoWay(function(s) { return parseFloat(s); }))
|
||||
)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("NeedleMeter",
|
||||
$(go.Node, "Auto", commonNodeStyle(),
|
||||
$(go.Shape, { fill: "darkslategray" }),
|
||||
$(go.Panel, "Spot",
|
||||
$(go.Panel, "Position",
|
||||
$(go.Panel, "Graduated",
|
||||
{ name: "SCALE", margin: 10 },
|
||||
commonScaleBindings(),
|
||||
$(go.Shape, { name: "PATH", geometryString: "M0 0 A120 120 0 0 1 200 0", stroke: "white" }),
|
||||
$(go.Shape, { geometryString: "M0 0 V10", stroke: "white" }),
|
||||
$(go.TextBlock,
|
||||
{ segmentOffset: new go.Point(0, 12), segmentOrientation: go.Link.OrientAlong, stroke: "white" })
|
||||
),
|
||||
$(go.Shape,
|
||||
{ stroke: "red", strokeWidth: 4, isGeometryPositioned: true },
|
||||
new go.Binding("geometry", "value", function(v, shp) {
|
||||
var scale = shp.part.findObject("SCALE");
|
||||
var pt = scale.graduatedPointForValue(v);
|
||||
var geo = new go.Geometry(go.Geometry.Line);
|
||||
geo.startX = 100 + scale.margin.left;
|
||||
geo.startY = 90 + scale.margin.top;
|
||||
geo.endX = pt.x + scale.margin.left;
|
||||
geo.endY = pt.y + scale.margin.top;
|
||||
return geo;
|
||||
}),
|
||||
sliderActions(true))
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ alignment: new go.Spot(0.5, 0.5, 0, 20), stroke: "white", font: "bold 10pt sans-serif" },
|
||||
new go.Binding("text"),
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.TextBlock,
|
||||
{ alignment: go.Spot.Top, margin: new go.Margin(4, 0, 0, 0) },
|
||||
{ stroke: "white", font: "bold italic 13pt sans-serif", isMultiline: false, editable: true },
|
||||
new go.Binding("text", "value", function(v) { return v.toString(); }).makeTwoWay(function(s) { return parseFloat(s); }),
|
||||
new go.Binding("stroke", "color"))
|
||||
)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("CircularMeter",
|
||||
$(go.Node, "Table", commonNodeStyle(),
|
||||
$(go.Panel, "Auto",
|
||||
{ row: 0 },
|
||||
$(go.Shape, "Circle",
|
||||
{ stroke: "orange", strokeWidth: 5, spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight },
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Panel, "Spot",
|
||||
$(go.Panel, "Graduated",
|
||||
{
|
||||
name: "SCALE", margin: 14,
|
||||
graduatedTickUnit: 2.5, // tick marks at each multiple of 2.5
|
||||
stretch: go.GraphObject.None // needed to avoid unnecessary re-measuring!!!
|
||||
},
|
||||
commonScaleBindings(),
|
||||
// the main path of the graduated panel, an arc starting at 135 degrees and sweeping for 270 degrees
|
||||
$(go.Shape, { name: "PATH", geometryString: "M-70.7 70.7 B135 270 0 0 100 100 M0 100", stroke: "white", strokeWidth: 4 }),
|
||||
// three differently sized tick marks
|
||||
$(go.Shape, { geometryString: "M0 0 V10", stroke: "white", strokeWidth: 1 }),
|
||||
$(go.Shape, { geometryString: "M0 0 V12", stroke: "white", strokeWidth: 2, interval: 2 }),
|
||||
$(go.Shape, { geometryString: "M0 0 V15", stroke: "white", strokeWidth: 3, interval: 4 }),
|
||||
$(go.TextBlock,
|
||||
{ // each tick label
|
||||
interval: 4,
|
||||
alignmentFocus: go.Spot.Center,
|
||||
font: "bold italic 14pt sans-serif", stroke: "white",
|
||||
segmentOffset: new go.Point(0, 30)
|
||||
})
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ alignment: new go.Spot(0.5, 0.9), stroke: "white", font: "bold italic 14pt sans-serif", editable: true },
|
||||
new go.Binding("text", "value", function(v) { return v.toString(); }).makeTwoWay(function(s) { return parseFloat(s); }),
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Shape, { fill: "red", strokeWidth: 0, geometryString: "F1 M-6 0 L0 -6 100 0 0 6z x M-100 0" },
|
||||
new go.Binding("angle", "value", function(v, shp) {
|
||||
// this determines the angle of the needle, based on the data.value argument
|
||||
var scale = shp.part.findObject("SCALE");
|
||||
var p = scale.graduatedPointForValue(v);
|
||||
var path = shp.part.findObject("PATH");
|
||||
var c = path.actualBounds.center;
|
||||
return c.directionPoint(p);
|
||||
}),
|
||||
sliderActions(true)),
|
||||
$(go.Shape, "Circle", { width: 2, height: 2, fill: "#444" })
|
||||
)
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ row: 1, font: "bold 11pt sans-serif" },
|
||||
new go.Binding("text"))
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("BarMeter",
|
||||
$(go.Node, "Table", commonNodeStyle(),
|
||||
{ scale: 0.8 },
|
||||
$(go.Panel, "Auto",
|
||||
{ row: 0 },
|
||||
$(go.Shape, "Circle",
|
||||
{ stroke: "orange", strokeWidth: 5, spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight },
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Panel, "Spot",
|
||||
$(go.Panel, "Graduated",
|
||||
{
|
||||
name: "SCALE", margin: 14,
|
||||
graduatedTickUnit: 2.5, // tick marks at each multiple of 2.5
|
||||
stretch: go.GraphObject.None // needed to avoid unnecessary re-measuring!!!
|
||||
},
|
||||
commonScaleBindings(),
|
||||
// the main path of the graduated panel, an arc starting at 135 degrees and sweeping for 270 degrees
|
||||
$(go.Shape, { name: "PATH", geometryString: "M-70.7 70.7 B135 270 0 0 100 100 M0 100", stroke: "white", strokeWidth: 4 }),
|
||||
// three differently sized tick marks
|
||||
$(go.Shape, { geometryString: "M0 0 V10", stroke: "white", strokeWidth: 1 }),
|
||||
$(go.Shape, { geometryString: "M0 0 V12", stroke: "white", strokeWidth: 2, interval: 2 }),
|
||||
$(go.Shape, { geometryString: "M0 0 V15", stroke: "white", strokeWidth: 3, interval: 4 }),
|
||||
$(go.TextBlock,
|
||||
{ // each tick label
|
||||
interval: 4,
|
||||
alignmentFocus: go.Spot.Center,
|
||||
font: "bold italic 14pt sans-serif", stroke: "white",
|
||||
segmentOffset: new go.Point(0, 30)
|
||||
})
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ alignment: go.Spot.Center, stroke: "white", font: "bold italic 14pt sans-serif", editable: true },
|
||||
new go.Binding("text", "value", function(v) { return v.toString(); }).makeTwoWay(function(s) { return parseFloat(s); }),
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Shape, { fill: "red", strokeWidth: 0 },
|
||||
new go.Binding("geometry", "value", function(v, shp) {
|
||||
var scale = shp.part.findObject("SCALE");
|
||||
var p0 = scale.graduatedPointForValue(scale.graduatedMin);
|
||||
var pv = scale.graduatedPointForValue(v);
|
||||
var path = shp.part.findObject("PATH");
|
||||
var radius = path.actualBounds.width/2;
|
||||
var c = path.actualBounds.center;
|
||||
var a0 = c.directionPoint(p0);
|
||||
var av = c.directionPoint(pv);
|
||||
var sweep = av-a0;
|
||||
if (sweep < 0) sweep += 360;
|
||||
var layerThickness = 8;
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(-radius, -radius)) // always make sure the Geometry includes the top left corner
|
||||
.add(new go.PathFigure(radius, radius)) // and the bottom right corner of the whole circular area
|
||||
.add(new go.PathFigure(p0.x-radius, p0.y-radius)
|
||||
.add(new go.PathSegment(go.PathSegment.Arc, a0, sweep, 0, 0, radius, radius))
|
||||
.add(new go.PathSegment(go.PathSegment.Line, pv.x-radius, pv.y-radius))
|
||||
.add(new go.PathSegment(go.PathSegment.Arc, av, -sweep, 0, 0, radius-layerThickness, radius-layerThickness).close()));
|
||||
}),
|
||||
sliderActions(true)),
|
||||
$(go.Shape, "Circle", { width: 2, height: 2, fill: "#444" })
|
||||
)
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ row: 1, font: "bold 11pt sans-serif" },
|
||||
new go.Binding("text"))
|
||||
));
|
||||
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ routing: go.Link.AvoidsNodes, corner: 12 },
|
||||
$(go.Shape, { isPanelMain: true, stroke: "gray", strokeWidth: 9 }),
|
||||
$(go.Shape, { isPanelMain: true, stroke: "lightgray", strokeWidth: 5 }),
|
||||
$(go.Shape, { isPanelMain: true, stroke: "whitesmoke" })
|
||||
)
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 1, value: 87, text: "Vertical", category: "Vertical", loc: "30 0", editable: true, color: "yellow" },
|
||||
{ key: 2, value: 23, text: "Circular Meter", category: "CircularMeter", loc: "250 -120", editable: true, color: "skyblue" },
|
||||
{ key: 3, value: 56, text: "Needle Meter", category: "NeedleMeter", loc: "250 110", editable: true, color: "lightsalmon" },
|
||||
{ key: 4, value: 16, max: 120, text: "Horizontal", category: "Horizontal", loc: "550 0", editable: true, color: "green" },
|
||||
{ key: 5, value: 23, max: 200, unit: 5, text: "Bar Meter", category: "BarMeter", loc: "550 200", editable: true, color: "orange" }
|
||||
],
|
||||
[
|
||||
{ from: 1, to: 2 },
|
||||
{ from: 1, to: 3 },
|
||||
{ from: 2, to: 4 },
|
||||
{ from: 3, to: 4 },
|
||||
{ from: 4, to: 5 }
|
||||
]);
|
||||
|
||||
loop(); // start a simple simulation
|
||||
}
|
||||
|
||||
function loop() {
|
||||
setTimeout(function() {
|
||||
myDiagram.commit(function() {
|
||||
myDiagram.links.each(function(l) {
|
||||
if (Math.random() < 0.2) return;
|
||||
var prev = l.fromNode.data.value;
|
||||
var now = l.toNode.data.value;
|
||||
if (prev > (l.fromNode.data.min || 0) && now < (l.toNode.data.max || 100)) {
|
||||
myDiagram.model.set(l.fromNode.data, "value", prev-1);
|
||||
myDiagram.model.set(l.toNode.data, "value", now+1);
|
||||
}
|
||||
});
|
||||
})
|
||||
loop();
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:800px; height:600px"></div>
|
||||
<p>
|
||||
Instruments are Panels that include:
|
||||
</p>
|
||||
<ul>
|
||||
<li>a scale which is a "Graduated" Panel showing a possible range of values</li>
|
||||
<li>one or more indicators that show the instrument's value</li>
|
||||
</ul>
|
||||
<p>
|
||||
Optionally there are other TextBlocks or Shapes that show additional information.
|
||||
Indicators can be Shapes or TextBlocks or more complicated Panels.
|
||||
For more about scales, please read <a href="../intro/graduatedPanels.html">Graduated Panels</a>.
|
||||
For simplicity, all of these instruments only show one value.
|
||||
But you could define instruments that show multiple values on the same scale,
|
||||
or that have multiple scales.
|
||||
</p>
|
||||
<p>
|
||||
When an instrument is also a control, the user can modify the instrument's value.
|
||||
When the instrument is editable, there may be a handle that the user can drag.
|
||||
This might be the same as the indicator or might be a different object.
|
||||
</p>
|
||||
<p>
|
||||
This sample defines five different types of instruments.
|
||||
<ul>
|
||||
<li><b>Horizontal</b>, a horizontal scale with a bar indicator and a slider handle</li>
|
||||
<li><b>Vertical</b>, a vertical scale with a bar indicator and a slider handle</li>
|
||||
<li><b>NeedleMeter</b>, a curved scale with a straight needle indicator</li>
|
||||
<li><b>CircularMeter</b>, a circular scale with a polygonal needle indicator</li>
|
||||
<li><b>BarMeter</b>, a circular scale with an annular bar indicator</li>
|
||||
</ul>
|
||||
<p>
|
||||
The value to be shown by the instrument is assumed to be the <code>data.value</code> property.
|
||||
The value is shown both textually in a TextBlock and graphically using an indicator on the scale.
|
||||
If the value of <code>data.editable</code> is true,
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
the user can drag something to change the instrument's value --
|
||||
the value is limited by the <a>Panel.graduatedMin</a> and <a>Panel.graduatedMax</a> values
|
||||
</li>
|
||||
<li>the user can in-place edit the TextBlock showing the value (if the node is selected, hit the F2 key)</li>
|
||||
</ul>
|
||||
<p>
|
||||
Of course you can change the details of anything you want to use.
|
||||
You might want to add more TextBlocks to show more information.
|
||||
A few properties already have data Bindings, such as:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a>TextBlock.text</a> from <code>data.text</code>, for the name of the instrument</li>
|
||||
<li><a>Panel.graduatedMin</a> from <code>data.min</code>, to control the range of the scale</li>
|
||||
<li><a>Panel.graduatedMax</a> from <code>data.max</code>, to control the range of the scale</li>
|
||||
<li>(various) from <code>data.color</code>, to control some colors used by the instrument</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Curviness</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Links with different amounts of curviness." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
new go.Binding("position", "position"),
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 3 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, go.Link.Bezier,
|
||||
// when using fromSpot/toSpot:
|
||||
{ fromSpot: go.Spot.Left, toSpot: go.Spot.Left },
|
||||
new go.Binding("fromEndSegmentLength", "curviness"),
|
||||
new go.Binding("toEndSegmentLength", "curviness"),
|
||||
|
||||
// if not using fromSpot/toSpot, use a binding to curviness instead:
|
||||
//new go.Binding("curviness", "curviness"),
|
||||
|
||||
$(go.Shape, // the link shape
|
||||
{ stroke: "black", strokeWidth: 1.5 }),
|
||||
|
||||
$(go.Shape, // the arrowhead, at the mid point of the link
|
||||
{ toArrow: "OpenTriangle", segmentIndex: -Infinity })
|
||||
);
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ position: new go.Point(100, 100), key: "Alpha", color: "lightblue" },
|
||||
{ position: new go.Point(100, 200), key: "Beta", color: "orange" },
|
||||
{ position: new go.Point(100, 300), key: "Gamma", color: "lightgreen" },
|
||||
{ position: new go.Point(100, 400), key: "Delta", color: "pink" }
|
||||
],
|
||||
[
|
||||
// The links have different curviness values.
|
||||
// Set by hand here, they are larger when the two nodes are farther away
|
||||
{ from: "Alpha", to: "Beta", curviness: 20 },
|
||||
{ from: "Alpha", to: "Gamma", curviness: 40 },
|
||||
{ from: "Gamma", to: "Delta", curviness: 20 },
|
||||
{ from: "Delta", to: "Alpha", curviness: 60 }
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:500px; height:500px"></div>
|
||||
<p>
|
||||
This sample explicitly binds the <a>Link.curviness</a> property, so that some links bend out farther than others.
|
||||
</p>
|
||||
<p>
|
||||
The link template also places an arrowhead at the middle of the link,
|
||||
by explicitly setting the arrowhead's <a>GraphObject.segmentIndex</a> to -Infinity
|
||||
<i>after</i> setting <a>Shape.toArrow</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,434 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Animation Demo</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Custom animation examples for GoJS." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
|
||||
<style>
|
||||
.flex-container {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
}
|
||||
.flex-container>div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</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", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
"clickCreatingTool.archetypeNodeData": {
|
||||
color: "palegreen",
|
||||
key: "node"
|
||||
},
|
||||
"undoManager.isEnabled": true,
|
||||
"animationManager.isInitial": false, // To use custom initial animation instead
|
||||
"InitialLayoutCompleted": animateFadeIn // animate with this function
|
||||
});
|
||||
|
||||
|
||||
function animateFadeIn(e) {
|
||||
var diagram = e.diagram;
|
||||
var animation = new go.Animation();
|
||||
animation.isViewportUnconstrained = true;
|
||||
animation.easing = go.Animation.EaseOutExpo; // Looks better for a fade in animation
|
||||
animation.duration = 900;
|
||||
animation.add(diagram, 'position', diagram.position.copy().offset(0, diagram instanceof go.Palette ? 200 : -200), diagram.position);
|
||||
animation.add(diagram, 'opacity', 0, 1);
|
||||
animation.start();
|
||||
}
|
||||
|
||||
var addNodeAdornment =
|
||||
$(go.Adornment, "Spot",
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
|
||||
$(go.Placeholder)),
|
||||
// the button to create a "next" node, at the top-right corner
|
||||
$("Button",
|
||||
{
|
||||
alignment: go.Spot.TopRight,
|
||||
click: addNode // this function is defined below
|
||||
},
|
||||
$(go.Shape, "PlusLine", { desiredSize: new go.Size(6, 6) })
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{
|
||||
selectionAdornmentTemplate: addNodeAdornment,
|
||||
locationSpot: go.Spot.Center
|
||||
},
|
||||
new go.Binding("location", "loc").makeTwoWay(),
|
||||
$(go.Shape, "RoundedRectangle", {
|
||||
strokeWidth: 2,
|
||||
portId: "", // this Shape is the Node's port, not the whole Node
|
||||
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
|
||||
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true,
|
||||
cursor: "pointer"
|
||||
},
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 10, font: '14px sans-serif' },
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: "Alpha", loc: new go.Point(0, 0), color: "lightblue" },
|
||||
{ key: "Beta", loc: new go.Point(150, 0), color: "orange" },
|
||||
{ key: "Gamma", loc: new go.Point(0, 150), color: "lightgreen" },
|
||||
{ key: "Delta", loc: new go.Point(150, 150), color: "pink" }
|
||||
],
|
||||
[
|
||||
// No links to start
|
||||
]);
|
||||
|
||||
// This animation can be used in LinkDrawn Diagram listeners to animate
|
||||
// from a straight temporary link to a Bezier finished link
|
||||
// Custom animation for the curviness of a bezier link
|
||||
go.AnimationManager.defineAnimationEffect('curviness',
|
||||
function (obj, startValue, endValue, easing, currentTime, duration, animationState) {
|
||||
if (isNaN(startValue)) startValue = 0;
|
||||
if (isNaN(endValue)) endValue = 20;
|
||||
obj.curviness = easing(currentTime, startValue, endValue - startValue, duration);
|
||||
}
|
||||
);
|
||||
|
||||
// This animation can be used in LinkDrawn Diagram listeners to animate
|
||||
// from a straight temporary link to an Orthogonal finished link
|
||||
go.AnimationManager.defineAnimationEffect('orthogonalLinkanim',
|
||||
function (link, initPoints, tempPoints, easing, currentTime, duration, animation) {
|
||||
var animationState = animation.getTemporaryState(link);
|
||||
if (animationState.initial === undefined) {
|
||||
// On the first animaiton tick, save the initial points
|
||||
animationState.initial = true;
|
||||
var pts = link.points.copy();
|
||||
tempPoints.points = pts;
|
||||
animationState.startPt = pts.first();
|
||||
animationState.toPt1 = pts.elt(2);
|
||||
animationState.toPt2 = pts.elt(3);
|
||||
animationState.endPt = pts.last();
|
||||
}
|
||||
var newpt1 = new go.Point(
|
||||
easing(currentTime, animationState.startPt.x, animationState.toPt1.x - animationState.startPt.x, duration),
|
||||
easing(currentTime, animationState.startPt.y, animationState.toPt1.y - animationState.startPt.y, duration));
|
||||
var newpt2 = new go.Point(
|
||||
easing(currentTime, animationState.endPt.x, -(animationState.endPt.x - animationState.toPt2.x), duration),
|
||||
easing(currentTime, animationState.endPt.y, -(animationState.endPt.y - animationState.toPt2.y), duration));
|
||||
|
||||
// Setting the array of points will automatically call makeGeometry which will redraw the segments of the line
|
||||
link.points = [animationState.startPt, tempPoints.points.elt(1),
|
||||
newpt1, newpt2,
|
||||
tempPoints.points.elt(4), animationState.endPt];
|
||||
}
|
||||
);
|
||||
|
||||
go.AnimationManager.defineAnimationEffect('corner',
|
||||
function (obj, startValue, endValue, easing, currentTime, duration, animation) {
|
||||
// If no corner set, default to 0 -> 20
|
||||
startValue = startValue || 0;
|
||||
endValue = endValue || 20;
|
||||
obj.corner = easing(currentTime, startValue, endValue - startValue, duration);
|
||||
}
|
||||
);
|
||||
|
||||
myDiagram.addDiagramListener('LinkDrawn', function (e) {
|
||||
var link = e.subject;
|
||||
var animation = new go.Animation();
|
||||
var linkChoice = document.getElementById("links").value
|
||||
if (linkChoice == "bezier") {
|
||||
link.curve = go.Link.Bezier;
|
||||
animation.easing = elasticEase;
|
||||
animation.add(link, "curviness", 0, link.curviness);
|
||||
animation.duration = 500;
|
||||
} else if (linkChoice == "orthogonal") {
|
||||
// The orthogonal animation is two animations, chained together. One to modify the points,
|
||||
// and then another to modify the link.corner value.
|
||||
|
||||
// Store the initial link.corner value,
|
||||
// then set it to 0 so that in between animations it does not revert back to the original state
|
||||
var initCorner = link.corner;
|
||||
link.corner = 0;
|
||||
// Store the original points to this object
|
||||
var tempPoints = {};
|
||||
animation.add(link, "orthogonalLinkanim", link.points, tempPoints);
|
||||
animation.duration = 300;
|
||||
// Chain animation after first one is completed
|
||||
animation.finished = function () {
|
||||
// Set points back to what they were before the animation
|
||||
myDiagram.startTransaction("Fix Points");
|
||||
link.points = tempPoints.points;
|
||||
myDiagram.commitTransaction("Fix Points");
|
||||
// Need to make a new animation object
|
||||
var animation2 = new go.Animation()
|
||||
animation2.add(link, "corner", 0, initCorner);
|
||||
animation2.duration = 250;
|
||||
animation2.start();
|
||||
}
|
||||
animation.start();
|
||||
|
||||
link.routing = go.Link.Orthogonal;
|
||||
// NYI ortho animation
|
||||
link.ensureBounds();
|
||||
}
|
||||
animation.start();
|
||||
});
|
||||
|
||||
|
||||
go.AnimationManager.defineAnimationEffect('bounceDelete',
|
||||
function (obj, startValue, endValue, easing, currentTime, duration, animation) {
|
||||
var animationState = animation.getTemporaryState(obj);
|
||||
if (animationState.initial === undefined) {
|
||||
// Set the initial positions as part of the animationState data
|
||||
animationState.yPos = obj.location.y;
|
||||
animationState.xPos = obj.location.x;
|
||||
animationState.yVelo = 0;
|
||||
animationState.xVelo = 0;
|
||||
animationState.newTime = 0;
|
||||
animationState.oldTime = 0;
|
||||
animationState.initial = true;
|
||||
}
|
||||
obj.location = getPointBounceDelete(currentTime, obj, animationState, obj.diagram);
|
||||
}
|
||||
);
|
||||
|
||||
// Get the point the object should be at based upon the time and original point
|
||||
function getPointBounceDelete(currentTime, obj, animationState, diagram) {
|
||||
if (diagram === null) return new go.Point(animationState.xPos, animationState.yPos);
|
||||
let height = obj.actualBounds.height;
|
||||
animationState.newTime = currentTime;
|
||||
// Animation uses a change in time in order to be more consistant
|
||||
let delTime = (animationState.newTime - animationState.oldTime) / 3;
|
||||
animationState.yVelo += .05 * delTime;
|
||||
// Add a slight easing to the x movement at the beginning of the animation
|
||||
if (currentTime < 200) {
|
||||
animationState.xVelo = currentTime / 300;
|
||||
}
|
||||
// Adjust positions based on the velocities and the change in time
|
||||
animationState.yPos += animationState.yVelo * delTime;
|
||||
animationState.xPos += animationState.xVelo * delTime;
|
||||
// Check to see if the Y position will be less than the bottom of the diagram, if so,
|
||||
// change the direction and scale down the velocity and set the position to the bottom of the diagram
|
||||
if (animationState.yPos > diagram.viewportBounds.height / 2 - height) {
|
||||
animationState.yVelo = -.75 * animationState.yVelo;
|
||||
animationState.yPos = diagram.viewportBounds.height / 2 - height;
|
||||
}
|
||||
let myPoint = new go.Point(animationState.xPos, animationState.yPos)
|
||||
// Get the new old time for use in the next iteration
|
||||
animationState.oldTime = animationState.newTime;
|
||||
return myPoint;
|
||||
}
|
||||
|
||||
myDiagram.addDiagramListener('SelectionDeleting', function (e) {
|
||||
var deletionSelection = document.getElementById('deletion');
|
||||
var animation = new go.Animation();
|
||||
var diagram = e.diagram;
|
||||
e.subject.each(function (p) {
|
||||
// Because we are deleting this part, we cannot animate it, instead we must animate a temporary copy
|
||||
// The animation handles this via addTemporaryPart, which must be passed a copy
|
||||
var part = p.copy();
|
||||
animation.addTemporaryPart(part, diagram);
|
||||
var initPosition = part.position.copy()
|
||||
part.locationSpot = go.Spot.Center;
|
||||
part.position = initPosition;
|
||||
switch (deletionSelection.value) {
|
||||
case "spinOut":
|
||||
animation.add(part, "angle", part.angle, part.angle + 1000);
|
||||
animation.add(part, "scale", part.scale, 0.01);
|
||||
break;
|
||||
case "fadeOut":
|
||||
animation.add(part, "opacity", part.opacity, 0);
|
||||
break;
|
||||
case "zoomOut":
|
||||
animation.add(part, "scale", part.scale, 0.01);
|
||||
break;
|
||||
case "floatOut":
|
||||
animation.add(part, "opacity", part.opacity, 0);
|
||||
animation.add(part, "position", part.position, part.position.copy().add(new go.Point(0, -80)));
|
||||
break;
|
||||
case "bounceOut":
|
||||
animation.add(part, "bounceDelete", part.location); // does't need an end value, bounceDelete determines one
|
||||
animation.add(part, "scale", part.scale, 0.01);
|
||||
animation.duration = 1500;
|
||||
animation.isViewportUnconstrained = true;
|
||||
break;
|
||||
default:
|
||||
// nothing animates
|
||||
break;
|
||||
}
|
||||
});
|
||||
animation.start();
|
||||
});
|
||||
|
||||
myDiagram.addDiagramListener('ClipboardPasted', function (e) {
|
||||
var creationSelection = document.getElementById('creation');
|
||||
// For best performance, be sure to use only one Animation for the entire selection
|
||||
// instead of creating one animation for each object in the selection
|
||||
var animation = new go.Animation();
|
||||
e.subject.each(function (part) {
|
||||
addCreatedPart(part, animation, creationSelection.value)
|
||||
});
|
||||
animation.start();
|
||||
});
|
||||
|
||||
myDiagram.addDiagramListener('PartCreated', function (e) { // From ClickCreatingTool
|
||||
var creationSelection = document.getElementById('creation');
|
||||
var animation = new go.Animation();
|
||||
addCreatedPart(e.subject, animation, creationSelection.value)
|
||||
animation.start();
|
||||
});
|
||||
|
||||
function addCreatedPart(part, animation, creationSelection) {
|
||||
switch (creationSelection) {
|
||||
case "spinIn":
|
||||
animation.add(part, "angle", part.angle + 1000, part.angle);
|
||||
animation.add(part, "scale", 0.01, part.scale);
|
||||
break;
|
||||
case "fadeIn":
|
||||
animation.add(part, "opacity", 0, part.opacity);
|
||||
break;
|
||||
case "zoomIn":
|
||||
animation.add(part, "scale", 0.01, part.scale);
|
||||
break;
|
||||
case "floatIn":
|
||||
animation.add(part, "opacity", 0, part.opacity);
|
||||
animation.add(part, "location", part.location.copy().add(new go.Point(0, -80)), part.location);
|
||||
break;
|
||||
default:
|
||||
// nothing animates
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.getElementById('addNode').addEventListener('click', function (e) { addNode(); });
|
||||
|
||||
|
||||
function addNode() {
|
||||
var diagram = myDiagram;
|
||||
var tempNodes = new go.List();
|
||||
diagram.startTransaction("Add States");
|
||||
diagram.nodes.each(function (node) {
|
||||
if (node.isSelected) {
|
||||
tempNodes.push(node);
|
||||
}
|
||||
})
|
||||
var animation = new go.Animation();
|
||||
// Set the easing to a custom easing function
|
||||
animation.easing = elasticEase;
|
||||
// Add a new node to the right of each node
|
||||
tempNodes.each(function (part) {
|
||||
// get the node data for which the user clicked the button
|
||||
var fromNode = part;
|
||||
var fromData = part.data;
|
||||
// create a new "State" data object, positioned off to the right of the adorned Node
|
||||
var toData = { key: "new" };
|
||||
toData.color = "purple";
|
||||
var p = fromNode.location.copy();
|
||||
// Place the new node randomly somewhere along a circular 200px radius
|
||||
var angle = Math.random() * Math.PI * 2;
|
||||
p.x += Math.cos(angle) * 200;
|
||||
p.y += Math.sin(angle) * 200;
|
||||
toData.loc = p;
|
||||
// add the new node data to the model
|
||||
var model = diagram.model;
|
||||
model.addNodeData(toData);
|
||||
|
||||
// create a link data from the old node data to the new node data
|
||||
var linkdata = {
|
||||
from: model.getKeyForNodeData(fromData), // or just: fromData.key
|
||||
to: model.getKeyForNodeData(toData),
|
||||
};
|
||||
// and add the link data to the model
|
||||
model.addLinkData(linkdata);
|
||||
|
||||
var newnode = diagram.findNodeForData(toData);
|
||||
// Animate each newly created node
|
||||
animation.add(newnode, "position", part.location, newnode.location);
|
||||
});
|
||||
|
||||
animation.start();
|
||||
diagram.commitTransaction("Add States");
|
||||
};
|
||||
|
||||
document.getElementById('reloadModel').addEventListener('click', function (e) {
|
||||
myDiagram.model = go.Model.fromJSON(myDiagram.model.toJSON())
|
||||
});
|
||||
|
||||
// Custom easing function used in some animations
|
||||
function elasticEase(currentTime, startValue, byValue, duration) {
|
||||
var ts = (currentTime /= duration) * currentTime;
|
||||
var tc = ts * currentTime;
|
||||
return startValue + byValue * (56 * tc * ts + -175 * ts * ts + 200 * tc + -100 * ts + 20 * currentTime);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 700px; height: 600px;"></div>
|
||||
|
||||
<div class="flex-container" style="width:700px">
|
||||
<p style="margin-bottom: 0;">
|
||||
This extension implements several custom animations within GoJS. It may be useful to copy some of them into your own project.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Double click in the Diagram background to create a node, or copy and paste nodes, to view creation animations.
|
||||
<li>Delete a node to view deletion animations.
|
||||
<li>Draw links to see new link creation animations.
|
||||
<li>Select a node and press the + button or the button below to see a link-and-node creation animation.
|
||||
<li>Reload the model using the button below to see the custom load animation
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
<p><strong>Options:</strong></p>
|
||||
<div>
|
||||
Creation Animation
|
||||
<select id="creation">
|
||||
<option value="spinIn">Spin In</option>
|
||||
<option value="fadeIn">Fade In</option>
|
||||
<option value="floatIn">Float In</option>
|
||||
<option value="zoomIn">Zoom In</option>
|
||||
<option>--None--</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
Deletion Animation
|
||||
<select id="deletion">
|
||||
<option value="spinOut">Spin Out</option>
|
||||
<option value="fadeOut">Fade Out</option>
|
||||
<option value="floatOut">Float Out</option>
|
||||
<option value="zoomOut">Zoom Out</option>
|
||||
<option value="bounceOut">Bounce Out</option>
|
||||
<option>--None--</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
Drawn Link Style
|
||||
<select id="links">
|
||||
<option value="bezier">Bezier Curve</option>
|
||||
<option value="orthogonal">Orthogonal Curve</option>
|
||||
<option>Linear (no animation)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button id="addNode">Add Node + Link from selected Node</button>
|
||||
<button id="reloadModel">Reload model</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,239 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML Context Menu</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Context menus implemented in HTML rather than as GoJS objects." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<style type="text/css">
|
||||
/* CSS for the traditional context menu */
|
||||
.menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
padding: 8px 0;
|
||||
z-index: 999;
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, .2), 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12);
|
||||
list-style: none;
|
||||
background-color: #ffffff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: block;
|
||||
position: relative;
|
||||
min-width: 60px;
|
||||
margin: 0;
|
||||
padding: 6px 16px;
|
||||
font: bold 12px sans-serif;
|
||||
color: rgba(0, 0, 0, .87);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-item::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.menu-item:hover::before {
|
||||
opacity: .04;
|
||||
}
|
||||
|
||||
.menu .menu {
|
||||
top: -8px;
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.show-menu, .menu-item:hover > .menu {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
|
||||
var myDiagram = null;
|
||||
|
||||
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", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// This is the actual HTML context menu:
|
||||
var cxElement = document.getElementById("contextMenu");
|
||||
|
||||
// Since we have only one main element, we don't have to declare a hide method,
|
||||
// we can set mainElement and GoJS will hide it automatically
|
||||
var myContextMenu = $(go.HTMLInfo, {
|
||||
show: showContextMenu,
|
||||
hide: hideContextMenu
|
||||
});
|
||||
|
||||
// define a simple Node template (but use the default Link template)
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ contextMenu: myContextMenu },
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 3 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: "Alpha", color: "#f38181" },
|
||||
{ key: "Beta", color: "#eaffd0" },
|
||||
{ key: "Gamma", color: "#95e1d3" },
|
||||
{ key: "Delta", color: "#fce38a" }
|
||||
],
|
||||
[
|
||||
{ from: "Alpha", to: "Beta" },
|
||||
{ from: "Alpha", to: "Gamma" },
|
||||
{ from: "Beta", to: "Beta" },
|
||||
{ from: "Gamma", to: "Delta" },
|
||||
{ from: "Delta", to: "Alpha" }
|
||||
]);
|
||||
|
||||
myDiagram.contextMenu = myContextMenu;
|
||||
|
||||
// We don't want the div acting as a context menu to have a (browser) context menu!
|
||||
cxElement.addEventListener("contextmenu", function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}, false);
|
||||
|
||||
function hideCX() {
|
||||
if (myDiagram.currentTool instanceof go.ContextMenuTool) {
|
||||
myDiagram.currentTool.doCancel();
|
||||
}
|
||||
}
|
||||
|
||||
function showContextMenu(obj, diagram, tool) {
|
||||
// Show only the relevant buttons given the current state.
|
||||
var cmd = diagram.commandHandler;
|
||||
var hasMenuItem = false;
|
||||
function maybeShowItem(elt, pred) {
|
||||
if (pred) {
|
||||
elt.style.display = "block";
|
||||
hasMenuItem = true;
|
||||
} else {
|
||||
elt.style.display = "none";
|
||||
}
|
||||
}
|
||||
maybeShowItem(document.getElementById("cut"), cmd.canCutSelection());
|
||||
maybeShowItem(document.getElementById("copy"), cmd.canCopySelection());
|
||||
maybeShowItem(document.getElementById("paste"), cmd.canPasteSelection(diagram.toolManager.contextMenuTool.mouseDownPoint));
|
||||
maybeShowItem(document.getElementById("delete"), cmd.canDeleteSelection());
|
||||
maybeShowItem(document.getElementById("color"), obj !== null);
|
||||
|
||||
// Now show the whole context menu element
|
||||
if (hasMenuItem) {
|
||||
cxElement.classList.add("show-menu");
|
||||
// we don't bother overriding positionContextMenu, we just do it here:
|
||||
var mousePt = diagram.lastInput.viewPoint;
|
||||
cxElement.style.left = mousePt.x + 5 + "px";
|
||||
cxElement.style.top = mousePt.y + "px";
|
||||
}
|
||||
|
||||
// Optional: Use a `window` click listener with event capture to
|
||||
// remove the context menu if the user clicks elsewhere on the page
|
||||
window.addEventListener("click", hideCX, true);
|
||||
}
|
||||
|
||||
function hideContextMenu() {
|
||||
cxElement.classList.remove("show-menu");
|
||||
// Optional: Use a `window` click listener with event capture to
|
||||
// remove the context menu if the user clicks elsewhere on the page
|
||||
window.removeEventListener("click", hideCX, true);
|
||||
}
|
||||
}
|
||||
|
||||
// This is the general menu command handler, parameterized by the name of the command.
|
||||
function cxcommand(event, val) {
|
||||
if (val === undefined) val = event.currentTarget.id;
|
||||
var diagram = myDiagram;
|
||||
switch (val) {
|
||||
case "cut": diagram.commandHandler.cutSelection(); break;
|
||||
case "copy": diagram.commandHandler.copySelection(); break;
|
||||
case "paste": diagram.commandHandler.pasteSelection(diagram.toolManager.contextMenuTool.mouseDownPoint); break;
|
||||
case "delete": diagram.commandHandler.deleteSelection(); break;
|
||||
case "color": {
|
||||
var color = window.getComputedStyle(event.target)['background-color'];
|
||||
changeColor(diagram, color); break;
|
||||
}
|
||||
}
|
||||
diagram.currentTool.stopTool();
|
||||
}
|
||||
|
||||
// A custom command, for changing the color of the selected node(s).
|
||||
function changeColor(diagram, color) {
|
||||
// Always make changes in a transaction, except when initializing the diagram.
|
||||
diagram.startTransaction("change color");
|
||||
diagram.selection.each(function(node) {
|
||||
if (node instanceof go.Node) { // ignore any selected Links and simple Parts
|
||||
// Examine and modify the data, not the Node directly.
|
||||
var data = node.data;
|
||||
// Call setDataProperty to support undo/redo as well as
|
||||
// automatically evaluating any relevant bindings.
|
||||
diagram.model.setDataProperty(data, "color", color);
|
||||
}
|
||||
});
|
||||
diagram.commitTransaction("change color");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="display: inline-block;">
|
||||
<!-- We make a div to contain both the Diagram div and the context menu (such that they are siblings)
|
||||
so that absolute positioning works easily.
|
||||
This DIV containing both MUST have a non-static CSS position (we use position: relative)
|
||||
so that our context menu's absolute coordinates work correctly. -->
|
||||
<div style="position: relative;" >
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
<ul id="contextMenu" class="menu">
|
||||
<li id="cut" class="menu-item" onclick="cxcommand(event)">Cut</li>
|
||||
<li id="copy" class="menu-item" onclick="cxcommand(event)">Copy</li>
|
||||
<li id="paste" class="menu-item" onclick="cxcommand(event)">Paste</li>
|
||||
<li id="delete" class="menu-item" onclick="cxcommand(event)">Delete</li>
|
||||
<li id="color" class="menu-item">Color
|
||||
<ul class="menu">
|
||||
<li class="menu-item" style="background-color: #f38181;" onclick="cxcommand(event, 'color')">Red</li>
|
||||
<li class="menu-item" style="background-color: #eaffd0;" onclick="cxcommand(event, 'color')">Green</li>
|
||||
<li class="menu-item" style="background-color: #95e1d3;" onclick="cxcommand(event, 'color')">Blue</li>
|
||||
<li class="menu-item" style="background-color: #fce38a;" onclick="cxcommand(event, 'color')">Yellow</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="description">
|
||||
<p>This demonstrates the implementation of a custom HTML context menu.</p>
|
||||
<p>For a light-box style HTML context menu implementation, see the <a href="htmlLightBoxContextMenu.html">LightBox Context Menu</a> sample.</p>
|
||||
<p>Right-click or tap-hold on a Node to bring up a context menu.
|
||||
If you have a selection copied in the clipboard, you can bring up a context menu anywhere to paste.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Different Criteria for Hiding "Children" of Collapsed Nodes</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Custom policy for collapsing and expanding subtrees, different than TreeExpanderButton." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
padding: 10,
|
||||
layout: $(go.LayeredDigraphLayout,
|
||||
{ direction: 90, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }),
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, go.Panel.Vertical,
|
||||
{ portId: "", fromLinkable: true, toLinkable: true },
|
||||
new go.Binding("visible"),
|
||||
$(go.Panel, go.Panel.Auto,
|
||||
$(go.Shape,
|
||||
{ fill: "white", minSize: new go.Size(30, 30), strokeWidth: 0 },
|
||||
{ cursor: "pointer" }, // indicate that linking may start here
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 2 },
|
||||
{ fromLinkable: false, toLinkable: false }, // don't start drawing a link from the text
|
||||
new go.Binding("text", "key"))),
|
||||
$("Button", // a replacement for "TreeExpanderButton" that works for non-tree-structured graphs
|
||||
// assume initially not visible because there are no links coming out
|
||||
{ visible: false },
|
||||
// bind the button visibility to whether it's not a leaf node
|
||||
new go.Binding("visible", "isTreeLeaf",
|
||||
function(leaf) { return !leaf; })
|
||||
.ofObject(),
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "ButtonIcon",
|
||||
figure: "MinusLine",
|
||||
desiredSize: new go.Size(6, 6)
|
||||
},
|
||||
new go.Binding("figure", "isCollapsed", // data.isCollapsed remembers "collapsed" or "expanded"
|
||||
function(collapsed) { return collapsed ? "PlusLine" : "MinusLine"; })),
|
||||
{
|
||||
click: function(e, obj) {
|
||||
e.diagram.startTransaction();
|
||||
var node = obj.part;
|
||||
if (node.data.isCollapsed) {
|
||||
expandFrom(node, node);
|
||||
} else {
|
||||
collapseFrom(node, node);
|
||||
}
|
||||
e.diagram.commitTransaction("toggled visibility of dependencies");
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
function collapseFrom(node, start) {
|
||||
if (node.data.isCollapsed) return;
|
||||
node.diagram.model.setDataProperty(node.data, "isCollapsed", true);
|
||||
if (node !== start) node.diagram.model.setDataProperty(node.data, "visible", false);
|
||||
node.findNodesOutOf().each(collapseFrom);
|
||||
}
|
||||
|
||||
function expandFrom(node, start) {
|
||||
if (!node.data.isCollapsed) return;
|
||||
node.diagram.model.setDataProperty(node.data, "isCollapsed", false);
|
||||
if (node !== start) node.diagram.model.setDataProperty(node.data, "visible", true);
|
||||
node.findNodesOutOf().each(expandFrom);
|
||||
}
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ relinkableFrom: true, relinkableTo: true, corner: 10 },
|
||||
$(go.Shape),
|
||||
$(go.Shape, { toArrow: "Standard" }));
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: "A", color: "lightgreen" },
|
||||
{ key: "B1", color: "yellow" },
|
||||
{ key: "B2", color: "yellow" },
|
||||
{ key: "C", color: "lightblue" },
|
||||
{ key: "D1", color: "orange" },
|
||||
{ key: "D2", color: "orange" },
|
||||
{ key: "E", color: "pink" },
|
||||
{ key: "F", color: "lightgreen" },
|
||||
{ key: "Z1", color: "lightgreen" },
|
||||
{ key: "Z2", color: "yellow" },
|
||||
{ key: "Z3", color: "orange" },
|
||||
{ key: "Z4", color: "pink" }
|
||||
], [
|
||||
{ from: "A", to: "B1" },
|
||||
{ from: "B1", to: "C" },
|
||||
{ from: "A", to: "B2" },
|
||||
{ from: "B2", to: "D2" },
|
||||
{ from: "C", to: "D1" },
|
||||
{ from: "C", to: "D2" },
|
||||
{ from: "D1", to: "E" },
|
||||
{ from: "D2", to: "E" },
|
||||
{ from: "D2", to: "F" },
|
||||
{ from: "Z1", to: "Z2" },
|
||||
{ from: "Z2", to: "Z3" },
|
||||
{ from: "Z3", to: "Z4" },
|
||||
{ from: "Z4", to: "Z1" }
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:600px; height:700px"></div>
|
||||
<p>
|
||||
The "TreeExpanderButton", which changes the <a>Node.isTreeExpanded</a> property, really only works with tree structures.
|
||||
When you want to hide/show the "downstream" nodes from a given node, using the "TreeExpanderButton" might not do what you like,
|
||||
especially when there are cycles in the graph structure.
|
||||
</p>
|
||||
<p>
|
||||
Instead, this sample implements a "Button" with custom behavior to modify the visibility of each Node.
|
||||
If this behavior is still not quite right for your app, you can adapt the behavior implemented in the
|
||||
<code>collapseFrom</code> and <code>expandFrom</code> functions to use different criteria for when to stop recursion.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,121 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Text Editing Examples</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Custom text editing using an HTML select box and some radio buttons." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<!-- custom text editors -->
|
||||
<script src="../extensions/TextEditorSelectBox.js"></script>
|
||||
<script src="../extensions/TextEditorRadioButtons.js"></script>
|
||||
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram = $(go.Diagram, "myDiagramDiv", // must identify the DIV
|
||||
{
|
||||
// default text editor is now a SelectBox
|
||||
"textEditingTool.defaultTextEditor": window.TextEditorSelectBox, // defined in textEditorSelectBox.js
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
var brush = new go.Brush(go.Brush.Linear);
|
||||
brush.addColorStop(0, "rgb(255, 211, 89)");
|
||||
brush.addColorStop(1, "rgb(255, 239, 113)");
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Vertical",
|
||||
{
|
||||
resizable: true,
|
||||
rotatable: true,
|
||||
locationSpot: go.Spot.Center
|
||||
},
|
||||
new go.Binding("location", "loc"),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
text: "Alpha",
|
||||
editable: true,
|
||||
font: "32pt Georgia, serif",
|
||||
background: "lightblue"
|
||||
},
|
||||
new go.Binding("choices")),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
text: "Beta",
|
||||
editable: true,
|
||||
font: "22pt Georgia, serif",
|
||||
background: "lightgreen",
|
||||
scale: 2
|
||||
},
|
||||
new go.Binding("choices")),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
text: "Gamma",
|
||||
editable: true,
|
||||
font: "60pt Georgia, serif",
|
||||
background: "orangered",
|
||||
scale: 0.4
|
||||
},
|
||||
new go.Binding("choices")),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
text: "One",
|
||||
editable: true,
|
||||
font: "bold 16pt Arial, Helvetica, sans-serif",
|
||||
background: brush,
|
||||
scale: 2,
|
||||
// this specific TextBlock uses a RadioButtons for editing text
|
||||
textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
|
||||
// this specific TextBlock has its own choices:
|
||||
choices: ['One', 'Two', 'Three', 'Four']
|
||||
})
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 1, choices: ['Alpha', 'Beta', 'Gamma', 'Theta'], loc: new go.Point(250, 150) },
|
||||
{ key: 2, choices: ['Alpha', 'Beta', 'Gamma', 'Theta'], loc: new go.Point(50, 50) }
|
||||
],
|
||||
[
|
||||
{ from: 1, to: 2 }
|
||||
]);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<!--
|
||||
The div needs an explicit size or else we won't see anything.
|
||||
Lets also add a border to help see the edges.
|
||||
-->
|
||||
<div id="myDiagramDiv"
|
||||
style="border: solid 1px black; width:500px; height:400px; min-width: 200px"></div>
|
||||
<p>
|
||||
This example shows how create custom textEditors for the TextEditingTool.
|
||||
</p>
|
||||
<p>
|
||||
Above is a Diagram with two nodes, each holding several TextBlocks.
|
||||
The TextEditingTool on the diagram has a custom editor that consists of an HTML select box with several preset values.
|
||||
This editor will change the text as soon as the user presses Enter, Tab, or clicks away from the select box.
|
||||
</p>
|
||||
<p>
|
||||
TextBlocks can also have their own custom editors that override the TextEditingTool's editor, by setting <a>TextBlock.textEditor</a>.
|
||||
The last TextBlock in each node has its own custom editor that consists of an HTML div with several radio buttons.
|
||||
This editor will change the text as soon as an option is selected.
|
||||
</p>
|
||||
<p>
|
||||
TextBlocks in this sample make use of <a>TextBlock.choices</a> to inform the custom text editing tools.
|
||||
</p>
|
||||
<p>The code for these text editors is in <a href="../extensions/TextEditorSelectBox.js" target="_blank">TextEditorSelectBox.js</a>
|
||||
and <a href="../extensions/TextEditorRadioButtons.js" target="_blank">TextEditorRadioButtons.js</a>.
|
||||
<p>You can see a re-implementation of the default text editors in the <a href="../extensions/TextEditor.html">Text Editor extension</a>.
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,240 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Data Flow Diagram</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Data flow or workflow graph of nodes with varying input and output ports with labels, oriented horizontally." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
initialContentAlignment: go.Spot.Left,
|
||||
initialAutoScale: go.Diagram.UniformToFill,
|
||||
layout: $(go.LayeredDigraphLayout,
|
||||
{ direction: 0 }),
|
||||
"undoManager.isEnabled": true
|
||||
}
|
||||
);
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
function makePort(name, leftside) {
|
||||
var port = $(go.Shape, "Rectangle",
|
||||
{
|
||||
fill: "gray", stroke: null,
|
||||
desiredSize: new go.Size(8, 8),
|
||||
portId: name, // declare this object to be a "port"
|
||||
toMaxLinks: 1, // don't allow more than one link into a port
|
||||
cursor: "pointer" // show a different cursor to indicate potential link point
|
||||
});
|
||||
|
||||
var lab = $(go.TextBlock, name, // the name of the port
|
||||
{ font: "7pt sans-serif" });
|
||||
|
||||
var panel = $(go.Panel, "Horizontal",
|
||||
{ margin: new go.Margin(2, 0) });
|
||||
|
||||
// set up the port/panel based on which side of the node it will be on
|
||||
if (leftside) {
|
||||
port.toSpot = go.Spot.Left;
|
||||
port.toLinkable = true;
|
||||
lab.margin = new go.Margin(1, 0, 0, 1);
|
||||
panel.alignment = go.Spot.TopLeft;
|
||||
panel.add(port);
|
||||
panel.add(lab);
|
||||
} else {
|
||||
port.fromSpot = go.Spot.Right;
|
||||
port.fromLinkable = true;
|
||||
lab.margin = new go.Margin(1, 1, 0, 0);
|
||||
panel.alignment = go.Spot.TopRight;
|
||||
panel.add(lab);
|
||||
panel.add(port);
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
function makeTemplate(typename, icon, background, inports, outports) {
|
||||
var node = $(go.Node, "Spot",
|
||||
$(go.Panel, "Auto",
|
||||
{ width: 100, height: 120 },
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
fill: background, stroke: null, strokeWidth: 0,
|
||||
spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight
|
||||
}),
|
||||
$(go.Panel, "Table",
|
||||
$(go.TextBlock, typename,
|
||||
{
|
||||
row: 0,
|
||||
margin: 3,
|
||||
maxSize: new go.Size(80, NaN),
|
||||
stroke: "white",
|
||||
font: "bold 11pt sans-serif"
|
||||
}),
|
||||
$(go.Picture, icon,
|
||||
{ row: 1, width: 16, height: 16, scale: 3.0 }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
row: 2,
|
||||
margin: 3,
|
||||
editable: true,
|
||||
maxSize: new go.Size(80, 40),
|
||||
stroke: "white",
|
||||
font: "bold 9pt sans-serif"
|
||||
},
|
||||
new go.Binding("text", "name").makeTwoWay())
|
||||
)
|
||||
),
|
||||
$(go.Panel, "Vertical",
|
||||
{
|
||||
alignment: go.Spot.Left,
|
||||
alignmentFocus: new go.Spot(0, 0.5, 8, 0)
|
||||
},
|
||||
inports),
|
||||
$(go.Panel, "Vertical",
|
||||
{
|
||||
alignment: go.Spot.Right,
|
||||
alignmentFocus: new go.Spot(1, 0.5, -8, 0)
|
||||
},
|
||||
outports)
|
||||
);
|
||||
myDiagram.nodeTemplateMap.set(typename, node);
|
||||
}
|
||||
|
||||
makeTemplate("Table", "images/table.svg", "forestgreen",
|
||||
[],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Join", "images/join.svg", "mediumorchid",
|
||||
[makePort("L", true), makePort("R", true)],
|
||||
[makePort("UL", false), makePort("ML", false), makePort("M", false), makePort("MR", false), makePort("UR", false)]);
|
||||
|
||||
makeTemplate("Project", "images/project.svg", "darkcyan",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Filter", "images/filter.svg", "cornflowerblue",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false), makePort("INV", false)]);
|
||||
|
||||
makeTemplate("Group", "images/group.svg", "mediumpurple",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Sort", "images/sort.svg", "sienna",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Export", "images/upload.svg", "darkred",
|
||||
[makePort("", true)],
|
||||
[]);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.Orthogonal, corner: 5,
|
||||
relinkableFrom: true, relinkableTo: true
|
||||
},
|
||||
$(go.Shape, { stroke: "gray", strokeWidth: 2 }),
|
||||
$(go.Shape, { stroke: "gray", fill: "gray", toArrow: "Standard" })
|
||||
);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
// Show the diagram's model in JSON format that the user may edit
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 600px"></div>
|
||||
<p>
|
||||
This sample demonstrates labeled ports on nodes, arranged as a data flow or workflow. These ports are set up as panels, created within
|
||||
the <b>makePort</b> function. This function sets various properties of the <a>Shape</a> and
|
||||
<a>TextBlock</a> that make up the panel, and properties of the panel itself. Most notable are
|
||||
<a>GraphObject.portId</a> to declare the shape as a port, and <a>GraphObject.fromLinkable</a> and
|
||||
<a>GraphObject.toLinkable</a> to set the way the ports can be linked.
|
||||
</p>
|
||||
<p>
|
||||
The diagram also uses the <b>makeTemplate</b> function to create the node templates with shared features.
|
||||
This function takes a type, an image, a background color, and arrays of ports to create the node
|
||||
to be added to the <a>Diagram.nodeTemplateMap</a>.
|
||||
</p>
|
||||
<p>
|
||||
For the same data model rendered somewhat differently, see the <a href="dataFlowVertical.html">Data Flow (vertical)</a> sample.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"nodeCategoryProperty": "type",
|
||||
"linkFromPortIdProperty": "frompid",
|
||||
"linkToPortIdProperty": "topid",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "type":"Table", "name":"Product"},
|
||||
{"key":2, "type":"Table", "name":"Sales"},
|
||||
{"key":3, "type":"Table", "name":"Period"},
|
||||
{"key":4, "type":"Table", "name":"Store"},
|
||||
{"key":11, "type":"Join", "name":"Product, Class"},
|
||||
{"key":12, "type":"Join", "name":"Period"},
|
||||
{"key":13, "type":"Join", "name":"Store"},
|
||||
{"key":21, "type":"Project", "name":"Product, Class"},
|
||||
{"key":31, "type":"Filter", "name":"Boston, Jan2014"},
|
||||
{"key":32, "type":"Filter", "name":"Boston, 2014"},
|
||||
{"key":41, "type":"Group", "name":"Sales"},
|
||||
{"key":42, "type":"Group", "name":"Total Sales"},
|
||||
{"key":51, "type":"Join", "name":"Product Name"},
|
||||
{"key":61, "type":"Sort", "name":"Product Name"},
|
||||
{"key":71, "type":"Export", "name":"File"}
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":1, "frompid":"OUT", "to":11, "topid":"L"},
|
||||
{"from":2, "frompid":"OUT", "to":11, "topid":"R"},
|
||||
{"from":3, "frompid":"OUT", "to":12, "topid":"R"},
|
||||
{"from":4, "frompid":"OUT", "to":13, "topid":"R"},
|
||||
{"from":11, "frompid":"M", "to":12, "topid":"L"},
|
||||
{"from":12, "frompid":"M", "to":13, "topid":"L"},
|
||||
{"from":13, "frompid":"M", "to":21},
|
||||
{"from":21, "frompid":"OUT", "to":31},
|
||||
{"from":21, "frompid":"OUT", "to":32},
|
||||
{"from":31, "frompid":"OUT", "to":41},
|
||||
{"from":32, "frompid":"OUT", "to":42},
|
||||
{"from":41, "frompid":"OUT", "to":51, "topid":"L"},
|
||||
{"from":42, "frompid":"OUT", "to":51, "topid":"R"},
|
||||
{"from":51, "frompid":"OUT", "to":61},
|
||||
{"from":61, "frompid":"OUT", "to":71}
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,239 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Data Flow Diagram</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Data flow or workflow graph of nodes with varying input and output ports with labels, oriented vertically." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
initialContentAlignment: go.Spot.Top,
|
||||
initialAutoScale: go.Diagram.UniformToFill,
|
||||
layout: $(go.LayeredDigraphLayout,
|
||||
{ direction: 90 }),
|
||||
"undoManager.isEnabled": true
|
||||
}
|
||||
);
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
// when the diagram is vertically oriented, "left" means "top" and "right" means "bottom"
|
||||
function makePort(name, leftside) {
|
||||
var port = $(go.Shape, "Circle",
|
||||
{
|
||||
fill: "black", stroke: null,
|
||||
desiredSize: new go.Size(8, 8),
|
||||
portId: name, // declare this object to be a "port"
|
||||
toMaxLinks: 1, // don't allow more than one link into a port
|
||||
cursor: "pointer" // show a different cursor to indicate potential link point
|
||||
});
|
||||
|
||||
var lab = $(go.TextBlock, name, // the name of the port
|
||||
{ font: "7pt sans-serif" });
|
||||
|
||||
var panel = $(go.Panel, "Vertical",
|
||||
{ margin: new go.Margin(0, 2) });
|
||||
|
||||
if (leftside) {
|
||||
port.toSpot = go.Spot.Top;
|
||||
port.toLinkable = true;
|
||||
lab.margin = new go.Margin(1, 0, 0, 1);
|
||||
panel.alignment = go.Spot.TopLeft;
|
||||
panel.add(port);
|
||||
panel.add(lab);
|
||||
} else {
|
||||
port.fromSpot = go.Spot.Bottom;
|
||||
port.fromLinkable = true;
|
||||
lab.margin = new go.Margin(1, 1, 0, 0);
|
||||
panel.alignment = go.Spot.TopRight;
|
||||
panel.add(lab);
|
||||
panel.add(port);
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
function makeTemplate(typename, icon, background, inports, outports) {
|
||||
var node = $(go.Node, "Spot",
|
||||
$(go.Panel, "Auto",
|
||||
{ width: 200, height: 90 },
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
{
|
||||
fill: background,
|
||||
spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight
|
||||
}),
|
||||
$(go.Panel, "Table",
|
||||
$(go.TextBlock, typename,
|
||||
{
|
||||
column: 0,
|
||||
margin: 3,
|
||||
maxSize: new go.Size(80, NaN),
|
||||
stroke: "black",
|
||||
font: "bold 10pt sans-serif"
|
||||
}),
|
||||
$(go.Picture, icon,
|
||||
{ column: 1, width: 55, height: 55 }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
column: 2,
|
||||
margin: 3,
|
||||
editable: true,
|
||||
maxSize: new go.Size(80, 40),
|
||||
stroke: "black",
|
||||
font: "bold 9pt sans-serif"
|
||||
},
|
||||
new go.Binding("text", "name").makeTwoWay())
|
||||
)
|
||||
),
|
||||
$(go.Panel, "Horizontal",
|
||||
{
|
||||
alignment: go.Spot.Top,
|
||||
alignmentFocus: new go.Spot(0.5, 0, 0, 4)
|
||||
},
|
||||
inports),
|
||||
$(go.Panel, "Horizontal",
|
||||
{
|
||||
alignment: go.Spot.Bottom,
|
||||
alignmentFocus: new go.Spot(0.5, 1, 0, -4)
|
||||
},
|
||||
outports)
|
||||
);
|
||||
myDiagram.nodeTemplateMap.set(typename, node);
|
||||
}
|
||||
|
||||
makeTemplate("Table", "images/table.svg", "forestgreen",
|
||||
[],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Join", "images/join.svg", "mediumorchid",
|
||||
[makePort("L", true), makePort("R", true)],
|
||||
[makePort("UL", false), makePort("ML", false), makePort("M", false), makePort("MR", false), makePort("UR", false)]);
|
||||
|
||||
makeTemplate("Project", "images/project.svg", "darkcyan",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Filter", "images/filter.svg", "cornflowerblue",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false), makePort("INV", false)]);
|
||||
|
||||
makeTemplate("Group", "images/group.svg", "mediumpurple",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Sort", "images/sort.svg", "sienna",
|
||||
[makePort("", true)],
|
||||
[makePort("OUT", false)]);
|
||||
|
||||
makeTemplate("Export", "images/upload.svg", "darkred",
|
||||
[makePort("", true)],
|
||||
[]);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.Orthogonal, corner: 5,
|
||||
relinkableFrom: true, relinkableTo: true
|
||||
},
|
||||
$(go.Shape, { stroke: "black", strokeWidth: 2 })
|
||||
);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
// Show the diagram's model in JSON format that the user may edit
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 600px"></div>
|
||||
<p>
|
||||
This sample demonstrates a data flow or workflow graph with labeled ports on nodes. These ports are set up as panels, created within
|
||||
the <b>makePort</b> function. This function sets various properties of the <a>Shape</a> and
|
||||
<a>TextBlock</a> that make up the panel, and properties of the panel itself. Most notable are
|
||||
<a>GraphObject.portId</a> to declare the shape as a port, and <a>GraphObject.fromLinkable</a> and
|
||||
<a>GraphObject.toLinkable</a> to set the way the ports can be linked.
|
||||
</p>
|
||||
<p>
|
||||
The diagram also uses the <b>makeTemplate</b> function to create the node templates with shared features.
|
||||
This function takes a type, an image, a background color, and arrays of ports to create the node
|
||||
to be added to the <a>Diagram.nodeTemplateMap</a>.
|
||||
</p>
|
||||
<p>
|
||||
For the same data model rendered somewhat differently, see the <a href="dataFlow.html">Data Flow (horizontal)</a> sample.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"nodeCategoryProperty": "type",
|
||||
"linkFromPortIdProperty": "frompid",
|
||||
"linkToPortIdProperty": "topid",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "type":"Table", "name":"Product"},
|
||||
{"key":2, "type":"Table", "name":"Sales"},
|
||||
{"key":3, "type":"Table", "name":"Period"},
|
||||
{"key":4, "type":"Table", "name":"Store"},
|
||||
{"key":11, "type":"Join", "name":"Product, Class"},
|
||||
{"key":12, "type":"Join", "name":"Period"},
|
||||
{"key":13, "type":"Join", "name":"Store"},
|
||||
{"key":21, "type":"Project", "name":"Product, Class"},
|
||||
{"key":31, "type":"Filter", "name":"Boston, Jan2014"},
|
||||
{"key":32, "type":"Filter", "name":"Boston, 2014"},
|
||||
{"key":41, "type":"Group", "name":"Sales"},
|
||||
{"key":42, "type":"Group", "name":"Total Sales"},
|
||||
{"key":51, "type":"Join", "name":"Product Name"},
|
||||
{"key":61, "type":"Sort", "name":"Product Name"},
|
||||
{"key":71, "type":"Export", "name":"File"}
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":1, "frompid":"OUT", "to":11, "topid":"L"},
|
||||
{"from":2, "frompid":"OUT", "to":11, "topid":"R"},
|
||||
{"from":3, "frompid":"OUT", "to":12, "topid":"R"},
|
||||
{"from":4, "frompid":"OUT", "to":13, "topid":"R"},
|
||||
{"from":11, "frompid":"M", "to":12, "topid":"L"},
|
||||
{"from":12, "frompid":"M", "to":13, "topid":"L"},
|
||||
{"from":13, "frompid":"M", "to":21},
|
||||
{"from":21, "frompid":"OUT", "to":31},
|
||||
{"from":21, "frompid":"OUT", "to":32},
|
||||
{"from":31, "frompid":"OUT", "to":41},
|
||||
{"from":32, "frompid":"OUT", "to":42},
|
||||
{"from":41, "frompid":"OUT", "to":51, "topid":"L"},
|
||||
{"from":42, "frompid":"OUT", "to":51, "topid":"R"},
|
||||
{"from":51, "frompid":"OUT", "to":61},
|
||||
{"from":61, "frompid":"OUT", "to":71}
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,468 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Data Visualization GoJS Sample</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive visualization of multi-dimensional data with HTML tooltips." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!--
|
||||
The div needs an explicit size or else we won't see anything.
|
||||
Lets also add a border to help see the edges.
|
||||
-->
|
||||
<style type="text/css">
|
||||
#myDiagramDiv {
|
||||
border: solid 1px black;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
margin-right: 12px;
|
||||
float: left;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
#controls {
|
||||
border: solid 1px gray;
|
||||
width: 250px;
|
||||
background: #ffffff;
|
||||
padding: 5px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#infoBoxHolder {
|
||||
z-index: 300;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
#infoBox {
|
||||
border: 1px solid #999;
|
||||
padding: 8px;
|
||||
background-color: whitesmoke;
|
||||
opacity: 0.9;
|
||||
position: relative;
|
||||
width: 170px;
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* this is known as the "clearfix" hack to allow
|
||||
floated objects to add to the height of a div */
|
||||
#infoBox:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
div.infoTitle {
|
||||
width: 100px;
|
||||
font-weight: normal;
|
||||
color: #787878;
|
||||
float: left;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
div.infoValues {
|
||||
width: 30px;
|
||||
text-align: right;
|
||||
float: right;
|
||||
}
|
||||
|
||||
label:hover, label:focus {
|
||||
background: #CEDFF2;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
var myDiagram;
|
||||
var myLocation = { // this controls the data properties used by data binding conversions
|
||||
x: "sepalLength",
|
||||
y: "sepalWidth"
|
||||
}
|
||||
var lastStroked = null; // this remembers the last highlight Shape
|
||||
|
||||
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
|
||||
|
||||
var myToolTip = $(go.HTMLInfo, {
|
||||
show: showToolTip,
|
||||
// do nothing on hide: This tooltip doesn't hide unless the mouse leaves the diagram
|
||||
})
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
"animationManager.initialAnimationStyle": go.AnimationManager.AnimateLocations,
|
||||
contentAlignment: go.Spot.Center, // content is always centered in the viewport
|
||||
autoScale: go.Diagram.Uniform, // scale always has all content fitting in the viewport
|
||||
"toolManager.hoverDelay": 10, // how quickly tooltips are shown
|
||||
isReadOnly: true, // don't let users modify anything
|
||||
mouseOver: doMouseOver, // this event handler is defined below
|
||||
click: doMouseOver // this event handler is defined below
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{
|
||||
selectable: false,
|
||||
toolTip: myToolTip
|
||||
},
|
||||
new go.Binding("location", "", function(nothing, elt) {
|
||||
return new go.Point(elt.data[myLocation.x] * 200,
|
||||
elt.data[myLocation.y] * 200)
|
||||
}),
|
||||
new go.AnimationTrigger("position", null, go.AnimationTrigger.Bundled),
|
||||
|
||||
$(go.Shape, "Hexagon",
|
||||
{
|
||||
name: "SHAPE",
|
||||
width: 20, height: 20,
|
||||
strokeWidth: 4, stroke: null
|
||||
},
|
||||
new go.Binding("fill", "species", function(name) {
|
||||
switch (name) {
|
||||
case "Iris-setosa": return "rgba(240, 120, 50, .6)";
|
||||
case "Iris-versicolor": return "rgba(240, 230, 120, .6)";
|
||||
case "Iris-virginica": return "rgba(125, 200, 120, .6)";
|
||||
}
|
||||
return "black";
|
||||
}))
|
||||
);
|
||||
|
||||
// This is the fundamental data set, taken from:
|
||||
// https://en.wikipedia.org/wiki/Iris_flower_data_set
|
||||
var irisData = [
|
||||
[5.1, 3.5, 1.4, 0.2, "Iris-setosa"],
|
||||
[4.9, 3.0, 1.4, 0.2, "Iris-setosa"],
|
||||
[4.7, 3.2, 1.3, 0.2, "Iris-setosa"],
|
||||
[4.6, 3.1, 1.5, 0.2, "Iris-setosa"],
|
||||
[5.0, 3.6, 1.4, 0.2, "Iris-setosa"],
|
||||
[5.4, 3.9, 1.7, 0.4, "Iris-setosa"],
|
||||
[4.6, 3.4, 1.4, 0.3, "Iris-setosa"],
|
||||
[5.0, 3.4, 1.5, 0.2, "Iris-setosa"],
|
||||
[4.4, 2.9, 1.4, 0.2, "Iris-setosa"],
|
||||
[4.9, 3.1, 1.5, 0.1, "Iris-setosa"],
|
||||
[5.4, 3.7, 1.5, 0.2, "Iris-setosa"],
|
||||
[4.8, 3.4, 1.6, 0.2, "Iris-setosa"],
|
||||
[4.8, 3.0, 1.4, 0.1, "Iris-setosa"],
|
||||
[4.3, 3.0, 1.1, 0.1, "Iris-setosa"],
|
||||
[5.8, 4.0, 1.2, 0.2, "Iris-setosa"],
|
||||
[5.7, 4.4, 1.5, 0.4, "Iris-setosa"],
|
||||
[5.4, 3.9, 1.3, 0.4, "Iris-setosa"],
|
||||
[5.1, 3.5, 1.4, 0.3, "Iris-setosa"],
|
||||
[5.7, 3.8, 1.7, 0.3, "Iris-setosa"],
|
||||
[5.1, 3.8, 1.5, 0.3, "Iris-setosa"],
|
||||
[5.4, 3.4, 1.7, 0.2, "Iris-setosa"],
|
||||
[5.1, 3.7, 1.5, 0.4, "Iris-setosa"],
|
||||
[4.6, 3.6, 1.0, 0.2, "Iris-setosa"],
|
||||
[5.1, 3.3, 1.7, 0.5, "Iris-setosa"],
|
||||
[4.8, 3.4, 1.9, 0.2, "Iris-setosa"],
|
||||
[5.0, 3.0, 1.6, 0.2, "Iris-setosa"],
|
||||
[5.0, 3.4, 1.6, 0.4, "Iris-setosa"],
|
||||
[5.2, 3.5, 1.5, 0.2, "Iris-setosa"],
|
||||
[5.2, 3.4, 1.4, 0.2, "Iris-setosa"],
|
||||
[4.7, 3.2, 1.6, 0.2, "Iris-setosa"],
|
||||
[4.8, 3.1, 1.6, 0.2, "Iris-setosa"],
|
||||
[5.4, 3.4, 1.5, 0.4, "Iris-setosa"],
|
||||
[5.2, 4.1, 1.5, 0.1, "Iris-setosa"],
|
||||
[5.5, 4.2, 1.4, 0.2, "Iris-setosa"],
|
||||
[4.9, 3.1, 1.5, 0.2, "Iris-setosa"],
|
||||
[5.0, 3.2, 1.2, 0.2, "Iris-setosa"],
|
||||
[5.5, 3.5, 1.3, 0.2, "Iris-setosa"],
|
||||
[4.9, 3.6, 1.4, 0.1, "Iris-setosa"],
|
||||
[4.4, 3.0, 1.3, 0.2, "Iris-setosa"],
|
||||
[5.1, 3.4, 1.5, 0.2, "Iris-setosa"],
|
||||
[5.0, 3.5, 1.3, 0.3, "Iris-setosa"],
|
||||
[4.5, 2.3, 1.3, 0.3, "Iris-setosa"],
|
||||
[4.4, 3.2, 1.3, 0.2, "Iris-setosa"],
|
||||
[5.0, 3.5, 1.6, 0.6, "Iris-setosa"],
|
||||
[5.1, 3.8, 1.9, 0.4, "Iris-setosa"],
|
||||
[4.8, 3.0, 1.4, 0.3, "Iris-setosa"],
|
||||
[5.1, 3.8, 1.6, 0.2, "Iris-setosa"],
|
||||
[4.6, 3.2, 1.4, 0.2, "Iris-setosa"],
|
||||
[5.3, 3.7, 1.5, 0.2, "Iris-setosa"],
|
||||
[5.0, 3.3, 1.4, 0.2, "Iris-setosa"],
|
||||
[7.0, 3.2, 4.7, 1.4, "Iris-versicolor"],
|
||||
[6.4, 3.2, 4.5, 1.5, "Iris-versicolor"],
|
||||
[6.9, 3.1, 4.9, 1.5, "Iris-versicolor"],
|
||||
[5.5, 2.3, 4.0, 1.3, "Iris-versicolor"],
|
||||
[6.5, 2.8, 4.6, 1.5, "Iris-versicolor"],
|
||||
[5.7, 2.8, 4.5, 1.3, "Iris-versicolor"],
|
||||
[6.3, 3.3, 4.7, 1.6, "Iris-versicolor"],
|
||||
[4.9, 2.4, 3.3, 1.0, "Iris-versicolor"],
|
||||
[6.6, 2.9, 4.6, 1.3, "Iris-versicolor"],
|
||||
[5.2, 2.7, 3.9, 1.4, "Iris-versicolor"],
|
||||
[5.0, 2.0, 3.5, 1.0, "Iris-versicolor"],
|
||||
[5.9, 3.0, 4.2, 1.5, "Iris-versicolor"],
|
||||
[6.0, 2.2, 4.0, 1.0, "Iris-versicolor"],
|
||||
[6.1, 2.9, 4.7, 1.4, "Iris-versicolor"],
|
||||
[5.6, 2.9, 3.6, 1.3, "Iris-versicolor"],
|
||||
[6.7, 3.1, 4.4, 1.4, "Iris-versicolor"],
|
||||
[5.6, 3.0, 4.5, 1.5, "Iris-versicolor"],
|
||||
[5.8, 2.7, 4.1, 1.0, "Iris-versicolor"],
|
||||
[6.2, 2.2, 4.5, 1.5, "Iris-versicolor"],
|
||||
[5.6, 2.5, 3.9, 1.1, "Iris-versicolor"],
|
||||
[5.9, 3.2, 4.8, 1.8, "Iris-versicolor"],
|
||||
[6.1, 2.8, 4.0, 1.3, "Iris-versicolor"],
|
||||
[6.3, 2.5, 4.9, 1.5, "Iris-versicolor"],
|
||||
[6.1, 2.8, 4.7, 1.2, "Iris-versicolor"],
|
||||
[6.4, 2.9, 4.3, 1.3, "Iris-versicolor"],
|
||||
[6.6, 3.0, 4.4, 1.4, "Iris-versicolor"],
|
||||
[6.8, 2.8, 4.8, 1.4, "Iris-versicolor"],
|
||||
[6.7, 3.0, 5.0, 1.7, "Iris-versicolor"],
|
||||
[6.0, 2.9, 4.5, 1.5, "Iris-versicolor"],
|
||||
[5.7, 2.6, 3.5, 1.0, "Iris-versicolor"],
|
||||
[5.5, 2.4, 3.8, 1.1, "Iris-versicolor"],
|
||||
[5.5, 2.4, 3.7, 1.0, "Iris-versicolor"],
|
||||
[5.8, 2.7, 3.9, 1.2, "Iris-versicolor"],
|
||||
[6.0, 2.7, 5.1, 1.6, "Iris-versicolor"],
|
||||
[5.4, 3.0, 4.5, 1.5, "Iris-versicolor"],
|
||||
[6.0, 3.4, 4.5, 1.6, "Iris-versicolor"],
|
||||
[6.7, 3.1, 4.7, 1.5, "Iris-versicolor"],
|
||||
[6.3, 2.3, 4.4, 1.3, "Iris-versicolor"],
|
||||
[5.6, 3.0, 4.1, 1.3, "Iris-versicolor"],
|
||||
[5.5, 2.5, 4.0, 1.3, "Iris-versicolor"],
|
||||
[5.5, 2.6, 4.4, 1.2, "Iris-versicolor"],
|
||||
[6.1, 3.0, 4.6, 1.4, "Iris-versicolor"],
|
||||
[5.8, 2.6, 4.0, 1.2, "Iris-versicolor"],
|
||||
[5.0, 2.3, 3.3, 1.0, "Iris-versicolor"],
|
||||
[5.6, 2.7, 4.2, 1.3, "Iris-versicolor"],
|
||||
[5.7, 3.0, 4.2, 1.2, "Iris-versicolor"],
|
||||
[5.7, 2.9, 4.2, 1.3, "Iris-versicolor"],
|
||||
[6.2, 2.9, 4.3, 1.3, "Iris-versicolor"],
|
||||
[5.1, 2.5, 3.0, 1.1, "Iris-versicolor"],
|
||||
[5.7, 2.8, 4.1, 1.3, "Iris-versicolor"],
|
||||
[6.3, 3.3, 6.0, 2.5, "Iris-virginica"],
|
||||
[5.8, 2.7, 5.1, 1.9, "Iris-virginica"],
|
||||
[7.1, 3.0, 5.9, 2.1, "Iris-virginica"],
|
||||
[6.3, 2.9, 5.6, 1.8, "Iris-virginica"],
|
||||
[6.5, 3.0, 5.8, 2.2, "Iris-virginica"],
|
||||
[7.6, 3.0, 6.6, 2.1, "Iris-virginica"],
|
||||
[4.9, 2.5, 4.5, 1.7, "Iris-virginica"],
|
||||
[7.3, 2.9, 6.3, 1.8, "Iris-virginica"],
|
||||
[6.7, 2.5, 5.8, 1.8, "Iris-virginica"],
|
||||
[7.2, 3.6, 6.1, 2.5, "Iris-virginica"],
|
||||
[6.5, 3.2, 5.1, 2.0, "Iris-virginica"],
|
||||
[6.4, 2.7, 5.3, 1.9, "Iris-virginica"],
|
||||
[6.8, 3.0, 5.5, 2.1, "Iris-virginica"],
|
||||
[5.7, 2.5, 5.0, 2.0, "Iris-virginica"],
|
||||
[5.8, 2.8, 5.1, 2.4, "Iris-virginica"],
|
||||
[6.4, 3.2, 5.3, 2.3, "Iris-virginica"],
|
||||
[6.5, 3.0, 5.5, 1.8, "Iris-virginica"],
|
||||
[7.7, 3.8, 6.7, 2.2, "Iris-virginica"],
|
||||
[7.7, 2.6, 6.9, 2.3, "Iris-virginica"],
|
||||
[6.0, 2.2, 5.0, 1.5, "Iris-virginica"],
|
||||
[6.9, 3.2, 5.7, 2.3, "Iris-virginica"],
|
||||
[5.6, 2.8, 4.9, 2.0, "Iris-virginica"],
|
||||
[7.7, 2.8, 6.7, 2.0, "Iris-virginica"],
|
||||
[6.3, 2.7, 4.9, 1.8, "Iris-virginica"],
|
||||
[6.7, 3.3, 5.7, 2.1, "Iris-virginica"],
|
||||
[7.2, 3.2, 6.0, 1.8, "Iris-virginica"],
|
||||
[6.2, 2.8, 4.8, 1.8, "Iris-virginica"],
|
||||
[6.1, 3.0, 4.9, 1.8, "Iris-virginica"],
|
||||
[6.4, 2.8, 5.6, 2.1, "Iris-virginica"],
|
||||
[7.2, 3.0, 5.8, 1.6, "Iris-virginica"],
|
||||
[7.4, 2.8, 6.1, 1.9, "Iris-virginica"],
|
||||
[7.9, 3.8, 6.4, 2.0, "Iris-virginica"],
|
||||
[6.4, 2.8, 5.6, 2.2, "Iris-virginica"],
|
||||
[6.3, 2.8, 5.1, 1.5, "Iris-virginica"],
|
||||
[6.1, 2.6, 5.6, 1.4, "Iris-virginica"],
|
||||
[7.7, 3.0, 6.1, 2.3, "Iris-virginica"],
|
||||
[6.3, 3.4, 5.6, 2.4, "Iris-virginica"],
|
||||
[6.4, 3.1, 5.5, 1.8, "Iris-virginica"],
|
||||
[6.0, 3.0, 4.8, 1.8, "Iris-virginica"],
|
||||
[6.9, 3.1, 5.4, 2.1, "Iris-virginica"],
|
||||
[6.7, 3.1, 5.6, 2.4, "Iris-virginica"],
|
||||
[6.9, 3.1, 5.1, 2.3, "Iris-virginica"],
|
||||
[5.8, 2.7, 5.1, 1.9, "Iris-virginica"],
|
||||
[6.8, 3.2, 5.9, 2.3, "Iris-virginica"],
|
||||
[6.7, 3.3, 5.7, 2.5, "Iris-virginica"],
|
||||
[6.7, 3.0, 5.2, 2.3, "Iris-virginica"],
|
||||
[6.3, 2.5, 5.0, 1.9, "Iris-virginica"],
|
||||
[6.5, 3.0, 5.2, 2.0, "Iris-virginica"],
|
||||
[6.2, 3.4, 5.4, 2.3, "Iris-virginica"],
|
||||
[5.9, 3.0, 5.1, 1.8, "Iris-virginica"]
|
||||
];
|
||||
|
||||
// now convert that data into an Array of JavaScript Objects,
|
||||
// to be used as the Model.nodeDataArray
|
||||
var array = [];
|
||||
for (var i = 0; i < irisData.length; i++) {
|
||||
var line = irisData[i];
|
||||
var data = {
|
||||
sepalLength: line[0],
|
||||
sepalWidth: line[1],
|
||||
petalLength: line[2],
|
||||
petalWidth: line[3],
|
||||
species: line[4]
|
||||
};
|
||||
array.push(data);
|
||||
}
|
||||
|
||||
// create the Model for the Diagram to display
|
||||
myDiagram.model = new go.Model(array);
|
||||
|
||||
|
||||
// Called when the mouse is over the diagram's background
|
||||
function doMouseOver(e) {
|
||||
if (e === undefined) e = myDiagram.lastInput;
|
||||
var doc = e.documentPoint;
|
||||
// find all Nodes that are within 100 units
|
||||
var list = myDiagram.findObjectsNear(doc, 100, null, function(x) { return x instanceof go.Node; });
|
||||
// now find the one that is closest to e.documentPoint
|
||||
var closest = null;
|
||||
var closestDist = 999999999;
|
||||
list.each(function(node) {
|
||||
var dist = doc.distanceSquaredPoint(node.getDocumentPoint(go.Spot.Center));
|
||||
if (dist < closestDist) {
|
||||
closestDist = dist;
|
||||
closest = node;
|
||||
}
|
||||
});
|
||||
showToolTip(closest, myDiagram);
|
||||
}
|
||||
|
||||
// Called with a Node (or null) that the mouse is over or near
|
||||
function showToolTip(obj, diagram) {
|
||||
if (obj !== null) {
|
||||
var node = obj.part;
|
||||
var e = diagram.lastInput;
|
||||
var shape = node.findObject("SHAPE");
|
||||
shape.stroke = "white";
|
||||
if (lastStroked !== null && lastStroked !== shape) lastStroked.stroke = null;
|
||||
lastStroked = shape;
|
||||
updateInfoBox(e.viewPoint, node.data);
|
||||
} else {
|
||||
if (lastStroked !== null) lastStroked.stroke = null;
|
||||
lastStroked = null;
|
||||
document.getElementById("infoBoxHolder").innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the infoBox is momentarily hidden if the user tries to mouse over it
|
||||
var infoBoxH = document.getElementById("infoBoxHolder");
|
||||
infoBoxH.addEventListener("mousemove", function() {
|
||||
var box = document.getElementById("infoBoxHolder");
|
||||
box.style.left = parseInt(box.style.left) + "px";
|
||||
box.style.top = parseInt(box.style.top) + 30 + "px";
|
||||
}, false);
|
||||
|
||||
var diagramDiv = document.getElementById("myDiagramDiv");
|
||||
// Make sure the infoBox is hidden when the mouse is not over the Diagram
|
||||
diagramDiv.addEventListener("mouseout", function(e) {
|
||||
if (lastStroked !== null) lastStroked.stroke = null;
|
||||
lastStroked = null;
|
||||
|
||||
var infoBox = document.getElementById("infoBox");
|
||||
var elem = document.elementFromPoint(e.clientX, e.clientY);
|
||||
if (elem !== null && (elem === infoBox || elem.parentNode === infoBox)) {
|
||||
var box = document.getElementById("infoBoxHolder");
|
||||
box.style.left = parseInt(box.style.left) + "px";
|
||||
box.style.top = parseInt(box.style.top) + 30 + "px";
|
||||
} else {
|
||||
var box = document.getElementById("infoBoxHolder");
|
||||
box.innerHTML = "";
|
||||
}
|
||||
}, false);
|
||||
} // end init
|
||||
|
||||
// This function is called to update the tooltip information
|
||||
// depending on the bound data of the Node that is closest to the pointer.
|
||||
function updateInfoBox(mousePt, data) {
|
||||
var box = document.getElementById("infoBoxHolder");
|
||||
box.innerHTML = "";
|
||||
var infobox = document.createElement("div");
|
||||
infobox.id = "infoBox";
|
||||
box.appendChild(infobox);
|
||||
for (var i = 0; i < 9; i++) {
|
||||
var child = document.createElement("div");
|
||||
switch (i) {
|
||||
case 0: child.textContent = data.species; break;
|
||||
case 1: child.className = "infoTitle"; child.textContent = "Sepal Length"; break;
|
||||
case 2: child.className = "infoValues"; child.textContent = data.sepalLength; break;
|
||||
case 3: child.className = "infoTitle"; child.textContent = "Sepal Width"; break;
|
||||
case 4: child.className = "infoValues"; child.textContent = data.sepalWidth; break;
|
||||
case 5: child.className = "infoTitle"; child.textContent = "Petal Length"; break;
|
||||
case 6: child.className = "infoValues"; child.textContent = data.petalLength; break;
|
||||
case 7: child.className = "infoTitle"; child.textContent = "Petal Width"; break;
|
||||
case 8: child.className = "infoValues"; child.textContent = data.petalWidth; break;
|
||||
}
|
||||
infobox.appendChild(child);
|
||||
}
|
||||
box.style.left = mousePt.x + 30 + "px";
|
||||
box.style.top = mousePt.y + 20 + "px";
|
||||
}
|
||||
|
||||
// This function is called which a radio button is pressed.
|
||||
// It changes the value of a variable that the node template's location conversion function uses.
|
||||
// It then updates all target bindings, causing all node locations to change.
|
||||
function changeAxes(e) {
|
||||
var value = e.value;
|
||||
if (e.name === "X") {
|
||||
myLocation.x = value;
|
||||
} else {
|
||||
myLocation.y = value;
|
||||
}
|
||||
myDiagram.startTransaction("updateBindings");
|
||||
myDiagram.updateAllTargetBindings();
|
||||
myDiagram.commitTransaction("updateBindings");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="display: inline-block">
|
||||
|
||||
<!--The DIV for the Diagram needs an explicit size or else we won't see anything.
|
||||
Also add a border to help see the edges. -->
|
||||
<div id="myDiagramDiv" style="background-color: #3D3D3D; border: solid 1px black; width:500px; height:500px"></div>
|
||||
<!-- This sibling of the Diagram provides information when the mouse is near a Diagram Node -->
|
||||
<div id="infoBoxHolder">
|
||||
<!-- Initially Empty, it is populated when updateInfoBox is called -->
|
||||
</div>
|
||||
|
||||
<div id="controls">
|
||||
<div style="border: solid 1px gray; float: left; padding: 2px;">
|
||||
<p style="text-align: center;">X-axis</p>
|
||||
<hr/>
|
||||
<input type="radio" name="X" onclick="changeAxes(this)" value="sepalLength" id="SLx" checked/>
|
||||
<label for="SLx">Sepal Length</label><br/>
|
||||
<input type="radio" name="X" onclick="changeAxes(this)" value="sepalWidth" id="SWx" />
|
||||
<label for="SWx">Sepal Width</label><br/>
|
||||
<input type="radio" name="X" onclick="changeAxes(this)" value="petalLength" id="PLx"/>
|
||||
<label for="PLx">Petal Length</label><br/>
|
||||
<input type="radio" name="X" onclick="changeAxes(this)" value="petalWidth" id="PWx"/>
|
||||
<label for="PWx">Petal Width</label><br/>
|
||||
</div>
|
||||
<div style="border: solid 1px gray; float: left; margin-left: 10px; padding: 2px;">
|
||||
<p style="text-align: center;">Y-axis</p>
|
||||
<hr/>
|
||||
<input type="radio" name="Y" onclick="changeAxes(this)" value="sepalLength" id="SLy"/>
|
||||
<label for="SLy">Sepal Length</label><br/>
|
||||
<input type="radio" name="Y" onclick="changeAxes(this)" value="sepalWidth" id="SWy" checked/>
|
||||
<label for="SWy">Sepal Width</label><br/>
|
||||
<input type="radio" name="Y" onclick="changeAxes(this)" value="petalLength" id="PLy"/>
|
||||
<label for="PLy">Petal Length</label><br/>
|
||||
<input type="radio" name="Y" onclick="changeAxes(this)" value="petalWidth" id="PWy"/>
|
||||
<label for="PWy">Petal Width</label><br/>
|
||||
</div>
|
||||
|
||||
<div id="description" style="float: left;">
|
||||
<p>This sample gives an example of a Diagram interacting with other HTML elements on the page.</p>
|
||||
<p>As the mouse moves over the diagram, a formatted HTML DIV element displays information about the nearest Node.</p>
|
||||
<p>The data displayed is from the <a href="https://en.wikipedia.org/wiki/Iris_flower_data_set">Iris flower data set</a>,
|
||||
describing the variations in dimensions of three related flower species.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,351 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Decision Tree</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive decision diagram with automatic expansion as the user makes choices." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,500' rel='stylesheet' type='text/css'>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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 name or refer to the DIV HTML element
|
||||
{
|
||||
initialContentAlignment: go.Spot.Left,
|
||||
allowSelect: false, // the user cannot select any part
|
||||
// create a TreeLayout for the decision tree
|
||||
layout: $(go.TreeLayout)
|
||||
});
|
||||
|
||||
// custom behavior for expanding/collapsing half of the subtree from a node
|
||||
function buttonExpandCollapse(e, port) {
|
||||
var node = port.part;
|
||||
node.diagram.startTransaction("expand/collapse");
|
||||
var portid = port.portId;
|
||||
node.findLinksOutOf(portid).each(function(l) {
|
||||
if (l.visible) {
|
||||
// collapse whole subtree recursively
|
||||
collapseTree(node, portid);
|
||||
} else {
|
||||
// only expands immediate children and their links
|
||||
l.visible = true;
|
||||
var n = l.getOtherNode(node);
|
||||
if (n !== null) {
|
||||
n.location = node.getDocumentPoint(go.Spot.TopRight);
|
||||
n.visible = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
myDiagram.toolManager.hideToolTip();
|
||||
node.diagram.commitTransaction("expand/collapse");
|
||||
}
|
||||
|
||||
// recursive function for collapsing complete subtree
|
||||
function collapseTree(node, portid) {
|
||||
node.findLinksOutOf(portid).each(function(l) {
|
||||
l.visible = false;
|
||||
var n = l.getOtherNode(node);
|
||||
if (n !== null) {
|
||||
n.visible = false;
|
||||
collapseTree(n, null); // null means all links, not just for a particular portId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// get the text for the tooltip from the data on the object being hovered over
|
||||
function tooltipTextConverter(data) {
|
||||
var str = "";
|
||||
var e = myDiagram.lastInput;
|
||||
var currobj = e.targetObject;
|
||||
if (currobj !== null && (currobj.name === "ButtonA" ||
|
||||
(currobj.panel !== null && currobj.panel.name === "ButtonA"))) {
|
||||
str = data.aToolTip;
|
||||
} else {
|
||||
str = data.bToolTip;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// define tooltips for buttons
|
||||
var tooltipTemplate =
|
||||
$("ToolTip",
|
||||
{ "Border.fill": "whitesmoke", "Border.stroke": "lightgray" },
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "8pt sans-serif",
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
desiredSize: new go.Size(200, NaN),
|
||||
alignment: go.Spot.Center,
|
||||
margin: 6
|
||||
},
|
||||
new go.Binding("text", "", tooltipTextConverter))
|
||||
);
|
||||
|
||||
// define the Node template for non-leaf nodes
|
||||
myDiagram.nodeTemplateMap.add("decision",
|
||||
$(go.Node, "Auto",
|
||||
new go.Binding("text", "key"),
|
||||
// define the node's outer shape, which will surround the Horizontal Panel
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: "whitesmoke", stroke: "lightgray" }),
|
||||
// define a horizontal Panel to place the node's text alongside the buttons
|
||||
$(go.Panel, "Horizontal",
|
||||
$(go.TextBlock,
|
||||
{ font: "30px Roboto, sans-serif", margin: 5 },
|
||||
new go.Binding("text", "key")),
|
||||
// define a vertical panel to place the node's two buttons one above the other
|
||||
$(go.Panel, "Vertical",
|
||||
{ defaultStretch: go.GraphObject.Fill, margin: 3 },
|
||||
$("Button", // button A
|
||||
{
|
||||
name: "ButtonA",
|
||||
click: buttonExpandCollapse,
|
||||
toolTip: tooltipTemplate
|
||||
},
|
||||
new go.Binding("portId", "a"),
|
||||
$(go.TextBlock,
|
||||
{ font: '500 16px Roboto, sans-serif' },
|
||||
new go.Binding("text", "aText"))
|
||||
), // end button A
|
||||
$("Button", // button B
|
||||
{
|
||||
name: "ButtonB",
|
||||
click: buttonExpandCollapse,
|
||||
toolTip: tooltipTemplate
|
||||
},
|
||||
new go.Binding("portId", "b"),
|
||||
$(go.TextBlock,
|
||||
{ font: '500 16px Roboto, sans-serif' },
|
||||
new go.Binding("text", "bText"))
|
||||
) // end button B
|
||||
) // end Vertical Panel
|
||||
) // end Horizontal Panel
|
||||
)); // end Node and call to add
|
||||
|
||||
// define the Node template for leaf nodes
|
||||
myDiagram.nodeTemplateMap.add("personality",
|
||||
$(go.Node, "Auto",
|
||||
new go.Binding("text", "key"),
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: "whitesmoke", stroke: "lightgray" }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: '13px Roboto, sans-serif',
|
||||
wrap: go.TextBlock.WrapFit, desiredSize: new go.Size(200, NaN), margin: 5
|
||||
},
|
||||
new go.Binding("text", "text"))
|
||||
));
|
||||
|
||||
// define the only Link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, go.Link.Orthogonal, // the whole link panel
|
||||
{ fromPortId: "" },
|
||||
new go.Binding("fromPortId", "fromport"),
|
||||
$(go.Shape, // the link shape
|
||||
{ stroke: "lightblue", strokeWidth: 2 })
|
||||
);
|
||||
|
||||
// create the model for the decision tree
|
||||
var model =
|
||||
$(go.GraphLinksModel,
|
||||
{ linkFromPortIdProperty: "fromport" });
|
||||
// set up the model with the node and link data
|
||||
makeNodes(model);
|
||||
makeLinks(model);
|
||||
myDiagram.model = model;
|
||||
|
||||
// make all but the start node invisible
|
||||
myDiagram.nodes.each(function(n) {
|
||||
if (n.text !== "Start") n.visible = false;
|
||||
});
|
||||
myDiagram.links.each(function(l) {
|
||||
l.visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
function makeNodes(model) {
|
||||
var nodeDataArray = [
|
||||
{ key: "Start" }, // the root node
|
||||
|
||||
// intermediate nodes: decisions on personality characteristics
|
||||
{ key: "I" },
|
||||
{ key: "E" },
|
||||
|
||||
{ key: "IN" },
|
||||
{ key: "IS" },
|
||||
{ key: "EN" },
|
||||
{ key: "ES" },
|
||||
|
||||
{ key: "INT" },
|
||||
{ key: "INF" },
|
||||
{ key: "IST" },
|
||||
{ key: "ISF" },
|
||||
{ key: "ENT" },
|
||||
{ key: "ENF" },
|
||||
{ key: "EST" },
|
||||
{ key: "ESF" },
|
||||
|
||||
// terminal nodes: the personality descriptions
|
||||
{
|
||||
key: "INTJ",
|
||||
text: "INTJ: Scientist\nThe most self-confident of all types. They focus on possibilities and use empirical logic to think about the future. They prefer that events and people serve some positive use. 1% of population."
|
||||
},
|
||||
{
|
||||
key: "INTP",
|
||||
text: "INTP: Architect\nAn architect of ideas, number systems, computer languages, and many other concepts. They exhibit great precision in thought and language. 1% of the population."
|
||||
},
|
||||
{
|
||||
key: "INFJ",
|
||||
text: "INFJ: Author\nFocus on possibilities. Place emphasis on values and come to decisions easily. They have a strong drive to contribute to the welfare of others. 1% of population."
|
||||
},
|
||||
{
|
||||
key: "INFP",
|
||||
text: "INFP: Questor\nPresent a calm and pleasant face to the world. Although they seem reserved, they are actually very idealistic and care passionately about a few special people or a cause. 1% of the population."
|
||||
},
|
||||
{
|
||||
key: "ISTJ",
|
||||
text: "ISTJ: Trustee\nISTJs like organized lives. They are dependable and trustworthy, as they dislike chaos and work on a task until completion. They prefer to deal with facts rather than emotions. 6% of the population."
|
||||
},
|
||||
{
|
||||
key: "ISTP",
|
||||
text: "ISTP: Artisan\nISTPs are quiet people who are very capable at analyzing how things work. Though quiet, they can be influential, with their seclusion making them all the more skilled. 17% of the population."
|
||||
},
|
||||
{
|
||||
key: "ISFJ",
|
||||
text: "ISFJ: Conservator\nISFJs are not particularly social and tend to be most concerned with maintaining order in their lives. They are dutiful, respectful towards, and interested in others, though they are often shy. They are, therefore, trustworthy, but not bossy. 6% of the population."
|
||||
},
|
||||
{
|
||||
key: "ISFP",
|
||||
text: "ISFP: Author\nFocus on possibilities. Place emphasis on values and come to decisions easily. They have a strong drive to contribute to the welfare of others. 1% of population."
|
||||
},
|
||||
{
|
||||
key: "ENTJ",
|
||||
text: "ENTJ: Fieldmarshal\nThe driving force of this personality is to lead. They like to impose structure and harness people to work towards distant goals. They reject inefficiency. 5% of the population."
|
||||
},
|
||||
{
|
||||
key: "ENTP",
|
||||
text: "ENTP: Inventor\nExercise their ingenuity by dealing with social, physical, and mechanical relationships. They are always sensitive to future possibilities. 5% of the population."
|
||||
},
|
||||
{
|
||||
key: "ENFJ",
|
||||
text: "ENFJ: Pedagogue\nExcellent leaders; they are charismatic and never doubt that others will follow them and do as they ask. They place a high value on cooperation. 5% of the population."
|
||||
},
|
||||
{
|
||||
key: "ENFP",
|
||||
text: "ENFP: Journalist\nPlace significance in everyday occurrences. They have great ability to understand the motives of others. They see life as a great drama. They have a great impact on others. 5% of the population."
|
||||
},
|
||||
{
|
||||
key: "ESTJ",
|
||||
text: "ESTJ: Administrator\nESTJs are pragmatic, and thus well-suited for business or administrative roles. They are traditionalists and conservatives, believing in the status quo. 13% of the population."
|
||||
},
|
||||
{
|
||||
key: "ESTP",
|
||||
text: "ESTP: Promoter\nESTPs tend to manipulate others in order to attain access to the finer aspects of life. However, they enjoy heading to such places with others. They are social and outgoing and are well-connected. 13% of the population."
|
||||
},
|
||||
{
|
||||
key: "ESFJ",
|
||||
text: "ESFJ: Seller\nESFJs tend to be social and concerned for others. They follow tradition and enjoy a structured community environment. Always magnanimous towards others, they expect the same respect and appreciation themselves. 13% of the population."
|
||||
},
|
||||
{
|
||||
key: "ESFP",
|
||||
text: "ESFP: Entertainer\nThe mantra of the ESFP would be \"Carpe Diem.\" They enjoy life to the fullest. They do not, thus, like routines and long-term goals. In general, they are very concerned with others and tend to always try to help others, often perceiving well their needs. 13% of the population."
|
||||
}
|
||||
];
|
||||
|
||||
// Provide the same choice information for all of the nodes on each level.
|
||||
// The level is implicit in the number of characters in the Key, except for the root node.
|
||||
// In a different application, there might be different choices for each node, so the initialization would be above, where the Info's are created.
|
||||
// But for this application, it makes sense to share the initialization code based on tree level.
|
||||
for (var i = 0; i < nodeDataArray.length; i++) {
|
||||
var d = nodeDataArray[i];
|
||||
if (d.key === "Start") {
|
||||
d.category = "decision";
|
||||
d.a = "I";
|
||||
d.aText = "Introversion";
|
||||
d.aToolTip = "The Introvert is “territorial” and desires space and solitude to recover energy. Introverts enjoy solitary activities such as reading and meditating. 25% of the population.";
|
||||
d.b = "E";
|
||||
d.bText = "Extraversion";
|
||||
d.bToolTip = "The Extravert is “sociable” and is energized by the presence of other people. Extraverts experience loneliness when not in contact with others. 75% of the population.";
|
||||
} else {
|
||||
switch (d.key.length) {
|
||||
case 1:
|
||||
d.category = "decision";
|
||||
d.a = "N";
|
||||
d.aText = "Intuition";
|
||||
d.aToolTip = "The “intuitive” person bases their lives on predictions and ingenuity. They consider the future and enjoy planning ahead. 25% of the population.";
|
||||
d.b = "S";
|
||||
d.bText = "Sensing";
|
||||
d.bToolTip = "The “sensing” person bases their life on facts, thinking primarily of their present situation. They are realistic and practical. 75% of the population.";
|
||||
break;
|
||||
case 2:
|
||||
d.category = "decision";
|
||||
d.a = "T";
|
||||
d.aText = "Thinking";
|
||||
d.aToolTip = "The “thinking” person bases their decisions on facts and without personal bias. They are more comfortable with making impersonal judgments. 50% of the population.";
|
||||
d.b = "F";
|
||||
d.bText = "Feeling";
|
||||
d.bToolTip = "The “feeling” person bases their decisions on personal experience and emotion. They make their emotions very visible. 50% of the population.";
|
||||
break;
|
||||
case 3:
|
||||
d.category = "decision";
|
||||
d.a = "J";
|
||||
d.aText = "Judgment";
|
||||
d.aToolTip = "The “judging” person enjoys closure. They establish deadlines and take them seriously. They despise being late. 50% of the population.";
|
||||
d.b = "P";
|
||||
d.bText = "Perception";
|
||||
d.bToolTip = "The “perceiving” person likes to keep options open and fluid. They have little regard for deadlines. Dislikes making decisions unless they are completely sure they are right. 50% of the population.";
|
||||
break;
|
||||
default:
|
||||
d.category = "personality";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
model.nodeDataArray = nodeDataArray;
|
||||
}
|
||||
|
||||
// The key strings implicitly hold the relationship information, based on their spellings.
|
||||
// Other than the root node ("Start"), each node's key string minus its last letter is the
|
||||
// key to the "parent" node.
|
||||
function makeLinks(model) {
|
||||
var linkDataArray = [];
|
||||
var nda = model.nodeDataArray;
|
||||
for (var i = 0; i < nda.length; i++) {
|
||||
var key = nda[i].key;
|
||||
if (key === "Start" || key.length === 0) continue;
|
||||
// e.g., if key=="INTJ", we want: prefix="INT" and letter="J"
|
||||
var prefix = key.slice(0, key.length - 1);
|
||||
var letter = key.charAt(key.length - 1);
|
||||
if (prefix.length === 0) prefix = "Start";
|
||||
var obj = { from: prefix, fromport: letter, to: key };
|
||||
linkDataArray.push(obj);
|
||||
}
|
||||
model.linkDataArray = linkDataArray;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
||||
<p>
|
||||
This sample allows a user to make progressive decisions about personality types.
|
||||
</p>
|
||||
<p>
|
||||
There are two kinds of nodes, so there are two node templates ("decision" and "personality")
|
||||
that determine the appearance and behavior of each <a>Node</a>.
|
||||
</p>
|
||||
<p>
|
||||
The "decision" template displays the abbreviated personality type and two choice buttons, all surrounded by a figure.
|
||||
Clicking a button will either expand the choice or will collapse all nodes leading from that choice.
|
||||
</p>
|
||||
<p>
|
||||
The "personality" template displays the personality descriptions, as the "leaf" nodes for the tree.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,393 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Graph Distances and Paths</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive diagram showing all distances from a node, and highlighting all paths between two nodes." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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,
|
||||
padding: 10,
|
||||
contentAlignment: go.Spot.Center,
|
||||
layout: $(go.ForceDirectedLayout, { defaultSpringLength: 10 }),
|
||||
maxSelectionCount: 2
|
||||
});
|
||||
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Horizontal",
|
||||
{
|
||||
locationSpot: go.Spot.Center, // Node.location is the center of the Shape
|
||||
locationObjectName: "SHAPE",
|
||||
selectionAdorned: false,
|
||||
selectionChanged: nodeSelectionChanged
|
||||
},
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, "Ellipse",
|
||||
{
|
||||
name: "SHAPE",
|
||||
fill: "lightgray", // default value, but also data-bound
|
||||
stroke: "transparent", // modified by highlighting
|
||||
strokeWidth: 2,
|
||||
desiredSize: new go.Size(30, 30),
|
||||
portId: ""
|
||||
}, // so links will go to the shape, not the whole node
|
||||
new go.Binding("fill", "isSelected", function(s, obj) { return s ? "red" : obj.part.data.color; }).ofObject()),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "distance", function(d) { if (d === Infinity) return "INF"; else return d | 0; }))),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text")));
|
||||
|
||||
// define the Link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
selectable: false, // links cannot be selected by the user
|
||||
curve: go.Link.Bezier,
|
||||
layerName: "Background" // don't cross in front of any nodes
|
||||
},
|
||||
$(go.Shape, // this shape only shows when it isHighlighted
|
||||
{ isPanelMain: true, stroke: null, strokeWidth: 5 },
|
||||
new go.Binding("stroke", "isHighlighted", function(h) { return h ? "red" : null; }).ofObject()),
|
||||
$(go.Shape,
|
||||
// mark each Shape to get the link geometry with isPanelMain: true
|
||||
{ isPanelMain: true, stroke: "black", strokeWidth: 1 },
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.Shape, { toArrow: "Standard" })
|
||||
);
|
||||
|
||||
// Override the clickSelectingTool's standardMouseSelect
|
||||
// If less than 2 nodes are selected, always add to the selection
|
||||
myDiagram.toolManager.clickSelectingTool.standardMouseSelect = function() {
|
||||
var diagram = this.diagram;
|
||||
if (diagram === null || !diagram.allowSelect) return;
|
||||
var e = diagram.lastInput;
|
||||
var count = diagram.selection.count;
|
||||
var curobj = diagram.findPartAt(e.documentPoint, false);
|
||||
if (curobj !== null) {
|
||||
if (count < 2) { // add the part to the selection
|
||||
if (!curobj.isSelected) {
|
||||
var part = curobj;
|
||||
if (part !== null) part.isSelected = true;
|
||||
}
|
||||
} else {
|
||||
if (!curobj.isSelected) {
|
||||
var part = curobj;
|
||||
if (part !== null) diagram.select(part);
|
||||
}
|
||||
}
|
||||
} else if (e.left && !(e.control || e.meta) && !e.shift) {
|
||||
// left click on background with no modifier: clear selection
|
||||
diagram.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
generateGraph();
|
||||
|
||||
// select two nodes that connect from the first one to the second one
|
||||
var num = myDiagram.model.nodeDataArray.length;
|
||||
var node1 = null;
|
||||
var node2 = null;
|
||||
for (var i = 0; i < num; i++) {
|
||||
node1 = myDiagram.findNodeForKey(i);
|
||||
var distances = findDistances(node1);
|
||||
for (var j = 0; j < num; j++) {
|
||||
node2 = myDiagram.findNodeForKey(j);
|
||||
var dist = distances.get(node2);
|
||||
if (dist > 1 && dist < Infinity) {
|
||||
node1.isSelected = true;
|
||||
node2.isSelected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (myDiagram.selection.count > 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
function generateGraph() {
|
||||
var names = [
|
||||
"Joshua", "Kathryn", "Robert", "Jason", "Scott", "Betsy", "John",
|
||||
"Walter", "Gabriel", "Simon", "Emily", "Tina", "Elena", "Samuel",
|
||||
"Jacob", "Michael", "Juliana", "Natalie", "Grace", "Ashley", "Dylan"
|
||||
];
|
||||
|
||||
var nodeDataArray = [];
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
nodeDataArray.push({ key: i, text: names[i], color: go.Brush.randomColor(128, 240) });
|
||||
}
|
||||
|
||||
var linkDataArray = [];
|
||||
var num = nodeDataArray.length;
|
||||
for (var i = 0; i < num * 2; i++) {
|
||||
var a = Math.floor(Math.random() * num);
|
||||
var b = Math.floor(Math.random() * num / 4) + 1;
|
||||
linkDataArray.push({ from: a, to: (a + b) % num, color: go.Brush.randomColor(0, 127) });
|
||||
}
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
}
|
||||
|
||||
|
||||
// There are three bits of functionality here:
|
||||
// 1: findDistances(Node) computes the distance of each Node from the given Node.
|
||||
// This function is used by showDistances to update the model data.
|
||||
// 2: findShortestPath(Node, Node) finds a shortest path from one Node to another.
|
||||
// This uses findDistances. This is used by highlightShortestPath.
|
||||
// 3: collectAllPaths(Node, Node) produces a collection of all paths from one Node to another.
|
||||
// This is used by listAllPaths. The result is remembered in a global variable
|
||||
// which is used by highlightSelectedPath. This does not depend on findDistances.
|
||||
|
||||
// Returns a Map of Nodes with distance values from the given source Node.
|
||||
// Assumes all links are unidirectional.
|
||||
function findDistances(source) {
|
||||
var diagram = source.diagram;
|
||||
// keep track of distances from the source node
|
||||
var distances = new go.Map(/*go.Node, "number"*/);
|
||||
// all nodes start with distance Infinity
|
||||
var nit = diagram.nodes;
|
||||
while (nit.next()) {
|
||||
var n = nit.value;
|
||||
distances.set(n, Infinity);
|
||||
}
|
||||
// the source node starts with distance 0
|
||||
distances.set(source, 0);
|
||||
// keep track of nodes for which we have set a non-Infinity distance,
|
||||
// but which we have not yet finished examining
|
||||
var seen = new go.Set(/*go.Node*/);
|
||||
seen.add(source);
|
||||
|
||||
// keep track of nodes we have finished examining;
|
||||
// this avoids unnecessary traversals and helps keep the SEEN collection small
|
||||
var finished = new go.Set(/*go.Node*/);
|
||||
while (seen.count > 0) {
|
||||
// look at the unfinished node with the shortest distance so far
|
||||
var least = leastNode(seen, distances);
|
||||
var leastdist = distances.get(least);
|
||||
// by the end of this loop we will have finished examining this LEAST node
|
||||
seen.delete(least);
|
||||
finished.add(least);
|
||||
// look at all Links connected with this node
|
||||
var it = least.findLinksOutOf();
|
||||
while (it.next()) {
|
||||
var link = it.value;
|
||||
var neighbor = link.getOtherNode(least);
|
||||
// skip nodes that we have finished
|
||||
if (finished.has(neighbor)) continue;
|
||||
var neighbordist = distances.get(neighbor);
|
||||
// assume "distance" along a link is unitary, but could be any non-negative number.
|
||||
var dist = leastdist + 1; //Math.sqrt(least.location.distanceSquaredPoint(neighbor.location));
|
||||
if (dist < neighbordist) {
|
||||
// if haven't seen that node before, add it to the SEEN collection
|
||||
if (neighbordist === Infinity) {
|
||||
seen.add(neighbor);
|
||||
}
|
||||
// record the new best distance so far to that node
|
||||
distances.set(neighbor, dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return distances;
|
||||
}
|
||||
|
||||
// This helper function finds a Node in the given collection that has the smallest distance.
|
||||
function leastNode(coll, distances) {
|
||||
var bestdist = Infinity;
|
||||
var bestnode = null;
|
||||
var it = coll.iterator;
|
||||
while (it.next()) {
|
||||
var n = it.value;
|
||||
var dist = distances.get(n);
|
||||
if (dist < bestdist) {
|
||||
bestdist = dist;
|
||||
bestnode = n;
|
||||
}
|
||||
}
|
||||
return bestnode;
|
||||
}
|
||||
|
||||
|
||||
// Find a path that is shortest from the BEGIN node to the END node.
|
||||
// (There might be more than one, and there might be none.)
|
||||
function findShortestPath(begin, end) {
|
||||
// compute and remember the distance of each node from the BEGIN node
|
||||
distances = findDistances(begin);
|
||||
|
||||
// now find a path from END to BEGIN, always choosing the adjacent Node with the lowest distance
|
||||
var path = new go.List();
|
||||
path.add(end);
|
||||
while (end !== null) {
|
||||
var next = leastNode(end.findNodesInto(), distances);
|
||||
if (next !== null) {
|
||||
if (distances.get(next) < distances.get(end)) {
|
||||
path.add(next); // making progress towards the beginning
|
||||
} else {
|
||||
next = null; // nothing better found -- stop looking
|
||||
}
|
||||
}
|
||||
end = next;
|
||||
}
|
||||
// reverse the list to start at the node closest to BEGIN that is on the path to END
|
||||
// NOTE: if there's no path from BEGIN to END, the first node won't be BEGIN!
|
||||
path.reverse();
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
// Recursively walk the graph starting from the BEGIN node;
|
||||
// when reaching the END node remember the list of nodes along the current path.
|
||||
// Finally return the collection of paths, which may be empty.
|
||||
// This assumes all links are unidirectional.
|
||||
function collectAllPaths(begin, end) {
|
||||
var stack = new go.List(/*go.Node*/);
|
||||
var coll = new go.List(/*go.List*/);
|
||||
|
||||
function find(source, end) {
|
||||
source.findNodesOutOf().each(function(n) {
|
||||
if (n === source) return; // ignore reflexive links
|
||||
if (n === end) { // success
|
||||
var path = stack.copy();
|
||||
path.add(end); // finish the path at the end node
|
||||
coll.add(path); // remember the whole path
|
||||
} else if (!stack.has(n)) { // inefficient way to check having visited
|
||||
stack.add(n); // remember that we've been here for this path (but not forever)
|
||||
find(n, end);
|
||||
stack.removeAt(stack.count - 1);
|
||||
} // else might be a cycle
|
||||
});
|
||||
}
|
||||
|
||||
stack.add(begin); // start the path at the begin node
|
||||
find(begin, end);
|
||||
return coll;
|
||||
}
|
||||
|
||||
// Return a string representation of a path for humans to read.
|
||||
function pathToString(path) {
|
||||
var s = path.length + ": ";
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
if (i > 0) s += " -- ";
|
||||
s += path.get(i).data.text;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
// When a node is selected show distances from the first selected node.
|
||||
// When a second node is selected, highlight the shortest path between two selected nodes.
|
||||
// If a node is deselected, clear all highlights.
|
||||
function nodeSelectionChanged(node) {
|
||||
var diagram = node.diagram;
|
||||
if (diagram === null) return;
|
||||
diagram.clearHighlighteds();
|
||||
if (node.isSelected) {
|
||||
// when there is a selection made, always clear out the list of all paths
|
||||
var sel = document.getElementById("myPaths");
|
||||
sel.innerHTML = "";
|
||||
|
||||
// show the distance for each node from the selected node
|
||||
var begin = diagram.selection.first();
|
||||
showDistances(begin);
|
||||
|
||||
if (diagram.selection.count === 2) {
|
||||
var end = node; // just became selected
|
||||
|
||||
// highlight the shortest path
|
||||
highlightShortestPath(begin, end);
|
||||
|
||||
// list all paths
|
||||
listAllPaths(begin, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Have each node show how far it is from the BEGIN node.
|
||||
function showDistances(begin) {
|
||||
// compute and remember the distance of each node from the BEGIN node
|
||||
distances = findDistances(begin);
|
||||
|
||||
// show the distance on each node
|
||||
var it = distances.iterator;
|
||||
while (it.next()) {
|
||||
var n = it.key;
|
||||
var dist = it.value;
|
||||
myDiagram.model.setDataProperty(n.data, "distance", dist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Highlight links along one of the shortest paths between the BEGIN and the END nodes.
|
||||
// Assume links are unidirectional.
|
||||
function highlightShortestPath(begin, end) {
|
||||
highlightPath(findShortestPath(begin, end));
|
||||
}
|
||||
|
||||
|
||||
// List all paths from BEGIN to END
|
||||
function listAllPaths(begin, end) {
|
||||
// compute and remember all paths from BEGIN to END: Lists of Nodes
|
||||
paths = collectAllPaths(begin, end);
|
||||
|
||||
// update the Selection element with a bunch of Option elements, one per path
|
||||
var sel = document.getElementById("myPaths");
|
||||
sel.innerHTML = ""; // clear out any old Option elements
|
||||
paths.each(function(p) {
|
||||
var opt = document.createElement("option");
|
||||
opt.text = pathToString(p);
|
||||
sel.add(opt, null);
|
||||
});
|
||||
sel.onchange = highlightSelectedPath;
|
||||
}
|
||||
|
||||
// A collection of all of the paths between a pair of nodes, a List of Lists of Nodes
|
||||
var paths = null;
|
||||
// This is only used for listing all paths for the selection onchange event.
|
||||
|
||||
// When the selected item changes in the Selection element,
|
||||
// highlight the corresponding path of nodes.
|
||||
function highlightSelectedPath() {
|
||||
var sel = document.getElementById("myPaths");
|
||||
var idx = sel.selectedIndex;
|
||||
var opt = sel.options[idx];
|
||||
var val = opt.value;
|
||||
highlightPath(paths.get(sel.selectedIndex));
|
||||
}
|
||||
|
||||
// Highlight a particular path, a List of Nodes.
|
||||
function highlightPath(path) {
|
||||
myDiagram.clearHighlighteds();
|
||||
for (var i = 0; i < path.count - 1; i++) {
|
||||
var f = path.get(i);
|
||||
var t = path.get(i + 1);
|
||||
f.findLinksTo(t).each(function(l) { l.isHighlighted = true; });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; background: white; width: 100%; height: 700px"></div>
|
||||
Click on a node to show distances from that node to each other node.
|
||||
Click on a second node to show a shortest path from the first node to the second node.
|
||||
(Note that there might not be any path between the nodes.)
|
||||
<p>
|
||||
Clicking on a third node will de-select the first two.
|
||||
<p>
|
||||
Here is a list of all paths between the first and second selected nodes.
|
||||
Select a path to highlight it in the diagram.
|
||||
</p>
|
||||
<select id="myPaths" style="min-width:100px" size="10"></select>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Donut Charts</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A donut chart in each node." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv");
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Spot",
|
||||
$(go.Panel,
|
||||
$(go.Shape, "Circle", // provide a whole-circle background for the chart
|
||||
{ width: 100, height: 100, strokeWidth: 0, fill: "transparent" }),
|
||||
$(go.Shape, { fill: "transparent", stroke: "cyan", strokeWidth: 8 },
|
||||
new go.Binding("geometry", "value", makeArc),
|
||||
new go.Binding("stroke", "color1")),
|
||||
$(go.Shape, { fill: "transparent", stroke: "gray", strokeWidth: 8 },
|
||||
new go.Binding("geometry", "value", makeArcRest),
|
||||
new go.Binding("stroke", "color2"))
|
||||
),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text"))
|
||||
);
|
||||
|
||||
// These arcs assume the angle starts at 270 degrees, at the top of the circle.
|
||||
// They all assume the circle is 100x100 in size.
|
||||
function makeArc(sweep) {
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(50, 0)
|
||||
.add(new go.PathSegment(go.PathSegment.Arc, -90, sweep, 50, 50, 50, 50)));
|
||||
}
|
||||
|
||||
function makeArcRest(sweep) {
|
||||
var p = new go.Point(50, 0).rotate(-90+sweep).offset(50, 50);
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(p.x, p.y)
|
||||
.add(new go.PathSegment(go.PathSegment.Arc, sweep-90, 360-sweep, 50, 50, 50, 50)));
|
||||
}
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 1, text: "Alpha", value: 0 },
|
||||
{ key: 2, text: "Beta", value: 90 },
|
||||
{ key: 3, text: "Gamma", value: 135 },
|
||||
{ key: 4, text: "Delta", value: 330, color1: "red", color2: "green" }
|
||||
],
|
||||
[
|
||||
{ from: 1, to: 2 },
|
||||
{ from: 1, to: 3 },
|
||||
{ from: 3, to: 4 },
|
||||
{ from: 4, to: 1 }
|
||||
]);
|
||||
}
|
||||
|
||||
function changeValue() {
|
||||
var node = myDiagram.selection.first();
|
||||
if (node instanceof go.Node) {
|
||||
myDiagram.model.commit(function(m) {
|
||||
var val = node.data.value;
|
||||
val += Math.random() * 40 - 20;
|
||||
if (val < 0) val = 20;
|
||||
else if (val >= 360) val = 340;
|
||||
m.set(node.data, "value", val);
|
||||
}, "changed data value");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<button onclick="changeValue()">Change Selected Value</button>
|
||||
<p>
|
||||
Each node contains a Position Panel containing two Shape elements that get Geometry values
|
||||
that show a data value as an annular bar in a circle. One can also specify the colors of
|
||||
the two bars -- the bar showing the value and the rest of the circle.
|
||||
</p>
|
||||
<p>
|
||||
For more sophisticated charts within nodes, see the <a href="canvases.html">Canvas Charts</a> sample.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Double Circle</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Arrange nodes into concentric circles using CircularLayout." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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 in this function
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv", // must be the ID or reference to div
|
||||
{
|
||||
initialAutoScale: go.Diagram.Uniform,
|
||||
"animationManager.isEnabled": false
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
$(go.Shape, "Circle",
|
||||
{ fill: "gray", stroke: "#D8D8D8" },
|
||||
new go.Binding("fill", "color")),
|
||||
// define the node's text
|
||||
$(go.TextBlock,
|
||||
{ margin: 5, font: "bold 11px Helvetica, bold Arial, sans-serif" },
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
// create the model for the double circle
|
||||
var data = [];
|
||||
// if you want a node in the center, set its layer: 0
|
||||
//data.push({ layer: 0, color: "red" });
|
||||
for (var i = 0; i < 10; i++) data.push({ layer: 1, color: go.Brush.randomColor() });
|
||||
for (var i = 0; i < 25; i++) data.push({ layer: 2, color: go.Brush.randomColor() });
|
||||
myDiagram.model = new go.GraphLinksModel(data);
|
||||
|
||||
doubleCircleLayout(myDiagram);
|
||||
}
|
||||
|
||||
function doubleCircleLayout(diagram) {
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
diagram.startTransaction("Multi Circle Layout");
|
||||
|
||||
var radius = 100;
|
||||
var layer = 1;
|
||||
var nodes = null;
|
||||
while (nodes = nodesByLayer(diagram, layer), nodes.count > 0) {
|
||||
var layout = $(go.CircularLayout,
|
||||
{ radius: radius });
|
||||
layout.doLayout(nodes);
|
||||
// recenter at (0, 0)
|
||||
var cntr = layout.actualCenter;
|
||||
diagram.moveParts(nodes, new go.Point(-cntr.x, -cntr.y));
|
||||
// next layout uses a larger radius
|
||||
radius += 100;
|
||||
layer++;
|
||||
}
|
||||
|
||||
nodesByLayer(diagram, 0).each(function(n) { n.location = new go.Point(0, 0); });
|
||||
|
||||
diagram.commitTransaction("Multi Circle Layout");
|
||||
}
|
||||
|
||||
function nodesByLayer(diagram, layer) {
|
||||
var set = new go.Set(/*go.Node*/);
|
||||
diagram.nodes.each(function(part) {
|
||||
if (part instanceof go.Node && part.data.layer === layer) set.add(part);
|
||||
});
|
||||
return set;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
||||
<p>
|
||||
This sample displays a diagram of two sets of nodes intended to be arranged in different circles.
|
||||
</p>
|
||||
<p>
|
||||
Unlike many <b>GoJS</b> apps, there is no <a>Diagram.layout</a> assigned.
|
||||
Layouts are performed explicitly in code -- a separate <a>CircularLayout</a> for each subset of nodes.
|
||||
The code will actually work with a variable number of layers/circles, not just two.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Double Tree</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Layout of two trees in opposite directions, assuming a single root, using TreeLayout." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/DoubleTreeLayout.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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 in this function
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
layout: $(DoubleTreeLayout,
|
||||
{
|
||||
//vertical: true, // default directions are horizontal
|
||||
// choose whether this subtree is growing towards the right or towards the left:
|
||||
directionFunction: function(n) { return n.data && n.data.dir !== "left"; }
|
||||
// controlling the parameters of each TreeLayout:
|
||||
//bottomRightOptions: { nodeSpacing: 0, layerSpacing: 20 },
|
||||
//topLeftOptions: { alignment: go.TreeLayout.AlignmentStart },
|
||||
})
|
||||
});
|
||||
|
||||
// define all of the gradient brushes
|
||||
var graygrad = $(go.Brush, "Linear", { 0: "#F5F5F5", 1: "#F1F1F1" });
|
||||
var bluegrad = $(go.Brush, "Linear", { 0: "#CDDAF0", 1: "#91ADDD" });
|
||||
var yellowgrad = $(go.Brush, "Linear", { 0: "#FEC901", 1: "#FEA200" });
|
||||
var lavgrad = $(go.Brush, "Linear", { 0: "#EF9EFA", 1: "#A570AD" });
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ isShadowed: true },
|
||||
// define the node's outer shape
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
{ fill: graygrad, stroke: "#D8D8D8" }, // default fill is gray
|
||||
new go.Binding("fill", "color")),
|
||||
// define the node's text
|
||||
$(go.TextBlock,
|
||||
{ margin: 5, font: "bold 11px Helvetica, bold Arial, sans-serif" },
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ selectable: false },
|
||||
$(go.Shape)); // the link shape
|
||||
|
||||
// create the model for the double tree; could be eiher TreeModel or GraphLinksModel
|
||||
myDiagram.model = new go.TreeModel([
|
||||
{ key: "Root", color: lavgrad },
|
||||
{ key: "Left1", parent: "Root", dir: "left", color: bluegrad },
|
||||
{ key: "leaf1", parent: "Left1" },
|
||||
{ key: "leaf2", parent: "Left1" },
|
||||
{ key: "Left2", parent: "Left1", color: bluegrad },
|
||||
{ key: "leaf3", parent: "Left2" },
|
||||
{ key: "leaf4", parent: "Left2" },
|
||||
{ key: "leaf5", parent: "Left1" },
|
||||
{ key: "Right1", parent: "Root", dir: "right", color: yellowgrad },
|
||||
{ key: "Right2", parent: "Right1", color: yellowgrad },
|
||||
{ key: "leaf11", parent: "Right2" },
|
||||
{ key: "leaf12", parent: "Right2" },
|
||||
{ key: "leaf13", parent: "Right2" },
|
||||
{ key: "leaf14", parent: "Right1" },
|
||||
{ key: "leaf15", parent: "Right1" },
|
||||
{ key: "Right3", parent: "Root", dir: "right", color: yellowgrad },
|
||||
{ key: "leaf16", parent: "Right3" },
|
||||
{ key: "leaf17", parent: "Right3" }
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
||||
<p>
|
||||
This sample displays a diagram of two trees sharing a single root node growing in opposite directions.
|
||||
The immediate child data of the ROOT node have a "dir" property
|
||||
that describes the direction that subtree should grow.
|
||||
</p>
|
||||
<p>
|
||||
The <a>Diagram.layout</a> is an instance of the <a>DoubleTreeLayout</a> extension layout,
|
||||
defined in <a href="../extensions/DoubleTreeLayout.js">extensions/DoubleTreeLayout.js</a>.
|
||||
The layout requires a <a>DoubleTreeLayout.directionFunction</a> predicate to decide for a child node
|
||||
of the root node which way the subtree should grow.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,234 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dragging Fields Between Records</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Drag and drop items between nodes." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
// Custom DraggingTool for dragging fields instead of whole Parts.
|
||||
// FieldDraggingTool.fieldTemplate needs to be set to a template of the field that you want shown while dragging.
|
||||
function FieldDraggingTool() {
|
||||
go.DraggingTool.call(this);
|
||||
this.fieldTemplate = null; // THIS NEEDS TO BE SET before a drag starts
|
||||
this.temporaryPart = null;
|
||||
}
|
||||
go.Diagram.inherit(FieldDraggingTool, go.DraggingTool);
|
||||
|
||||
// override this method
|
||||
FieldDraggingTool.prototype.findDraggablePart = function() {
|
||||
var diagram = this.diagram;
|
||||
var obj = diagram.findObjectAt(diagram.lastInput.documentPoint);
|
||||
while (obj !== null && obj.type !== go.Panel.TableRow) obj = obj.panel;
|
||||
if (obj !== null && obj.type === go.Panel.TableRow &&
|
||||
this.fieldTemplate !== null && this.temporaryPart === null) {
|
||||
var tempPart =
|
||||
go.GraphObject.make(go.Node, "Table",
|
||||
{ layerName: "Tool", locationSpot: go.Spot.Center },
|
||||
this.fieldTemplate.copy()); // copy the template!
|
||||
this.temporaryPart = tempPart;
|
||||
// assume OBJ is now a Panel representing a field, bound to field data
|
||||
// update the temporary Part via data binding
|
||||
tempPart.location = diagram.lastInput.documentPoint; // need to set location explicitly
|
||||
diagram.add(tempPart); // add to Diagram before setting data
|
||||
tempPart.data = obj.data; // bind to the same field data as being dragged
|
||||
return tempPart;
|
||||
}
|
||||
return go.DraggingTool.prototype.findDraggablePart.call(this);
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doActivate = function() {
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doActivate.call(this);
|
||||
var diagram = this.diagram;
|
||||
this.standardMouseSelect();
|
||||
this.isActive = true;
|
||||
// instead of the usual result of computeEffectiveCollection, just use the temporaryPart alone
|
||||
var map = new go.Map(/*go.Part, go.DraggingInfo*/);
|
||||
map.set(this.temporaryPart, new go.DraggingInfo(diagram.lastInput.documentPoint.copy()));
|
||||
this.draggedParts = map;
|
||||
this.startTransaction("Drag Field");
|
||||
diagram.isMouseCaptured = true;
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doDeactivate = function() {
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doDeactivate.call(this);
|
||||
var diagram = this.diagram;
|
||||
// make sure the temporary Part is no longer in the Diagram
|
||||
diagram.remove(this.temporaryPart);
|
||||
this.temporaryPart = null;
|
||||
// now do all the standard deactivation cleanup,
|
||||
// including setting isActive = false, clearing out draggedParts, calling stopTransaction(),
|
||||
// and setting diagram.isMouseCaptured = false
|
||||
go.DraggingTool.prototype.doDeactivate.call(this);
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doMouseMove = function() {
|
||||
if (!this.isActive) return;
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doMouseMove.call(this);
|
||||
var diagram = this.diagram;
|
||||
// just move the temporaryPart (in draggedParts), without regard to moving or copying permissions of the Node
|
||||
var offset = diagram.lastInput.documentPoint.copy().subtract(diagram.firstInput.documentPoint);
|
||||
this.moveParts(this.draggedParts, offset, false);
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doMouseUp = function() {
|
||||
if (!this.isActive) return;
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doMouseUp.call(this);
|
||||
var diagram = this.diagram;
|
||||
var data = this.temporaryPart.data;
|
||||
var dest = diagram.findPartAt(diagram.lastInput.documentPoint, false);
|
||||
if (dest !== null && dest.data && dest.data.fields) {
|
||||
var panel = dest.findObject("TABLE");
|
||||
var idx = panel.findRowForLocalY(panel.getLocalPoint(diagram.lastInput.documentPoint).y);
|
||||
diagram.model.insertArrayItem(dest.data.fields, idx + 1,
|
||||
{ name: data.name, info: data.info, color: data.color, figure: data.figure });
|
||||
}
|
||||
var src = this.currentPart;
|
||||
// whether or not there was a destination node, delete the original field
|
||||
if (!(diagram.lastInput.control || diagram.lastInput.meta)) {
|
||||
var sidx = src.data.fields.indexOf(data);
|
||||
if (sidx >= 0) {
|
||||
diagram.model.removeArrayItem(src.data.fields, sidx);
|
||||
}
|
||||
}
|
||||
this.transactionResult = "Inserted Field";
|
||||
this.stopTool();
|
||||
};
|
||||
// end of FieldDraggingTool
|
||||
|
||||
|
||||
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",
|
||||
{
|
||||
validCycle: go.Diagram.CycleNotDirected, // don't allow loops
|
||||
draggingTool: $(FieldDraggingTool), // use custom DraggingTool
|
||||
// automatically update the model that is shown on this page
|
||||
"ModelChanged": function(e) { if (e.isTransactionFinished) showModel(); },
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// This template is a Panel that is used to represent each item in a Panel.itemArray.
|
||||
// The Panel is data bound to the item object.
|
||||
// This template needs to be used by the FieldDraggingTool as well as the Diagram.nodeTemplate.
|
||||
var fieldTemplate =
|
||||
$(go.Panel, "TableRow", // this Panel is a row in the containing Table
|
||||
new go.Binding("portId", "name"), // this Panel is a "port"
|
||||
{
|
||||
background: "transparent", // so this port's background can be picked by the mouse
|
||||
fromSpot: go.Spot.Right, // links only go from the right side to the left side
|
||||
toSpot: go.Spot.Left
|
||||
}, // allow drawing links from or to this port
|
||||
$(go.Shape,
|
||||
{ width: 12, height: 12, column: 0, strokeWidth: 2, margin: 4 },
|
||||
new go.Binding("figure", "figure"),
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: new go.Margin(0, 2), column: 1, font: "bold 13px sans-serif" },
|
||||
new go.Binding("text", "name")),
|
||||
$(go.TextBlock,
|
||||
{ margin: new go.Margin(0, 2), column: 2, font: "13px sans-serif" },
|
||||
new go.Binding("text", "info"))
|
||||
);
|
||||
|
||||
// the FieldDraggingTool needs a template for what to show while dragging
|
||||
myDiagram.toolManager.draggingTool.fieldTemplate = fieldTemplate;
|
||||
|
||||
// This template represents a whole "record".
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{
|
||||
movable: false,
|
||||
copyable: false,
|
||||
deletable: false,
|
||||
locationSpot: go.Spot.Center
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// this rectangular shape surrounds the content of the node
|
||||
$(go.Shape,
|
||||
{ fill: "#EEEEEE" }),
|
||||
// the content consists of a header and a list of items
|
||||
$(go.Panel, "Vertical",
|
||||
// this is the header for the whole node
|
||||
$(go.Panel, "Auto",
|
||||
{ stretch: go.GraphObject.Horizontal }, // as wide as the whole node
|
||||
$(go.Shape,
|
||||
{ fill: "#1570A6", stroke: null }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
alignment: go.Spot.Center,
|
||||
margin: 3,
|
||||
stroke: "white",
|
||||
textAlign: "center",
|
||||
font: "bold 12pt sans-serif"
|
||||
},
|
||||
new go.Binding("text", "title"))),
|
||||
// this Panel holds a Panel for each item object in the itemArray;
|
||||
// each item Panel is defined by the itemTemplate to be a TableRow in this Table
|
||||
$(go.Panel, "Table",
|
||||
{
|
||||
name: "TABLE",
|
||||
padding: 2,
|
||||
minSize: new go.Size(100, 10),
|
||||
defaultStretch: go.GraphObject.Horizontal,
|
||||
itemTemplate: fieldTemplate
|
||||
},
|
||||
new go.Binding("itemArray", "fields")
|
||||
) // end Table Panel of items
|
||||
) // end Vertical Panel
|
||||
); // end Node
|
||||
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{
|
||||
linkFromPortIdProperty: "fromPort",
|
||||
linkToPortIdProperty: "toPort",
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray: [
|
||||
{
|
||||
key: 1,
|
||||
title: "Record1",
|
||||
fields: [
|
||||
{ name: "field1", info: "", color: "#F7B84B", figure: "Ellipse" },
|
||||
{ name: "field2", info: "the second one", color: "#F25022", figure: "Ellipse" },
|
||||
{ name: "fieldThree", info: "3rd", color: "#00BCF2" }
|
||||
],
|
||||
loc: "0 0"
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
title: "Record2",
|
||||
fields: [
|
||||
{ name: "fieldA", info: "", color: "#FFB900", figure: "Diamond" }
|
||||
],
|
||||
loc: "250 0"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
showModel(); // show the diagram's initial model
|
||||
|
||||
function showModel() {
|
||||
document.getElementById("mySavedModel").textContent = myDiagram.model.toJson();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:300px"></div>
|
||||
<p>Drag fields between records to move them; dragging within the same node can reorder them.
|
||||
Fields can be copied when holding down the control key; they are deleted when dropped in the diagram's background.
|
||||
The "record" Nodes are not movable or copyable or deletable.</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>
|
||||
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dragging a Field from a Record onto an HTML Element</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Drag an item from a node out of the diagram and onto another HTML element." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
// Custom DraggingTool for dragging fields instead of whole Parts.
|
||||
// FieldDraggingTool.fieldTemplate needs to be set to a template of the field that you want shown while dragging.
|
||||
function FieldDraggingTool() {
|
||||
go.DraggingTool.call(this);
|
||||
this.fieldTemplate = null; // THIS NEEDS TO BE SET before a drag starts
|
||||
this.temporaryPart = null;
|
||||
}
|
||||
go.Diagram.inherit(FieldDraggingTool, go.DraggingTool);
|
||||
|
||||
// override this method
|
||||
FieldDraggingTool.prototype.findDraggablePart = function() {
|
||||
var diagram = this.diagram;
|
||||
var obj = diagram.findObjectAt(diagram.lastInput.documentPoint);
|
||||
while (obj !== null && obj.type !== go.Panel.TableRow) obj = obj.panel;
|
||||
if (obj !== null && obj.type === go.Panel.TableRow &&
|
||||
this.fieldTemplate !== null && this.temporaryPart === null) {
|
||||
var tempPart =
|
||||
go.GraphObject.make(go.Node, "Table",
|
||||
{ layerName: "Tool", locationSpot: go.Spot.Center },
|
||||
this.fieldTemplate.copy()); // copy the template!
|
||||
this.temporaryPart = tempPart;
|
||||
// assume OBJ is now a Panel representing a field, bound to field data
|
||||
// update the temporary Part via data binding
|
||||
tempPart.location = diagram.lastInput.documentPoint; // need to set location explicitly
|
||||
diagram.add(tempPart); // add to Diagram before setting data
|
||||
tempPart.data = obj.data; // bind to the same field data as being dragged
|
||||
return tempPart;
|
||||
}
|
||||
return go.DraggingTool.prototype.findDraggablePart.call(this);
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doActivate = function() {
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doActivate.call(this);
|
||||
var diagram = this.diagram;
|
||||
this.standardMouseSelect();
|
||||
this.isActive = true;
|
||||
// instead of the usual result of computeEffectiveCollection, just use the temporaryPart alone
|
||||
var map = new go.Map(/*go.Part, go.DraggingInfo*/);
|
||||
map.set(this.temporaryPart, new go.DraggingInfo(diagram.lastInput.documentPoint.copy()));
|
||||
this.draggedParts = map;
|
||||
this.startTransaction("Drag Field");
|
||||
diagram.isMouseCaptured = true;
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doDeactivate = function() {
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doDeactivate.call(this);
|
||||
var diagram = this.diagram;
|
||||
// make sure the temporary Part is no longer in the Diagram
|
||||
diagram.remove(this.temporaryPart);
|
||||
this.temporaryPart = null;
|
||||
// now do all the standard deactivation cleanup,
|
||||
// including setting isActive = false, clearing out draggedParts, calling stopTransaction(),
|
||||
// and setting diagram.isMouseCaptured = false
|
||||
go.DraggingTool.prototype.doDeactivate.call(this);
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doMouseMove = function() {
|
||||
if (!this.isActive) return;
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doMouseMove.call(this);
|
||||
var diagram = this.diagram;
|
||||
// just move the temporaryPart (in draggedParts), without regard to moving or copying permissions of the Node
|
||||
var offset = diagram.lastInput.documentPoint.copy().subtract(diagram.firstInput.documentPoint);
|
||||
this.moveParts(this.draggedParts, offset, false);
|
||||
};
|
||||
|
||||
FieldDraggingTool.prototype.doMouseUp = function() {
|
||||
if (!this.isActive) return;
|
||||
if (this.temporaryPart === null) return go.DraggingTool.prototype.doMouseUp.call(this);
|
||||
var diagram = this.diagram;
|
||||
var data = this.temporaryPart.data;
|
||||
var input = diagram.lastInput;
|
||||
var id = input.event.target.id;
|
||||
if (input.isTouchEvent) {
|
||||
// Touch events always target the first object touched, we want the last.
|
||||
// Determine if you are using Touch or Pointer:
|
||||
var evt = input.event.changedTouches ? input.event.changedTouches[0] : input.event;
|
||||
id = document.elementFromPoint(evt.clientX, evt.clientY).id;
|
||||
}
|
||||
if (input.event && id === "myDroppedFields") {
|
||||
document.getElementById("myDroppedFields").textContent += data.name + " (" + data.info + ")\n";
|
||||
}
|
||||
this.transactionResult = "Dragged Field";
|
||||
this.stopTool();
|
||||
};
|
||||
// end of FieldDraggingTool
|
||||
|
||||
|
||||
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",
|
||||
{
|
||||
validCycle: go.Diagram.CycleNotDirected, // don't allow loops
|
||||
draggingTool: $(FieldDraggingTool), // use custom DraggingTool
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// This template is a Panel that is used to represent each item in a Panel.itemArray.
|
||||
// The Panel is data bound to the item object.
|
||||
var fieldTemplate =
|
||||
$(go.Panel, "TableRow", // this Panel is a row in the containing Table
|
||||
new go.Binding("portId", "name"), // this Panel is a "port"
|
||||
{
|
||||
background: "transparent", // so this port's background can be picked by the mouse
|
||||
fromSpot: go.Spot.Right, // links only go from the right side to the left side
|
||||
toSpot: go.Spot.Left
|
||||
}, // allow drawing links from or to this port
|
||||
$(go.Shape,
|
||||
{ width: 12, height: 12, column: 0, strokeWidth: 2, margin: 4 },
|
||||
new go.Binding("figure", "figure"),
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: new go.Margin(0, 2), column: 1, font: "bold 13px sans-serif" },
|
||||
new go.Binding("text", "name")),
|
||||
$(go.TextBlock,
|
||||
{ margin: new go.Margin(0, 2), column: 2, font: "13px sans-serif" },
|
||||
new go.Binding("text", "info"))
|
||||
);
|
||||
|
||||
// the FieldDraggingTool needs a template for what to show while dragging
|
||||
myDiagram.toolManager.draggingTool.fieldTemplate = fieldTemplate;
|
||||
|
||||
// This template represents a whole "record".
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{
|
||||
movable: false,
|
||||
copyable: false,
|
||||
deletable: false
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// this rectangular shape surrounds the content of the node
|
||||
$(go.Shape,
|
||||
{ fill: "#EEEEEE" }),
|
||||
// the content consists of a header and a list of items
|
||||
$(go.Panel, "Vertical",
|
||||
// this is the header for the whole node
|
||||
$(go.Panel, "Auto",
|
||||
{ stretch: go.GraphObject.Horizontal }, // as wide as the whole node
|
||||
$(go.Shape,
|
||||
{ fill: "#1570A6", stroke: null }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
alignment: go.Spot.Center,
|
||||
margin: 3,
|
||||
stroke: "white",
|
||||
textAlign: "center",
|
||||
font: "bold 12pt sans-serif"
|
||||
},
|
||||
new go.Binding("text", "key"))),
|
||||
// this Panel holds a Panel for each item object in the itemArray;
|
||||
// each item Panel is defined by the itemTemplate to be a TableRow in this Table
|
||||
$(go.Panel, "Table",
|
||||
{
|
||||
name: "TABLE",
|
||||
padding: 2,
|
||||
minSize: new go.Size(100, 10),
|
||||
defaultStretch: go.GraphObject.Horizontal,
|
||||
itemTemplate: fieldTemplate
|
||||
},
|
||||
new go.Binding("itemArray", "fields")
|
||||
) // end Table Panel of items
|
||||
) // end Vertical Panel
|
||||
); // end Node
|
||||
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{
|
||||
linkFromPortIdProperty: "fromPort",
|
||||
linkToPortIdProperty: "toPort",
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray: [
|
||||
{
|
||||
key: "Record1",
|
||||
fields: [
|
||||
{ name: "field1", info: "", color: "#F7B84B", figure: "Ellipse" },
|
||||
{ name: "field2", info: "the second one", color: "#F25022", figure: "Ellipse" },
|
||||
{ name: "fieldThree", info: "3rd", color: "#00BCF2" }
|
||||
],
|
||||
loc: "0 0"
|
||||
},
|
||||
{
|
||||
key: "Record2",
|
||||
fields: [
|
||||
{ name: "fieldA", info: "", color: "#FFB900", figure: "Diamond" }
|
||||
],
|
||||
loc: "250 0"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:300px"></div>
|
||||
<p>Drag a field from one of the record nodes and drop onto the PRE element below.
|
||||
The "record" Nodes are not movable or copyable or deletable.</p>
|
||||
<p>Here you can drop a field from one of the records above:</p>
|
||||
<pre id="myDroppedFields" style="width:200px;height:300px;border:dashed"></pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Drag Unoccupied</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Limit the dragging of nodes to avoid any overlap with other nodes.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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
|
||||
|
||||
// R is a Rect in document coordinates
|
||||
// NODE is the Node being moved -- ignore when looking for Parts intersecting the Rect
|
||||
function isUnoccupied(r, node) {
|
||||
var diagram = node.diagram;
|
||||
|
||||
// nested function used by Layer.findObjectsIn, below
|
||||
// only consider Parts, and ignore the given Node, any Links, and Group members
|
||||
function navig(obj) {
|
||||
var part = obj.part;
|
||||
if (part === node) return null;
|
||||
if (part instanceof go.Link) return null;
|
||||
if (part.isMemberOf(node)) return null;
|
||||
if (node.isMemberOf(part)) return null;
|
||||
return part;
|
||||
}
|
||||
|
||||
// only consider non-temporary Layers
|
||||
var lit = diagram.layers;
|
||||
while (lit.next()) {
|
||||
var lay = lit.value;
|
||||
if (lay.isTemporary) continue;
|
||||
if (lay.findObjectsIn(r, navig, null, true).count > 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// a Part.dragComputation function that prevents a Part from being dragged to overlap another Part
|
||||
// use PT instead of GRIDPT if DraggingTool.isGridSnapEnabled but movement should not snap to grid
|
||||
function avoidNodeOverlap(node, pt, gridpt) {
|
||||
if (node.diagram instanceof go.Palette) return gridpt;
|
||||
// this assumes each node is fully rectangular
|
||||
var bnds = node.actualBounds;
|
||||
var loc = node.location;
|
||||
// use PT instead of GRIDPT if you want to ignore any grid snapping behavior
|
||||
// see if the area at the proposed location is unoccupied
|
||||
var r = new go.Rect(gridpt.x - (loc.x - bnds.x), gridpt.y - (loc.y - bnds.y), bnds.width, bnds.height);
|
||||
// maybe inflate R if you want some space between the node and any other nodes
|
||||
r.inflate(-0.5, -0.5); // by default, deflate to avoid edge overlaps with "exact" fits
|
||||
// when dragging a node from another Diagram, choose an unoccupied area
|
||||
if (!(node.diagram.currentTool instanceof go.DraggingTool) &&
|
||||
(!node._temp || !node.layer.isTemporary)) { // in Temporary Layer during external drag-and-drop
|
||||
node._temp = true; // flag to avoid repeated searches during external drag-and-drop
|
||||
while (!isUnoccupied(r, node)) {
|
||||
r.x += 10; // note that this is an unimaginative search algorithm --
|
||||
r.y += 2; // you can improve the search here to be more appropriate for your app
|
||||
}
|
||||
r.inflate(0.5, 0.5); // restore to actual size
|
||||
// return the proposed new location point
|
||||
return new go.Point(r.x - (loc.x - bnds.x), r.y - (loc.y - bnds.y));
|
||||
}
|
||||
if (isUnoccupied(r, node)) return gridpt; // OK
|
||||
return loc; // give up -- don't allow the node to be moved to the new location
|
||||
}
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"animationManager.isEnabled": false,
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// Define the template for Nodes, just some text inside a colored rectangle
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ dragComputation: avoidNodeOverlap },
|
||||
{ minSize: new go.Size(50, 20), resizable: true },
|
||||
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
|
||||
new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// temporarily put selected nodes in Foreground layer
|
||||
new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
|
||||
$(go.Shape, "Rectangle",
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "color"))
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ pos: "-30 0", size: "50 300", color: go.Brush.randomColor() },
|
||||
{ pos: "120 20", size: "300 50", color: go.Brush.randomColor() },
|
||||
{ pos: "100 200", size: "300 50", color: go.Brush.randomColor() },
|
||||
{ pos: "500 50", size: "50 300", color: go.Brush.randomColor() },
|
||||
{ key: 1, pos: "100 100", size: "50 50", color: "gray" },
|
||||
{ key: 2, pos: "200 140", size: "50 50", color: "gray" }
|
||||
]);
|
||||
|
||||
myDiagram.findNodeForKey(1).isSelected = true;
|
||||
|
||||
// initialize the Palette that is on the left side of the page
|
||||
myPalette =
|
||||
$(go.Palette, "myPaletteDiv", // must name or refer to the DIV HTML element
|
||||
{
|
||||
nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram
|
||||
model: new go.GraphLinksModel([ // specify the contents of the Palette
|
||||
{ size: "50 50", color: go.Brush.randomColor() },
|
||||
{ size: "60 40", color: go.Brush.randomColor() }
|
||||
])
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<div id="myPaletteDiv" style="width: 100px; background-color: floralwhite; border: solid 1px black; margin-right: 2px"></div>
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 400px"></div>
|
||||
</div>
|
||||
<p>
|
||||
Drag a node around.
|
||||
Notice how you cannot force the dragged node to overlap any other (stationary) node.
|
||||
If you drag more than one node, notice how the relative positions of the dragged nodes are maintained
|
||||
except when forced to be shifted in order to avoid overlapping other nodes.
|
||||
</p>
|
||||
<p>
|
||||
This functionality is implemented by a custom <a>Part.dragComputation</a> property function,
|
||||
which affects how the <a>DraggingTool</a> can move selected nodes.
|
||||
You will want to adjust how it finds an empty spot for the dragged node when dragging from another Diagram.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,313 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Draggable Link</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Drag a link to reconnect it. Nodes have custom Adornments for selection, resizing, and rotating. The Palette includes links." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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 name or refer to the DIV HTML element
|
||||
{
|
||||
grid: $(go.Panel, "Grid",
|
||||
$(go.Shape, "LineH", { stroke: "lightgray", strokeWidth: 0.5 }),
|
||||
$(go.Shape, "LineH", { stroke: "gray", strokeWidth: 0.5, interval: 10 }),
|
||||
$(go.Shape, "LineV", { stroke: "lightgray", strokeWidth: 0.5 }),
|
||||
$(go.Shape, "LineV", { stroke: "gray", strokeWidth: 0.5, interval: 10 })
|
||||
),
|
||||
"draggingTool.dragsLink": true,
|
||||
"draggingTool.isGridSnapEnabled": true,
|
||||
"linkingTool.isUnconnectedLinkValid": true,
|
||||
"linkingTool.portGravity": 20,
|
||||
"relinkingTool.isUnconnectedLinkValid": true,
|
||||
"relinkingTool.portGravity": 20,
|
||||
"relinkingTool.fromHandleArchetype":
|
||||
$(go.Shape, "Diamond", { segmentIndex: 0, cursor: "pointer", desiredSize: new go.Size(8, 8), fill: "tomato", stroke: "darkred" }),
|
||||
"relinkingTool.toHandleArchetype":
|
||||
$(go.Shape, "Diamond", { segmentIndex: -1, cursor: "pointer", desiredSize: new go.Size(8, 8), fill: "darkred", stroke: "tomato" }),
|
||||
"linkReshapingTool.handleArchetype":
|
||||
$(go.Shape, "Diamond", { desiredSize: new go.Size(7, 7), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
"rotatingTool.handleAngle": 270,
|
||||
"rotatingTool.handleDistance": 30,
|
||||
"rotatingTool.snapAngleMultiple": 15,
|
||||
"rotatingTool.snapAngleEpsilon": 15,
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
// Define a function for creating a "port" that is normally transparent.
|
||||
// The "name" is used as the GraphObject.portId, the "spot" is used to control how links connect
|
||||
// and where the port is positioned on the node, and the boolean "output" and "input" arguments
|
||||
// control whether the user can draw links from or to the port.
|
||||
function makePort(name, spot, output, input) {
|
||||
// the port is basically just a small transparent square
|
||||
return $(go.Shape, "Circle",
|
||||
{
|
||||
fill: null, // not seen, by default; set to a translucent gray by showSmallPorts, defined below
|
||||
stroke: null,
|
||||
desiredSize: new go.Size(7, 7),
|
||||
alignment: spot, // align the port on the main Shape
|
||||
alignmentFocus: spot, // just inside the Shape
|
||||
portId: name, // declare this object to be a "port"
|
||||
fromSpot: spot, toSpot: spot, // declare where links may connect at this port
|
||||
fromLinkable: output, toLinkable: input, // declare whether the user may draw links to/from here
|
||||
cursor: "pointer" // show a different cursor to indicate potential link point
|
||||
});
|
||||
}
|
||||
|
||||
var nodeSelectionAdornmentTemplate =
|
||||
$(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: null, stroke: "deepskyblue", strokeWidth: 1.5, strokeDashArray: [4, 2] }),
|
||||
$(go.Placeholder)
|
||||
);
|
||||
|
||||
var nodeResizeAdornmentTemplate =
|
||||
$(go.Adornment, "Spot",
|
||||
{ locationSpot: go.Spot.Right },
|
||||
$(go.Placeholder),
|
||||
$(go.Shape, { alignment: go.Spot.TopLeft, cursor: "nw-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
$(go.Shape, { alignment: go.Spot.Top, cursor: "n-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
$(go.Shape, { alignment: go.Spot.TopRight, cursor: "ne-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
|
||||
$(go.Shape, { alignment: go.Spot.Left, cursor: "w-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
$(go.Shape, { alignment: go.Spot.Right, cursor: "e-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
|
||||
$(go.Shape, { alignment: go.Spot.BottomLeft, cursor: "se-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
$(go.Shape, { alignment: go.Spot.Bottom, cursor: "s-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
$(go.Shape, { alignment: go.Spot.BottomRight, cursor: "sw-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "deepskyblue" })
|
||||
);
|
||||
|
||||
var nodeRotateAdornmentTemplate =
|
||||
$(go.Adornment,
|
||||
{ locationSpot: go.Spot.Center, locationObjectName: "CIRCLE" },
|
||||
$(go.Shape, "Circle", { name: "CIRCLE", cursor: "pointer", desiredSize: new go.Size(7, 7), fill: "lightblue", stroke: "deepskyblue" }),
|
||||
$(go.Shape, { geometryString: "M3.5 7 L3.5 30", isGeometryPositioned: true, stroke: "deepskyblue", strokeWidth: 1.5, strokeDashArray: [4, 2] })
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Spot",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{ selectable: true, selectionAdornmentTemplate: nodeSelectionAdornmentTemplate },
|
||||
{ resizable: true, resizeObjectName: "PANEL", resizeAdornmentTemplate: nodeResizeAdornmentTemplate },
|
||||
{ rotatable: true, rotateAdornmentTemplate: nodeRotateAdornmentTemplate },
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
// the main object is a Panel that surrounds a TextBlock with a Shape
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "PANEL" },
|
||||
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
|
||||
$(go.Shape, "Rectangle", // default figure
|
||||
{
|
||||
portId: "", // the default port: if no spot on link data, use closest side
|
||||
fromLinkable: true, toLinkable: true, cursor: "pointer",
|
||||
fill: "white", // default color
|
||||
strokeWidth: 2
|
||||
},
|
||||
new go.Binding("figure"),
|
||||
new go.Binding("fill")),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "bold 11pt Helvetica, Arial, sans-serif",
|
||||
margin: 8,
|
||||
maxSize: new go.Size(160, NaN),
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
editable: true
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
),
|
||||
// four small named ports, one on each side:
|
||||
makePort("T", go.Spot.Top, false, true),
|
||||
makePort("L", go.Spot.Left, true, true),
|
||||
makePort("R", go.Spot.Right, true, true),
|
||||
makePort("B", go.Spot.Bottom, true, false),
|
||||
{ // handle mouse enter/leave events to show/hide the ports
|
||||
mouseEnter: function(e, node) { showSmallPorts(node, true); },
|
||||
mouseLeave: function(e, node) { showSmallPorts(node, false); }
|
||||
}
|
||||
);
|
||||
|
||||
function showSmallPorts(node, show) {
|
||||
node.ports.each(function(port) {
|
||||
if (port.portId !== "") { // don't change the default port, which is the big shape
|
||||
port.fill = show ? "rgba(0,0,0,.3)" : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var linkSelectionAdornmentTemplate =
|
||||
$(go.Adornment, "Link",
|
||||
$(go.Shape,
|
||||
// isPanelMain declares that this Shape shares the Link.geometry
|
||||
{ isPanelMain: true, fill: null, stroke: "deepskyblue", strokeWidth: 0 }) // use selection object's strokeWidth
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ selectable: true, selectionAdornmentTemplate: linkSelectionAdornmentTemplate },
|
||||
{ relinkableFrom: true, relinkableTo: true, reshapable: true },
|
||||
{
|
||||
routing: go.Link.AvoidsNodes,
|
||||
curve: go.Link.JumpOver,
|
||||
corner: 5,
|
||||
toShortLength: 4
|
||||
},
|
||||
new go.Binding("points").makeTwoWay(),
|
||||
$(go.Shape, // the link path shape
|
||||
{ isPanelMain: true, strokeWidth: 2 }),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: "Standard", stroke: null }),
|
||||
$(go.Panel, "Auto",
|
||||
new go.Binding("visible", "isSelected").ofObject(),
|
||||
$(go.Shape, "RoundedRectangle", // the link shape
|
||||
{ fill: "#F8F8F8", stroke: null }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
textAlign: "center",
|
||||
font: "10pt helvetica, arial, sans-serif",
|
||||
stroke: "#919191",
|
||||
margin: 2,
|
||||
minSize: new go.Size(10, NaN),
|
||||
editable: true
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
)
|
||||
);
|
||||
|
||||
load(); // load an initial diagram from some JSON text
|
||||
|
||||
// initialize the Palette that is on the left side of the page
|
||||
myPalette =
|
||||
$(go.Palette, "myPaletteDiv", // must name or refer to the DIV HTML element
|
||||
{
|
||||
maxSelectionCount: 1,
|
||||
nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram
|
||||
linkTemplate: // simplify the link template, just in this Palette
|
||||
$(go.Link,
|
||||
{ // because the GridLayout.alignment is Location and the nodes have locationSpot == Spot.Center,
|
||||
// to line up the Link in the same manner we have to pretend the Link has the same location spot
|
||||
locationSpot: go.Spot.Center,
|
||||
selectionAdornmentTemplate:
|
||||
$(go.Adornment, "Link",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
$(go.Shape,
|
||||
{ isPanelMain: true, fill: null, stroke: "deepskyblue", strokeWidth: 0 }),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: "Standard", stroke: null })
|
||||
)
|
||||
},
|
||||
{
|
||||
routing: go.Link.AvoidsNodes,
|
||||
curve: go.Link.JumpOver,
|
||||
corner: 5,
|
||||
toShortLength: 4
|
||||
},
|
||||
new go.Binding("points"),
|
||||
$(go.Shape, // the link path shape
|
||||
{ isPanelMain: true, strokeWidth: 2 }),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: "Standard", stroke: null })
|
||||
),
|
||||
model: new go.GraphLinksModel([ // specify the contents of the Palette
|
||||
{ text: "Start", figure: "Circle", fill: "#00AD5F" },
|
||||
{ text: "Step" },
|
||||
{ text: "DB", figure: "Database", fill: "lightgray" },
|
||||
{ text: "???", figure: "Diamond", fill: "lightskyblue" },
|
||||
{ text: "End", figure: "Circle", fill: "#CE0620" },
|
||||
{ text: "Comment", figure: "RoundedRectangle", fill: "lightyellow" }
|
||||
], [
|
||||
// the Palette also has a disconnected Link, which the user can drag-and-drop
|
||||
{ points: new go.List(/*go.Point*/).addAll([new go.Point(0, 0), new go.Point(30, 0), new go.Point(30, 40), new go.Point(60, 40)]) }
|
||||
])
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Show the diagram's model in JSON format that the user may edit
|
||||
function save() {
|
||||
saveDiagramProperties(); // do this first, before writing to JSON
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
loadDiagramProperties(); // do this after the Model.modelData has been brought into memory
|
||||
}
|
||||
|
||||
function saveDiagramProperties() {
|
||||
myDiagram.model.modelData.position = go.Point.stringify(myDiagram.position);
|
||||
}
|
||||
function loadDiagramProperties(e) {
|
||||
// set Diagram.initialPosition, not Diagram.position, to handle initialization side-effects
|
||||
var pos = myDiagram.model.modelData.position;
|
||||
if (pos) myDiagram.initialPosition = go.Point.parse(pos);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<div id="myPaletteDiv" style="width: 105px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
|
||||
<div id="myDiagramDiv" style="flex-grow: 1; height: 620px; border: solid 1px black"></div>
|
||||
</div>
|
||||
<p>
|
||||
This sample demonstrates the ability for the user to drag around a Link as if it were a Node.
|
||||
When either end of the link passes over a valid port, the port is highlighted.
|
||||
</p>
|
||||
<p>
|
||||
The link-dragging functionality is enabled by setting some or all of the following properties:
|
||||
<a>DraggingTool.dragsLink</a>, <a>LinkingTool.isUnconnectedLinkValid</a>, and
|
||||
<a>RelinkingTool.isUnconnectedLinkValid</a>.
|
||||
</p>
|
||||
<p>
|
||||
Note that a Link is present in the <a>Palette</a> so that it too can be dragged out and onto
|
||||
the main Diagram. Because links are not automatically routed when either end is not connected
|
||||
with a Node, the route is provided explicitly when that Palette item is defined.
|
||||
</p>
|
||||
<p>
|
||||
This also demonstrates several custom Adornments:
|
||||
<a>Part.selectionAdornmentTemplate</a>, <a>Part.resizeAdornmentTemplate</a>, and
|
||||
<a>Part.rotateAdornmentTemplate</a>.
|
||||
</p>
|
||||
<p>
|
||||
Finally this sample demonstrates saving and restoring the <a>Diagram.position</a> as a property
|
||||
on the <a>Model.modelData</a> object that is automatically saved and restored when calling <a>Model.toJson</a>
|
||||
and <a>Model,fromJson</a>.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"linkFromPortIdProperty": "fromPort",
|
||||
"linkToPortIdProperty": "toPort",
|
||||
"nodeDataArray": [
|
||||
],
|
||||
"linkDataArray": [
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,337 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Movable, Copyable, Deletable Ports</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Nodes with selectable, movable, copyable, and deletable ports." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"undoManager.isEnabled": true,
|
||||
// don't allow links within a group
|
||||
"linkingTool.linkValidation": differentGroups,
|
||||
"relinkingTool.linkValidation": differentGroups,
|
||||
mouseDrop: function(e) {
|
||||
// when the selection is dropped in the diagram's background,
|
||||
// and it includes any "port"s, cancel the drop
|
||||
if (myDiagram.selection.any(selectionIncludesPorts)) {
|
||||
myDiagram.currentTool.doCancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function differentGroups(fromnode, fromport, tonode, toport) {
|
||||
return fromnode.containingGroup !== tonode.containingGroup;
|
||||
}
|
||||
|
||||
function selectionIncludesPorts(n) {
|
||||
return n.containingGroup !== null && !myDiagram.selection.has(n.containingGroup);
|
||||
}
|
||||
|
||||
var UnselectedBrush = "lightgray"; // item appearance, if not "selected"
|
||||
var SelectedBrush = "dodgerblue"; // item appearance, if "selected"
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ selectionAdorned: false },
|
||||
{
|
||||
mouseDrop: function(e, n) {
|
||||
// when the selection is entirely ports and is dropped onto a Group, transfer membership
|
||||
if (n.containingGroup !== null && myDiagram.selection.all(selectionIncludesPorts)) {
|
||||
myDiagram.selection.each(function(p) { p.containingGroup = n.containingGroup; });
|
||||
} else {
|
||||
myDiagram.currentTool.doCancel();
|
||||
}
|
||||
}
|
||||
},
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "SHAPE",
|
||||
fill: UnselectedBrush, stroke: "gray",
|
||||
geometryString: "F1 m 0,0 l 5,0 1,4 -1,4 -5,0 1,-4 -1,-4 z",
|
||||
spot1: new go.Spot(0, 0, 5, 1), // keep the text inside the shape
|
||||
spot2: new go.Spot(1, 1, -5, 0),
|
||||
// some port-related properties
|
||||
portId: "",
|
||||
toSpot: go.Spot.Left,
|
||||
toLinkable: false,
|
||||
fromSpot: go.Spot.Right,
|
||||
fromLinkable: false,
|
||||
cursor: "pointer"
|
||||
},
|
||||
new go.Binding("fill", "isSelected", function(s) { return s ? SelectedBrush : UnselectedBrush; }).ofObject(),
|
||||
new go.Binding("toLinkable", "_in"),
|
||||
new go.Binding("fromLinkable", "_in", function(b) { return !b; })),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "name"))
|
||||
);
|
||||
|
||||
myDiagram.groupTemplate =
|
||||
$(go.Group, "Auto",
|
||||
{
|
||||
selectionAdorned: false,
|
||||
locationSpot: go.Spot.Center, locationObjectName: "ICON"
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{
|
||||
mouseDrop: function(e, g) {
|
||||
// when the selection is entirely ports and is dropped onto a Group, transfer membership
|
||||
if (myDiagram.selection.all(selectionIncludesPorts)) {
|
||||
myDiagram.selection.each(function(p) { p.containingGroup = g; });
|
||||
} else {
|
||||
myDiagram.currentTool.doCancel();
|
||||
}
|
||||
},
|
||||
layout: new InputOutputGroupLayout()
|
||||
},
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
{ stroke: "gray", strokeWidth: 2, fill: "transparent" },
|
||||
new go.Binding("stroke", "isSelected", function(b) { return b ? SelectedBrush : UnselectedBrush; }).ofObject()),
|
||||
$(go.Panel, "Vertical",
|
||||
{ margin: 6 },
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "name"),
|
||||
{ alignment: go.Spot.Left }),
|
||||
$(go.Panel, "Spot",
|
||||
{ name: "ICON", height: 60 }, // an initial height; size will be set by InputOutputGroupLayout
|
||||
$(go.Shape,
|
||||
{ fill: null, stroke: null, stretch: go.GraphObject.Fill }),
|
||||
$(go.Picture, "images/60x90.png",
|
||||
{ width: 30, height: 45 })
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ routing: go.Link.Orthogonal, corner: 10, toShortLength: -3 },
|
||||
{ relinkableFrom: true, relinkableTo: true },
|
||||
$(go.Shape, { stroke: "gray", strokeWidth: 2.5 })
|
||||
);
|
||||
|
||||
load(); // initialize myDiagram's model from the TextArea
|
||||
}
|
||||
|
||||
function findPortNode(g, name, input) {
|
||||
for (var it = g.memberParts; it.next();) {
|
||||
var n = it.value;
|
||||
if (!(n instanceof go.Node)) continue;
|
||||
if (n.data.name === name && n.data._in === input) return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Transform the given data to the data structures needed internally.
|
||||
// For each data object in the "ins" Array of the node data, add a "port" Node to the Group.
|
||||
// For each data object in the "outs" Array, add a "port" Node to the Group.
|
||||
// For each link data, convert the "from" and "fromPort" information to the actual "port" Node,
|
||||
// and then the same for "to" and "toPort".
|
||||
// The internal model uses property names starting with "_" to avoid having Model.toJson() write them out.
|
||||
function setupDiagram(nodes, links) {
|
||||
var model = new go.GraphLinksModel();
|
||||
model.linkFromKeyProperty = "_f";
|
||||
model.linkToKeyProperty = "_t";
|
||||
model.nodeIsGroupProperty = "_isg";
|
||||
model.nodeGroupKeyProperty = "_g";
|
||||
|
||||
// first create all of the nodes, implemented as Groups
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var nodedata = nodes[i];
|
||||
nodedata._isg = true;
|
||||
}
|
||||
model.addNodeDataCollection(nodes);
|
||||
// now each node data will have a unique key, if not already specified
|
||||
|
||||
// then create all of the ports, implemented as Nodes that are members of those Groups
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var nodedata = nodes[i];
|
||||
if (Array.isArray(nodedata.ins)) {
|
||||
for (var j = 0; j < nodedata.ins.length; j++) {
|
||||
var portdata = nodedata.ins[j];
|
||||
portdata._in = true;
|
||||
portdata._g = nodedata.key;
|
||||
}
|
||||
model.addNodeDataCollection(nodedata.ins);
|
||||
nodedata.ins = undefined;
|
||||
}
|
||||
if (Array.isArray(nodedata.outs)) {
|
||||
for (var j = 0; j < nodedata.outs.length; j++) {
|
||||
var portdata = nodedata.outs[j];
|
||||
portdata._in = false;
|
||||
portdata._g = nodedata.key;
|
||||
}
|
||||
model.addNodeDataCollection(nodedata.outs);
|
||||
nodedata.outs = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
myDiagram.model = model;
|
||||
// now Groups and Nodes exist, so can find the Node corresponding to a node's port
|
||||
|
||||
// finally process all of the links, to account for ports actually being member nodes
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var linkdata = links[i];
|
||||
var fromNode = myDiagram.findNodeForKey(linkdata.from);
|
||||
var toNode = myDiagram.findNodeForKey(linkdata.to);
|
||||
if (fromNode !== null && toNode !== null) {
|
||||
// look in the Group for a "port" Node with the right name and directionality
|
||||
var fromPortNode = findPortNode(fromNode, linkdata.fromPort, false);
|
||||
var toPortNode = findPortNode(toNode, linkdata.toPort, true);
|
||||
if (fromPortNode !== null && toPortNode !== null) {
|
||||
linkdata._f = fromPortNode.data.key;
|
||||
linkdata._t = toPortNode.data.key;
|
||||
linkdata.from = linkdata.fromPort = linkdata.to = linkdata.toPort = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
model.addLinkDataCollection(links);
|
||||
}
|
||||
|
||||
function save() {
|
||||
// can't just call myDiagram.model.toJson() -- need to transform to external format
|
||||
var m = new go.GraphLinksModel();
|
||||
m.linkFromPortIdProperty = "fromPort";
|
||||
m.linkToPortIdProperty = "toPort";
|
||||
var arr = myDiagram.model.nodeDataArray;
|
||||
myDiagram.nodes.each(function(g) {
|
||||
if (g instanceof go.Group) {
|
||||
g.data.ins = undefined;
|
||||
g.data.outs = undefined;
|
||||
m.addNodeData(g.data);
|
||||
}
|
||||
});
|
||||
myDiagram.nodes.each(function(n) {
|
||||
if (!(n instanceof go.Group)) {
|
||||
var gd = n.containingGroup.data;
|
||||
var a = n.data._in ? gd.ins : gd.outs;
|
||||
if (!a) {
|
||||
a = [];
|
||||
if (n.data._in) gd.ins = a; else gd.outs = a;
|
||||
}
|
||||
a.push(n.data);
|
||||
}
|
||||
});
|
||||
myDiagram.links.each(function(l) {
|
||||
l.data.from = l.fromNode.containingGroup.data.key;
|
||||
l.data.fromPort = l.fromNode.data.name;
|
||||
l.data.to = l.toNode.containingGroup.data.key;
|
||||
l.data.toPort = l.toNode.data.name;
|
||||
m.addLinkData(l.data);
|
||||
});
|
||||
document.getElementById("mySavedModel").value = m.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
|
||||
function load() {
|
||||
var m = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
setupDiagram(m.nodeDataArray, m.linkDataArray);
|
||||
}
|
||||
|
||||
|
||||
// The Group.layout, for arranging the "port" Nodes within the Group
|
||||
function InputOutputGroupLayout() {
|
||||
go.Layout.call(this);
|
||||
}
|
||||
go.Diagram.inherit(InputOutputGroupLayout, go.Layout);
|
||||
|
||||
InputOutputGroupLayout.prototype.doLayout = function(coll) {
|
||||
coll = this.collectParts(coll);
|
||||
|
||||
var portSpacing = 2;
|
||||
var iconAreaWidth = 60;
|
||||
|
||||
// compute the counts and areas of the inputs and the outputs
|
||||
var left = 0;
|
||||
var leftwidth = 0; // max
|
||||
var leftheight = 0; // total
|
||||
var right = 0;
|
||||
var rightwidth = 0; // max
|
||||
var rightheight = 0; // total
|
||||
coll.each(function(n) {
|
||||
if (n instanceof go.Link) return; // ignore Links
|
||||
if (n.data._in) {
|
||||
left++;
|
||||
leftwidth = Math.max(leftwidth, n.actualBounds.width);
|
||||
leftheight += n.actualBounds.height;
|
||||
} else {
|
||||
right++;
|
||||
rightwidth = Math.max(rightwidth, n.actualBounds.width);
|
||||
rightheight += n.actualBounds.height;
|
||||
}
|
||||
});
|
||||
if (left > 0) leftheight += portSpacing * (left - 1);
|
||||
if (right > 0) rightheight += portSpacing * (right - 1);
|
||||
|
||||
var loc = new go.Point(0, 0);
|
||||
if (this.group !== null && this.group.location.isReal()) loc = this.group.location;
|
||||
|
||||
// first lay out the left side, the inputs
|
||||
var y = loc.y - leftheight / 2;
|
||||
coll.each(function(n) {
|
||||
if (n instanceof go.Link) return; // ignore Links
|
||||
if (!n.data._in) return; // ignore outputs
|
||||
n.position = new go.Point(loc.x - iconAreaWidth / 2 - leftwidth, y);
|
||||
y += n.actualBounds.height + portSpacing;
|
||||
});
|
||||
|
||||
// now the right side, the outputs
|
||||
y = loc.y - rightheight / 2;
|
||||
coll.each(function(n) {
|
||||
if (n instanceof go.Link) return; // ignore Links
|
||||
if (n.data._in) return; // ignore inputs
|
||||
n.position = new go.Point(loc.x + iconAreaWidth / 2 + rightwidth - n.actualBounds.width, y);
|
||||
y += n.actualBounds.height + portSpacing;
|
||||
});
|
||||
|
||||
// then position the group and size its icon area
|
||||
if (this.group !== null) {
|
||||
// position the group so that its ICON is in the middle, between the "ports"
|
||||
this.group.location = loc;
|
||||
// size the ICON so that it's wide enough to overlap the "ports" and tall enough to hold all of the "ports"
|
||||
var icon = this.group.findObject("ICON");
|
||||
if (icon !== null) icon.desiredSize = new go.Size(iconAreaWidth + leftwidth / 2 + rightwidth / 2, Math.max(leftheight, rightheight) + 10);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>
|
||||
To allow ports to be selected, dragged, copied, and deleted, they are implemented as Nodes.
|
||||
That means the nodes have to be implemented as Groups.
|
||||
The user can delete selected ports.
|
||||
The user cannot drop a port onto the background, but only onto a node.
|
||||
</p>
|
||||
<p>
|
||||
There is a custom Layout used by such Group nodes, <code>InputOutputGroupLayout</code>,
|
||||
to line up the input ports on the left side and the output ports on the right side.
|
||||
</p>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
The transformed model data (not the actual myDiagram.model):
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"linkFromPortIdProperty": "fromPort",
|
||||
"linkToPortIdProperty": "toPort",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "name":"Server", "ins":[ {"name":"s1", "key":-3},{"name":"s2", "key":-4} ], "outs":[ {"name":"o1", "key":-5} ], "loc":"-80 -80"},
|
||||
{"key":2, "name":"Other", "ins":[ {"name":"s1", "key":-6},{"name":"s2", "key":-7} ], "outs":[ {"name":"o1", "key":-8} ], "loc":"80 80"}
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":1, "fromPort":"o1", "to":2, "toPort":"s2"}
|
||||
]
|
||||
}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,344 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dynamic Pie Chart</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A GoJS pie chart that updates dynamically as counts change." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var pieRadius = 100;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"textEditingTool.starting": go.TextEditingTool.SingleClick,
|
||||
"ModelChanged": onModelChanged,
|
||||
"undoManager.isEnabled": true
|
||||
}
|
||||
);
|
||||
|
||||
// When a count changes in our model, ensure we trigger a redrawing of each slice in the pie
|
||||
function onModelChanged(e) {
|
||||
if (e.change === go.ChangedEvent.Property && e.propertyName === "count") {
|
||||
var slicedata = e.object;
|
||||
var nodedata = findNodeDataForSlice(slicedata);
|
||||
if (nodedata) {
|
||||
// Update the count binding to force makeGeo/positionSlice
|
||||
myDiagram.model.updateTargetBindings(nodedata, "count");
|
||||
// If the count went to 0, hide the slice
|
||||
var sliceindex = nodedata.slices.indexOf(slicedata);
|
||||
var slice = myDiagram.findNodeForKey(nodedata.key).findObject("PIE").elt(sliceindex);
|
||||
var sliceshape = slice.findObject("SLICE");
|
||||
if (slicedata.count === 0)
|
||||
sliceshape.visible = false;
|
||||
else
|
||||
sliceshape.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sliceTemplate =
|
||||
$(go.Panel,
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "SLICE",
|
||||
strokeWidth: 2, stroke: "transparent",
|
||||
isGeometryPositioned: true
|
||||
},
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("geometry", "", makeGeo)
|
||||
),
|
||||
new go.Binding("position", "", positionSlice),
|
||||
{ // Allow the user to "select" slices when clicking them
|
||||
click: function(e, slice) {
|
||||
var sliceShape = slice.findObject("SLICE");
|
||||
var oldskips = slice.diagram.skipsUndoManager;
|
||||
slice.diagram.skipsUndoManager = true;
|
||||
if (sliceShape.stroke === "transparent") {
|
||||
sliceShape.stroke = go.Brush.darkenBy(slice.data.color, 0.4);
|
||||
// Move the slice out from the pie when selected
|
||||
var nodedata = findNodeDataForSlice(slice.data);
|
||||
if (nodedata) {
|
||||
var sliceindex = nodedata.slices.indexOf(slice.data);
|
||||
var angles = getAngles(nodedata, sliceindex);
|
||||
if (angles.sweep !== 360) {
|
||||
var angle = angles.start + angles.sweep / 2;
|
||||
var offsetPoint = new go.Point(pieRadius / 10, 0);
|
||||
slice.position = offsetPoint.rotate(angle).offset(pieRadius / 10, pieRadius / 10);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sliceShape.stroke = "transparent";
|
||||
slice.position = new go.Point(pieRadius / 10, pieRadius / 10);
|
||||
}
|
||||
slice.diagram.skipsUndoManager = oldskips;
|
||||
}
|
||||
},
|
||||
{
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
{ "Border.fill": "lightgray" },
|
||||
$(go.TextBlock,
|
||||
{ font: "10pt Verdana, sans-serif", margin: 4 },
|
||||
new go.Binding("text", "", function(data) {
|
||||
// Display text and percentage rounded to 2 decimals
|
||||
var nodedata = findNodeDataForSlice(data);
|
||||
if (nodedata) {
|
||||
var percent = Math.round((data.count / getTotalCount(nodedata) * 100) * 100) / 100;
|
||||
return data.text + ": " + percent + "%";
|
||||
}
|
||||
return "";
|
||||
}))
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
var optionTemplate =
|
||||
$(go.Panel, "TableRow",
|
||||
$(go.TextBlock,
|
||||
{
|
||||
column: 0,
|
||||
font: "10pt Verdana, sans-serif", alignment: go.Spot.Left,
|
||||
margin: 5
|
||||
},
|
||||
new go.Binding("text")
|
||||
),
|
||||
$(go.Panel, "Auto",
|
||||
{ column: 1 },
|
||||
$(go.Shape, { fill: "#F2F2F2" }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "10pt Verdana, sans-serif",
|
||||
textAlign: "right", margin: 2,
|
||||
wrap: go.TextBlock.None, width: 40,
|
||||
editable: true, isMultiline: false,
|
||||
textValidation: isValidCount
|
||||
},
|
||||
new go.Binding("text", "count").makeTwoWay(function(count) { return parseInt(count, 10); })
|
||||
)
|
||||
),
|
||||
$(go.Panel, "Horizontal",
|
||||
{ column: 2 },
|
||||
$("Button",
|
||||
{
|
||||
click: incrementCount
|
||||
},
|
||||
$(go.Shape, "PlusLine", { margin: 3, desiredSize: new go.Size(7, 7) })
|
||||
),
|
||||
$("Button",
|
||||
{
|
||||
click: decrementCount
|
||||
},
|
||||
$(go.Shape, "MinusLine", { margin: 3, desiredSize: new go.Size(7, 7) })
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Vertical",
|
||||
{ deletable: false },
|
||||
$(go.TextBlock,
|
||||
{ font: "11pt Verdana, sans-serif", margin: 5 },
|
||||
new go.Binding("text")
|
||||
),
|
||||
$(go.Panel, "Horizontal",
|
||||
$(go.Panel, "Position",
|
||||
{
|
||||
name: "PIE",
|
||||
// account for slices offsetting when selected so the node won't change size
|
||||
desiredSize: new go.Size(pieRadius * 2.2 + 5, pieRadius * 2.2 + 5),
|
||||
itemTemplate: sliceTemplate
|
||||
},
|
||||
new go.Binding("itemArray", "slices")
|
||||
),
|
||||
$(go.Panel, "Table",
|
||||
{
|
||||
margin: 5,
|
||||
itemTemplate: optionTemplate
|
||||
},
|
||||
new go.Binding("itemArray", "slices")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.model = $(go.Model,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray:
|
||||
[
|
||||
{
|
||||
key: 0,
|
||||
text: "Sample Poll",
|
||||
slices: [
|
||||
{ text: "Option 1", count: 21, color: "#B378C1" },
|
||||
{ text: "Option 2", count: 11, color: "#F25F5C" },
|
||||
{ text: "Option 3", count: 5, color: "#FFE066" },
|
||||
{ text: "Option 4", count: 2, color: "#2B98C5" },
|
||||
{ text: "Option 5", count: 1, color: "#70C1B3" }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Validation function for editing text
|
||||
function isValidCount(textblock, oldstr, newstr) {
|
||||
if (newstr === "") return false;
|
||||
var num = +newstr; // quick way to convert a string to a number
|
||||
return !isNaN(num) && Number.isInteger(num) && num >= 0;
|
||||
}
|
||||
|
||||
// Given some slice data, find the corresponding node data
|
||||
function findNodeDataForSlice(slice) {
|
||||
var arr = myDiagram.model.nodeDataArray;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var data = arr[i];
|
||||
if (data.slices.indexOf(slice) >= 0) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeGeo(data) {
|
||||
var nodedata = findNodeDataForSlice(data);
|
||||
var sliceindex = nodedata.slices.indexOf(data);
|
||||
var angles = getAngles(nodedata, sliceindex);
|
||||
|
||||
// Constructing the Geomtery this way is much more efficient than calling go.GraphObject.make:
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(pieRadius, pieRadius) // start point
|
||||
.add(new go.PathSegment(go.PathSegment.Arc,
|
||||
angles.start, angles.sweep, // angles
|
||||
pieRadius, pieRadius, // center
|
||||
pieRadius, pieRadius) // radius
|
||||
.close()));
|
||||
}
|
||||
|
||||
// Ensure slices get the proper positioning after we update any counts
|
||||
function positionSlice(data, obj) {
|
||||
var nodedata = findNodeDataForSlice(data);
|
||||
var sliceindex = nodedata.slices.indexOf(data);
|
||||
var angles = getAngles(nodedata, sliceindex);
|
||||
|
||||
var selected = obj.findObject("SLICE").stroke !== "transparent";
|
||||
if (selected && angles.sweep !== 360) {
|
||||
var offsetPoint = new go.Point(pieRadius / 10, 0); // offset by 1/10 the radius
|
||||
offsetPoint = offsetPoint.rotate(angles.start + angles.sweep / 2); // rotate to the correct angle
|
||||
offsetPoint = offsetPoint.offset(pieRadius / 10, pieRadius / 10); // translate center toward middle of pie panel
|
||||
return offsetPoint;
|
||||
}
|
||||
return new go.Point(pieRadius / 10, pieRadius / 10);
|
||||
}
|
||||
|
||||
// This is a bit inefficient, but should be OK for normal-sized graphs with reasonable numbers of slices per node
|
||||
function findAllSelectedItems() {
|
||||
var slices = [];
|
||||
for (var nit = myDiagram.nodes; nit.next();) {
|
||||
var node = nit.value;
|
||||
var pie = node.findObject("PIE");
|
||||
if (pie) {
|
||||
for (var sit = pie.elements; sit.next();) {
|
||||
var slicepanel = sit.value;
|
||||
if (slicepanel.findObject("SLICE").stroke !== "transparent") slices.push(slicepanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
return slices;
|
||||
}
|
||||
|
||||
// Override the standard CommandHandler deleteSelection behavior.
|
||||
// If there are any selected slices, delete them instead of deleting any selected nodes or links.
|
||||
myDiagram.commandHandler.canDeleteSelection = function() {
|
||||
// True if there are any selected deletable nodes or links,
|
||||
// or if there are any selected slices within nodes
|
||||
return go.CommandHandler.prototype.canDeleteSelection.call(myDiagram.commandHandler) ||
|
||||
findAllSelectedItems().length > 0;
|
||||
};
|
||||
|
||||
myDiagram.commandHandler.deleteSelection = function() {
|
||||
var slices = findAllSelectedItems();
|
||||
if (slices.length > 0) { // if there are any selected slices, delete them
|
||||
myDiagram.startTransaction("delete slices");
|
||||
var nodeset = new go.Set();
|
||||
for (var i = 0; i < slices.length; i++) {
|
||||
var panel = slices[i];
|
||||
var nodedata = panel.part.data;
|
||||
var slicearray = nodedata.slices;
|
||||
var slicedata = panel.data;
|
||||
var sliceindex = slicearray.indexOf(slicedata);
|
||||
// Remove the slice from the model
|
||||
myDiagram.model.removeArrayItem(slicearray, sliceindex);
|
||||
nodeset.add(nodedata);
|
||||
}
|
||||
// Force geometries to be redrawn on any node that had slices deleted
|
||||
nodeset.each(function(data) {
|
||||
myDiagram.model.updateTargetBindings(data, "count");
|
||||
});
|
||||
myDiagram.commitTransaction("delete slices");
|
||||
} else { // otherwise just delete nodes and/or links, as usual
|
||||
go.CommandHandler.prototype.deleteSelection.call(myDiagram.commandHandler);
|
||||
}
|
||||
};
|
||||
|
||||
// Return total count of a given node
|
||||
function getTotalCount(nodedata) {
|
||||
var totCount = 0;
|
||||
for (var i = 0; i < nodedata.slices.length; i++) {
|
||||
totCount += nodedata.slices[i].count;
|
||||
}
|
||||
return totCount;
|
||||
}
|
||||
|
||||
// Determine start and sweep angles given some node data and the index of the slice
|
||||
function getAngles(nodedata, index) {
|
||||
var totCount = getTotalCount(nodedata);
|
||||
var startAngle = -90;
|
||||
for (var i = 0; i < index; i++) {
|
||||
startAngle += 360 * nodedata.slices[i].count / totCount;
|
||||
}
|
||||
return { "start": startAngle, "sweep": 360 * nodedata.slices[index].count / totCount };
|
||||
}
|
||||
|
||||
// When user hits + button, increment count on that option
|
||||
function incrementCount(e, obj) {
|
||||
myDiagram.model.startTransaction("increment count");
|
||||
var slicedata = obj.panel.panel.data;
|
||||
myDiagram.model.setDataProperty(slicedata, "count", slicedata.count + 1);
|
||||
myDiagram.model.commitTransaction("increment count");
|
||||
}
|
||||
|
||||
// When user hits - button, decrement count on that option
|
||||
function decrementCount(e, obj) {
|
||||
myDiagram.model.startTransaction("decrement count");
|
||||
var slicedata = obj.panel.panel.data;
|
||||
if (slicedata.count > 0)
|
||||
myDiagram.model.setDataProperty(slicedata, "count", slicedata.count - 1);
|
||||
myDiagram.model.commitTransaction("decrement count");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 500px;"></div>
|
||||
<p>
|
||||
This sample demonstrates the ability to build an updateable pie chart with selectable slices.
|
||||
The Geometry for each slice is built using a <a>PathFigure</a> with a <a>PathSegment,Arc</a>.
|
||||
Slices use a custom <b>click</b> function, which sets a stroke and offsets slices as they are selected.
|
||||
Functionality for "selection" and deletion of these slices is similar to the <a href="selectableFields.html">Selectable Fields sample</a>,
|
||||
using some overridden <a>CommandHandler</a> functions.
|
||||
Each slice also has a tooltip showing the text and percentage of votes.
|
||||
</p>
|
||||
<p>
|
||||
Poll results can be adjusted and the pie chart will automatically update to reflect any changes.
|
||||
This includes deleting selected slices, updating the count using a TextBlock, or using the +/- buttons.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,518 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dynamic Ports</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Nodes with varying lists of ports on each of four sides." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make; //for conciseness in defining node templates
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv", //Diagram refers to its DIV HTML element by id
|
||||
{ "undoManager.isEnabled": true });
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
// To simplify this code we define a function for creating a context menu button:
|
||||
function makeButton(text, action, visiblePredicate) {
|
||||
return $("ContextMenuButton",
|
||||
$(go.TextBlock, text),
|
||||
{ click: action },
|
||||
// don't bother with binding GraphObject.visible if there's no predicate
|
||||
visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.diagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
|
||||
}
|
||||
|
||||
var nodeMenu = // context menu for each Node
|
||||
$("ContextMenu",
|
||||
makeButton("Copy",
|
||||
function(e, obj) { e.diagram.commandHandler.copySelection(); }),
|
||||
makeButton("Delete",
|
||||
function(e, obj) { e.diagram.commandHandler.deleteSelection(); }),
|
||||
$(go.Shape, "LineH", { strokeWidth: 2, height: 1, stretch: go.GraphObject.Horizontal }),
|
||||
makeButton("Add top port",
|
||||
function(e, obj) { addPort("top"); }),
|
||||
makeButton("Add left port",
|
||||
function(e, obj) { addPort("left"); }),
|
||||
makeButton("Add right port",
|
||||
function(e, obj) { addPort("right"); }),
|
||||
makeButton("Add bottom port",
|
||||
function(e, obj) { addPort("bottom"); })
|
||||
);
|
||||
|
||||
var portSize = new go.Size(8, 8);
|
||||
|
||||
var portMenu = // context menu for each port
|
||||
$("ContextMenu",
|
||||
makeButton("Swap order",
|
||||
function(e, obj) { swapOrder(obj.part.adornedObject); }),
|
||||
makeButton("Remove port",
|
||||
// in the click event handler, the obj.part is the Adornment;
|
||||
// its adornedObject is the port
|
||||
function(e, obj) { removePort(obj.part.adornedObject); }),
|
||||
makeButton("Change color",
|
||||
function(e, obj) { changeColor(obj.part.adornedObject); }),
|
||||
makeButton("Remove side ports",
|
||||
function(e, obj) { removeAll(obj.part.adornedObject); })
|
||||
);
|
||||
|
||||
// the node template
|
||||
// includes a panel on each side with an itemArray of panels containing ports
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Table",
|
||||
{
|
||||
locationObjectName: "BODY",
|
||||
locationSpot: go.Spot.Center,
|
||||
selectionObjectName: "BODY",
|
||||
contextMenu: nodeMenu
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
|
||||
// the body
|
||||
$(go.Panel, "Auto",
|
||||
{
|
||||
row: 1, column: 1, name: "BODY",
|
||||
stretch: go.GraphObject.Fill
|
||||
},
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
fill: "#dbf6cb", stroke: null, strokeWidth: 0,
|
||||
minSize: new go.Size(60, 60)
|
||||
}),
|
||||
$(go.TextBlock,
|
||||
{ margin: 10, textAlign: "center", font: "bold 14px Segoe UI,sans-serif", stroke: "#484848", editable: true },
|
||||
new go.Binding("text", "name").makeTwoWay())
|
||||
), // end Auto Panel body
|
||||
|
||||
// the Panel holding the left port elements, which are themselves Panels,
|
||||
// created for each item in the itemArray, bound to data.leftArray
|
||||
$(go.Panel, "Vertical",
|
||||
new go.Binding("itemArray", "leftArray"),
|
||||
{
|
||||
row: 1, column: 0,
|
||||
itemTemplate:
|
||||
$(go.Panel,
|
||||
{
|
||||
_side: "left", // internal property to make it easier to tell which side it's on
|
||||
fromSpot: go.Spot.Left, toSpot: go.Spot.Left,
|
||||
fromLinkable: true, toLinkable: true, cursor: "pointer",
|
||||
contextMenu: portMenu
|
||||
},
|
||||
new go.Binding("portId", "portId"),
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
stroke: null, strokeWidth: 0,
|
||||
desiredSize: portSize,
|
||||
margin: new go.Margin(1, 0)
|
||||
},
|
||||
new go.Binding("fill", "portColor"))
|
||||
) // end itemTemplate
|
||||
}
|
||||
), // end Vertical Panel
|
||||
|
||||
// the Panel holding the top port elements, which are themselves Panels,
|
||||
// created for each item in the itemArray, bound to data.topArray
|
||||
$(go.Panel, "Horizontal",
|
||||
new go.Binding("itemArray", "topArray"),
|
||||
{
|
||||
row: 0, column: 1,
|
||||
itemTemplate:
|
||||
$(go.Panel,
|
||||
{
|
||||
_side: "top",
|
||||
fromSpot: go.Spot.Top, toSpot: go.Spot.Top,
|
||||
fromLinkable: true, toLinkable: true, cursor: "pointer",
|
||||
contextMenu: portMenu
|
||||
},
|
||||
new go.Binding("portId", "portId"),
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
stroke: null, strokeWidth: 0,
|
||||
desiredSize: portSize,
|
||||
margin: new go.Margin(0, 1)
|
||||
},
|
||||
new go.Binding("fill", "portColor"))
|
||||
) // end itemTemplate
|
||||
}
|
||||
), // end Horizontal Panel
|
||||
|
||||
// the Panel holding the right port elements, which are themselves Panels,
|
||||
// created for each item in the itemArray, bound to data.rightArray
|
||||
$(go.Panel, "Vertical",
|
||||
new go.Binding("itemArray", "rightArray"),
|
||||
{
|
||||
row: 1, column: 2,
|
||||
itemTemplate:
|
||||
$(go.Panel,
|
||||
{
|
||||
_side: "right",
|
||||
fromSpot: go.Spot.Right, toSpot: go.Spot.Right,
|
||||
fromLinkable: true, toLinkable: true, cursor: "pointer",
|
||||
contextMenu: portMenu
|
||||
},
|
||||
new go.Binding("portId", "portId"),
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
stroke: null, strokeWidth: 0,
|
||||
desiredSize: portSize,
|
||||
margin: new go.Margin(1, 0)
|
||||
},
|
||||
new go.Binding("fill", "portColor"))
|
||||
) // end itemTemplate
|
||||
}
|
||||
), // end Vertical Panel
|
||||
|
||||
// the Panel holding the bottom port elements, which are themselves Panels,
|
||||
// created for each item in the itemArray, bound to data.bottomArray
|
||||
$(go.Panel, "Horizontal",
|
||||
new go.Binding("itemArray", "bottomArray"),
|
||||
{
|
||||
row: 2, column: 1,
|
||||
itemTemplate:
|
||||
$(go.Panel,
|
||||
{
|
||||
_side: "bottom",
|
||||
fromSpot: go.Spot.Bottom, toSpot: go.Spot.Bottom,
|
||||
fromLinkable: true, toLinkable: true, cursor: "pointer",
|
||||
contextMenu: portMenu
|
||||
},
|
||||
new go.Binding("portId", "portId"),
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
stroke: null, strokeWidth: 0,
|
||||
desiredSize: portSize,
|
||||
margin: new go.Margin(0, 1)
|
||||
},
|
||||
new go.Binding("fill", "portColor"))
|
||||
) // end itemTemplate
|
||||
}
|
||||
) // end Horizontal Panel
|
||||
); // end Node
|
||||
|
||||
// an orthogonal link template, reshapable and relinkable
|
||||
myDiagram.linkTemplate =
|
||||
$(CustomLink, // defined below
|
||||
{
|
||||
routing: go.Link.AvoidsNodes,
|
||||
corner: 4,
|
||||
curve: go.Link.JumpGap,
|
||||
reshapable: true,
|
||||
resegmentable: true,
|
||||
relinkableFrom: true,
|
||||
relinkableTo: true
|
||||
},
|
||||
new go.Binding("points").makeTwoWay(),
|
||||
$(go.Shape, { stroke: "#2F4F4F", strokeWidth: 2 })
|
||||
);
|
||||
|
||||
// support double-clicking in the background to add a copy of this data as a node
|
||||
myDiagram.toolManager.clickCreatingTool.archetypeNodeData = {
|
||||
name: "Unit",
|
||||
leftArray: [],
|
||||
rightArray: [],
|
||||
topArray: [],
|
||||
bottomArray: []
|
||||
};
|
||||
|
||||
myDiagram.contextMenu =
|
||||
$("ContextMenu",
|
||||
makeButton("Paste",
|
||||
function(e, obj) { e.diagram.commandHandler.pasteSelection(e.diagram.toolManager.contextMenuTool.mouseDownPoint); },
|
||||
function(o) { return o.diagram.commandHandler.canPasteSelection(o.diagram.toolManager.contextMenuTool.mouseDownPoint); }),
|
||||
makeButton("Undo",
|
||||
function(e, obj) { e.diagram.commandHandler.undo(); },
|
||||
function(o) { return o.diagram.commandHandler.canUndo(); }),
|
||||
makeButton("Redo",
|
||||
function(e, obj) { e.diagram.commandHandler.redo(); },
|
||||
function(o) { return o.diagram.commandHandler.canRedo(); })
|
||||
);
|
||||
|
||||
// load the diagram from JSON data
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
// This custom-routing Link class tries to separate parallel links from each other.
|
||||
// This assumes that ports are lined up in a row/column on a side of the node.
|
||||
function CustomLink() {
|
||||
go.Link.call(this);
|
||||
};
|
||||
go.Diagram.inherit(CustomLink, go.Link);
|
||||
|
||||
CustomLink.prototype.findSidePortIndexAndCount = function(node, port) {
|
||||
var nodedata = node.data;
|
||||
if (nodedata !== null) {
|
||||
var portdata = port.data;
|
||||
var side = port._side;
|
||||
var arr = nodedata[side + "Array"];
|
||||
var len = arr.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (arr[i] === portdata) return [i, len];
|
||||
}
|
||||
}
|
||||
return [-1, len];
|
||||
};
|
||||
|
||||
CustomLink.prototype.computeEndSegmentLength = function(node, port, spot, from) {
|
||||
var esl = go.Link.prototype.computeEndSegmentLength.call(this, node, port, spot, from);
|
||||
var other = this.getOtherPort(port);
|
||||
if (port !== null && other !== null) {
|
||||
var thispt = port.getDocumentPoint(this.computeSpot(from));
|
||||
var otherpt = other.getDocumentPoint(this.computeSpot(!from));
|
||||
if (Math.abs(thispt.x - otherpt.x) > 20 || Math.abs(thispt.y - otherpt.y) > 20) {
|
||||
var info = this.findSidePortIndexAndCount(node, port);
|
||||
var idx = info[0];
|
||||
var count = info[1];
|
||||
if (port._side == "top" || port._side == "bottom") {
|
||||
if (otherpt.x < thispt.x) {
|
||||
return esl + 4 + idx * 8;
|
||||
} else {
|
||||
return esl + (count - idx - 1) * 8;
|
||||
}
|
||||
} else { // left or right
|
||||
if (otherpt.y < thispt.y) {
|
||||
return esl + 4 + idx * 8;
|
||||
} else {
|
||||
return esl + (count - idx - 1) * 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return esl;
|
||||
};
|
||||
|
||||
CustomLink.prototype.hasCurviness = function() {
|
||||
if (isNaN(this.curviness)) return true;
|
||||
return go.Link.prototype.hasCurviness.call(this);
|
||||
};
|
||||
|
||||
CustomLink.prototype.computeCurviness = function() {
|
||||
if (isNaN(this.curviness)) {
|
||||
var fromnode = this.fromNode;
|
||||
var fromport = this.fromPort;
|
||||
var fromspot = this.computeSpot(true);
|
||||
var frompt = fromport.getDocumentPoint(fromspot);
|
||||
var tonode = this.toNode;
|
||||
var toport = this.toPort;
|
||||
var tospot = this.computeSpot(false);
|
||||
var topt = toport.getDocumentPoint(tospot);
|
||||
if (Math.abs(frompt.x - topt.x) > 20 || Math.abs(frompt.y - topt.y) > 20) {
|
||||
if ((fromspot.equals(go.Spot.Left) || fromspot.equals(go.Spot.Right)) &&
|
||||
(tospot.equals(go.Spot.Left) || tospot.equals(go.Spot.Right))) {
|
||||
var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);
|
||||
var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);
|
||||
var c = (fromseglen - toseglen) / 2;
|
||||
if (frompt.x + fromseglen >= topt.x - toseglen) {
|
||||
if (frompt.y < topt.y) return c;
|
||||
if (frompt.y > topt.y) return -c;
|
||||
}
|
||||
} else if ((fromspot.equals(go.Spot.Top) || fromspot.equals(go.Spot.Bottom)) &&
|
||||
(tospot.equals(go.Spot.Top) || tospot.equals(go.Spot.Bottom))) {
|
||||
var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);
|
||||
var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);
|
||||
var c = (fromseglen - toseglen) / 2;
|
||||
if (frompt.x + fromseglen >= topt.x - toseglen) {
|
||||
if (frompt.y < topt.y) return c;
|
||||
if (frompt.y > topt.y) return -c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return go.Link.prototype.computeCurviness.call(this);
|
||||
};
|
||||
// end CustomLink class
|
||||
|
||||
|
||||
// Add a port to the specified side of the selected nodes.
|
||||
function addPort(side) {
|
||||
myDiagram.startTransaction("addPort");
|
||||
myDiagram.selection.each(function(node) {
|
||||
// skip any selected Links
|
||||
if (!(node instanceof go.Node)) return;
|
||||
// compute the next available index number for the side
|
||||
var i = 0;
|
||||
while (node.findPort(side + i.toString()) !== node) i++;
|
||||
// now this new port name is unique within the whole Node because of the side prefix
|
||||
var name = side + i.toString();
|
||||
// get the Array of port data to be modified
|
||||
var arr = node.data[side + "Array"];
|
||||
if (arr) {
|
||||
// create a new port data object
|
||||
var newportdata = {
|
||||
portId: name,
|
||||
portColor: getPortColor()
|
||||
// if you add port data properties here, you should copy them in copyPortData above
|
||||
};
|
||||
// and add it to the Array of port data
|
||||
myDiagram.model.insertArrayItem(arr, -1, newportdata);
|
||||
}
|
||||
});
|
||||
myDiagram.commitTransaction("addPort");
|
||||
}
|
||||
|
||||
// Exchange the position/order of the given port with the next one.
|
||||
// If it's the last one, swap with the previous one.
|
||||
function swapOrder(port) {
|
||||
var arr = port.panel.itemArray;
|
||||
if (arr.length >= 2) { // only if there are at least two ports!
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (arr[i].portId === port.portId) {
|
||||
myDiagram.startTransaction("swap ports");
|
||||
if (i >= arr.length - 1) i--; // now can swap I and I+1, even if it's the last port
|
||||
var newarr = arr.slice(0); // copy Array
|
||||
newarr[i] = arr[i + 1]; // swap items
|
||||
newarr[i + 1] = arr[i];
|
||||
// remember the new Array in the model
|
||||
myDiagram.model.setDataProperty(port.part.data, port._side + "Array", newarr);
|
||||
myDiagram.commitTransaction("swap ports");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the clicked port from the node.
|
||||
// Links to the port will be redrawn to the node's shape.
|
||||
function removePort(port) {
|
||||
myDiagram.startTransaction("removePort");
|
||||
var pid = port.portId;
|
||||
var arr = port.panel.itemArray;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (arr[i].portId === pid) {
|
||||
myDiagram.model.removeArrayItem(arr, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
myDiagram.commitTransaction("removePort");
|
||||
}
|
||||
|
||||
// Remove all ports from the same side of the node as the clicked port.
|
||||
function removeAll(port) {
|
||||
myDiagram.startTransaction("removePorts");
|
||||
var nodedata = port.part.data;
|
||||
var side = port._side; // there are four property names, all ending in "Array"
|
||||
myDiagram.model.setDataProperty(nodedata, side + "Array", []); // an empty Array
|
||||
myDiagram.commitTransaction("removePorts");
|
||||
}
|
||||
|
||||
// Change the color of the clicked port.
|
||||
function changeColor(port) {
|
||||
myDiagram.startTransaction("colorPort");
|
||||
var data = port.data;
|
||||
myDiagram.model.setDataProperty(data, "portColor", getPortColor());
|
||||
myDiagram.commitTransaction("colorPort");
|
||||
}
|
||||
|
||||
// Use some pastel colors for ports
|
||||
function getPortColor() {
|
||||
var portColors = ["#fae3d7", "#d6effc", "#ebe3fc", "#eaeef8", "#fadfe5", "#6cafdb", "#66d6d1"]
|
||||
return portColors[Math.floor(Math.random() * portColors.length)];
|
||||
}
|
||||
|
||||
// Save the model to / load it from JSON text shown on the page itself, not in a database.
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
|
||||
// When copying a node, we need to copy the data that the node is bound to.
|
||||
// This JavaScript object includes properties for the node as a whole, and
|
||||
// four properties that are Arrays holding data for each port.
|
||||
// Those arrays and port data objects need to be copied too.
|
||||
// Thus Model.copiesArrays and Model.copiesArrayObjects both need to be true.
|
||||
|
||||
// Link data includes the names of the to- and from- ports;
|
||||
// so the GraphLinksModel needs to set these property names:
|
||||
// linkFromPortIdProperty and linkToPortIdProperty.
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="width:600px; height:500px; border:1px solid black"></div>
|
||||
Add port to selected nodes:
|
||||
<button onclick="addPort('top')">Top</button>
|
||||
<button onclick="addPort('bottom')">Bottom</button>
|
||||
<button onclick="addPort('left')">Left</button>
|
||||
<button onclick="addPort('right')">Right</button>
|
||||
<p>
|
||||
Double-click in the diagram background in order to add a new node there.
|
||||
In this sample you can add ports to a selected node by clicking the above buttons or by using the context menu.
|
||||
Draw links between ports by dragging between ports.
|
||||
If you select a link you can relink or reshape it.
|
||||
Right-click or touch-hold on a port to bring up a context menu that allows you to remove it or change its color.
|
||||
</p>
|
||||
<p>
|
||||
The diagram also uses a custom link to allow for special routing to help parallel links avoid each other
|
||||
using overridden <a>Link.computeEndSegmentLength</a>, <a>Link.hasCurviness</a>, and <a>Link.computeCurviness</a>
|
||||
functions.
|
||||
</p>
|
||||
<p>
|
||||
See the <a href="../intro/ports.html">Ports Intro page</a> for an explanation of GoJS ports.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
</div>
|
||||
|
||||
<textarea id="mySavedModel" style="width:100%;height:250px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"copiesArrays": true,
|
||||
"copiesArrayObjects": true,
|
||||
"linkFromPortIdProperty": "fromPort",
|
||||
"linkToPortIdProperty": "toPort",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "name":"Unit One", "loc":"101 204",
|
||||
"leftArray":[ {"portColor":"#fae3d7", "portId":"left0"} ],
|
||||
"topArray":[ {"portColor":"#d6effc", "portId":"top0"} ],
|
||||
"bottomArray":[ {"portColor":"#ebe3fc", "portId":"bottom0"} ],
|
||||
"rightArray":[ {"portColor":"#eaeef8", "portId":"right0"},{"portColor":"#fadfe5", "portId":"right1"} ] },
|
||||
{"key":2, "name":"Unit Two", "loc":"320 152",
|
||||
"leftArray":[ {"portColor":"#6cafdb", "portId":"left0"},{"portColor":"#66d6d1", "portId":"left1"},{"portColor":"#fae3d7", "portId":"left2"} ],
|
||||
"topArray":[ {"portColor":"#d6effc", "portId":"top0"} ],
|
||||
"bottomArray":[ {"portColor":"#eaeef8", "portId":"bottom0"},{"portColor":"#eaeef8", "portId":"bottom1"},{"portColor":"#6cafdb", "portId":"bottom2"} ],
|
||||
"rightArray":[ ] },
|
||||
{"key":3, "name":"Unit Three", "loc":"384 319",
|
||||
"leftArray":[ {"portColor":"#66d6d1", "portId":"left0"},{"portColor":"#fadfe5", "portId":"left1"},{"portColor":"#6cafdb", "portId":"left2"} ],
|
||||
"topArray":[ {"portColor":"#66d6d1", "portId":"top0"} ],
|
||||
"bottomArray":[ {"portColor":"#6cafdb", "portId":"bottom0"} ],
|
||||
"rightArray":[ ] },
|
||||
{"key":4, "name":"Unit Four", "loc":"138 351",
|
||||
"leftArray":[ {"portColor":"#fae3d7", "portId":"left0"} ],
|
||||
"topArray":[ {"portColor":"#6cafdb", "portId":"top0"} ],
|
||||
"bottomArray":[ {"portColor":"#6cafdb", "portId":"bottom0"} ],
|
||||
"rightArray":[ {"portColor":"#6cafdb", "portId":"right0"},{"portColor":"#66d6d1", "portId":"right1"} ] }
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":4, "to":2, "fromPort":"top0", "toPort":"bottom0"},
|
||||
{"from":4, "to":2, "fromPort":"top0", "toPort":"bottom0"},
|
||||
{"from":3, "to":2, "fromPort":"top0", "toPort":"bottom1"},
|
||||
{"from":4, "to":3, "fromPort":"right0", "toPort":"left0"},
|
||||
{"from":4, "to":3, "fromPort":"right1", "toPort":"left2"},
|
||||
{"from":1, "to":2, "fromPort":"right0", "toPort":"left1"},
|
||||
{"from":1, "to":2, "fromPort":"right1", "toPort":"left2"}
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,194 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Entity Relationship</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive entity-relationship diagram or data model diagram implemented by GoJS in JavaScript for HTML." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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 name or refer to the DIV HTML element
|
||||
{
|
||||
allowDelete: false,
|
||||
allowCopy: false,
|
||||
layout: $(go.ForceDirectedLayout),
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
var colors = {
|
||||
'red': '#be4b15',
|
||||
'green': '#52ce60',
|
||||
'blue': '#6ea5f8',
|
||||
'lightred': '#fd8852',
|
||||
'lightblue': '#afd4fe',
|
||||
'lightgreen': '#b9e986',
|
||||
'pink': '#faadc1',
|
||||
'purple': '#d689ff',
|
||||
'orange': '#fdb400',
|
||||
}
|
||||
|
||||
// the template for each attribute in a node's array of item data
|
||||
var itemTempl =
|
||||
$(go.Panel, "Horizontal",
|
||||
$(go.Shape,
|
||||
{ desiredSize: new go.Size(15, 15), strokeJoin: "round", strokeWidth: 3, stroke: null, margin: 2 },
|
||||
new go.Binding("figure", "figure"),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "color")),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
stroke: "#333333",
|
||||
font: "bold 14px sans-serif"
|
||||
},
|
||||
new go.Binding("text", "name"))
|
||||
);
|
||||
|
||||
// define the Node template, representing an entity
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto", // the whole node panel
|
||||
{
|
||||
selectionAdorned: true,
|
||||
resizable: true,
|
||||
layoutConditions: go.Part.LayoutStandard & ~go.Part.LayoutNodeSized,
|
||||
fromSpot: go.Spot.AllSides,
|
||||
toSpot: go.Spot.AllSides,
|
||||
isShadowed: true,
|
||||
shadowOffset: new go.Point(3, 3),
|
||||
shadowColor: "#C5C1AA"
|
||||
},
|
||||
new go.Binding("location", "location").makeTwoWay(),
|
||||
// whenever the PanelExpanderButton changes the visible property of the "LIST" panel,
|
||||
// clear out any desiredSize set by the ResizingTool.
|
||||
new go.Binding("desiredSize", "visible", function(v) { return new go.Size(NaN, NaN); }).ofObject("LIST"),
|
||||
// define the node's outer shape, which will surround the Table
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
{ fill: 'white', stroke: "#eeeeee", strokeWidth: 3 }),
|
||||
$(go.Panel, "Table",
|
||||
{ margin: 8, stretch: go.GraphObject.Fill },
|
||||
$(go.RowColumnDefinition, { row: 0, sizing: go.RowColumnDefinition.None }),
|
||||
// the table header
|
||||
$(go.TextBlock,
|
||||
{
|
||||
row: 0, alignment: go.Spot.Center,
|
||||
margin: new go.Margin(0, 24, 0, 2), // leave room for Button
|
||||
font: "bold 16px sans-serif"
|
||||
},
|
||||
new go.Binding("text", "key")),
|
||||
// the collapse/expand button
|
||||
$("PanelExpanderButton", "LIST", // the name of the element whose visibility this button toggles
|
||||
{ row: 0, alignment: go.Spot.TopRight }),
|
||||
// the list of Panels, each showing an attribute
|
||||
$(go.Panel, "Vertical",
|
||||
{
|
||||
name: "LIST",
|
||||
row: 1,
|
||||
padding: 3,
|
||||
alignment: go.Spot.TopLeft,
|
||||
defaultAlignment: go.Spot.Left,
|
||||
stretch: go.GraphObject.Horizontal,
|
||||
itemTemplate: itemTempl
|
||||
},
|
||||
new go.Binding("itemArray", "items"))
|
||||
) // end Table Panel
|
||||
); // end Node
|
||||
|
||||
// define the Link template, representing a relationship
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{
|
||||
selectionAdorned: true,
|
||||
layerName: "Foreground",
|
||||
reshapable: true,
|
||||
routing: go.Link.AvoidsNodes,
|
||||
corner: 5,
|
||||
curve: go.Link.JumpOver
|
||||
},
|
||||
$(go.Shape, // the link shape
|
||||
{ stroke: "#303B45", strokeWidth: 2.5 }),
|
||||
$(go.TextBlock, // the "from" label
|
||||
{
|
||||
textAlign: "center",
|
||||
font: "bold 14px sans-serif",
|
||||
stroke: "#1967B3",
|
||||
segmentIndex: 0,
|
||||
segmentOffset: new go.Point(NaN, NaN),
|
||||
segmentOrientation: go.Link.OrientUpright
|
||||
},
|
||||
new go.Binding("text", "text")),
|
||||
$(go.TextBlock, // the "to" label
|
||||
{
|
||||
textAlign: "center",
|
||||
font: "bold 14px sans-serif",
|
||||
stroke: "#1967B3",
|
||||
segmentIndex: -1,
|
||||
segmentOffset: new go.Point(NaN, NaN),
|
||||
segmentOrientation: go.Link.OrientUpright
|
||||
},
|
||||
new go.Binding("text", "toText"))
|
||||
);
|
||||
|
||||
// create the model for the E-R diagram
|
||||
var nodeDataArray = [
|
||||
{
|
||||
key: "Products",
|
||||
items: [{ name: "ProductID", iskey: true, figure: "Decision", color: colors.red },
|
||||
{ name: "ProductName", iskey: false, figure: "Hexagon", color: colors.blue },
|
||||
{ name: "SupplierID", iskey: false, figure: "Decision", color: "purple" },
|
||||
{ name: "CategoryID", iskey: false, figure: "Decision", color: "purple" }]
|
||||
},
|
||||
{
|
||||
key: "Suppliers",
|
||||
items: [{ name: "SupplierID", iskey: true, figure: "Decision", color: colors.red },
|
||||
{ name: "CompanyName", iskey: false, figure: "Hexagon", color: colors.blue },
|
||||
{ name: "ContactName", iskey: false, figure: "Hexagon", color: colors.blue },
|
||||
{ name: "Address", iskey: false, figure: "Hexagon", color: colors.blue }]
|
||||
},
|
||||
{
|
||||
key: "Categories",
|
||||
items: [{ name: "CategoryID", iskey: true, figure: "Decision", color: colors.red },
|
||||
{ name: "CategoryName", iskey: false, figure: "Hexagon", color: colors.blue },
|
||||
{ name: "Description", iskey: false, figure: "Hexagon", color: colors.blue },
|
||||
{ name: "Picture", iskey: false, figure: "TriangleUp", color: colors.pink }]
|
||||
},
|
||||
{
|
||||
key: "Order Details",
|
||||
items: [{ name: "OrderID", iskey: true, figure: "Decision", color: colors.red },
|
||||
{ name: "ProductID", iskey: true, figure: "Decision", color: colors.red },
|
||||
{ name: "UnitPrice", iskey: false, figure: "Circle", color: colors.green },
|
||||
{ name: "Quantity", iskey: false, figure: "Circle", color: colors.green },
|
||||
{ name: "Discount", iskey: false, figure: "Circle", color: colors.green }]
|
||||
},
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: "Products", to: "Suppliers", text: "0..N", toText: "1" },
|
||||
{ from: "Products", to: "Categories", text: "0..N", toText: "1" },
|
||||
{ from: "Order Details", to: "Products", text: "0..N", toText: "1" }
|
||||
];
|
||||
myDiagram.model = $(go.GraphLinksModel,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray: nodeDataArray,
|
||||
linkDataArray: linkDataArray
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 700px"></div>
|
||||
<p>Sample for representing the relationship between various entities. Try dragging the nodes -- their links will avoid other nodes, by virtue of the <a>Link,AvoidsNodes</a> property assigned to the
|
||||
custom link template's <a>Link.routing</a>. Also note the use of <a href="../intro/buttons.html" target="_blank">Panel Expander Buttons</a> to allow for expandable/collapsible node data.
|
||||
</p>
|
||||
<p>Buttons are defined in <a href="../extensions/Buttons.js">Buttons.js</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Euler Diagram</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A diagram showing nodes connected by different kinds of links with concentric circular backgrounds. Clicking on a node opens a window to a Wikipedia page." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/HyperlinkText.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{ isReadOnly: true, allowSelect: false, contentAlignment: go.Spot.Center });
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("location", "loc", go.Point.parse),
|
||||
$(go.Shape, "Ellipse",
|
||||
{ fill: "transparent" },
|
||||
new go.Binding("stroke", "color"),
|
||||
new go.Binding("strokeWidth", "width"),
|
||||
new go.Binding("strokeDashArray", "dash")),
|
||||
$("HyperlinkText",
|
||||
function(node) { return "https://en.wikipedia.org/w/index.php?search=" + encodeURIComponent(node.data.text); },
|
||||
function(node) { return node.data.text; },
|
||||
{ margin: 1, maxSize: new go.Size(80, 80), textAlign: "center" })
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplateMap.add("center",
|
||||
$(go.Node, "Spot",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("location", "loc", go.Point.parse),
|
||||
$(go.Shape, "Circle",
|
||||
{
|
||||
fill: "rgba(128,128,128,0.1)", stroke: null,
|
||||
width: 550, height: 550
|
||||
}),
|
||||
$(go.Shape, "Circle",
|
||||
{
|
||||
fill: "rgba(128,128,128,0.05)", stroke: null,
|
||||
width: 400, height: 400
|
||||
}),
|
||||
$(go.Shape, "Circle",
|
||||
{
|
||||
fill: "rgba(128,128,128,0.033)", stroke: null,
|
||||
width: 250, height: 250
|
||||
}),
|
||||
$(go.Panel, "Spot",
|
||||
$(go.Shape, "Circle",
|
||||
{ isPanelMain: true, fill: "transparent", portId: "" },
|
||||
new go.Binding("stroke", "hicolor"),
|
||||
new go.Binding("strokeWidth", "hiwidth")),
|
||||
$(go.Shape, "Circle",
|
||||
{ isPanelMain: true, fill: "transparent" },
|
||||
new go.Binding("stroke", "color"),
|
||||
new go.Binding("strokeWidth", "width"),
|
||||
new go.Binding("strokeDashArray", "dash")),
|
||||
$("HyperlinkText",
|
||||
function(node) { return "https://en.wikipedia.org/w/index.php?search=" + encodeURIComponent(node.data.text); },
|
||||
function(node) { return node.data.text; },
|
||||
{ margin: 1, maxSize: new go.Size(80, 80), textAlign: "center" })
|
||||
)
|
||||
));
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
$(go.Shape,
|
||||
new go.Binding("stroke", "color"),
|
||||
new go.Binding("strokeWidth", "width"),
|
||||
new go.Binding("strokeDashArray", "dash"))
|
||||
);
|
||||
|
||||
var nodeDataArray = [
|
||||
{ key: 1, text: "Cognitive Procedural", loc: "300 300", category: "center" },
|
||||
{ key: 2, text: "Cognitive Problem Solving", loc: "600 300", category: "center", hicolor: "lightblue", hiwidth: 7 },
|
||||
{ key: 11, text: "Logical Reasoning", loc: "450 275" },
|
||||
{ key: 12, text: "Scaffolding", loc: "450 325" },
|
||||
{ key: 13, text: "Part Task Training", loc: "425 400" },
|
||||
{ key: 21, text: "Training Wheels", loc: "325 125" },
|
||||
{ key: 22, text: "Exploratory Learning", loc: "250 150" },
|
||||
{ key: 23, text: "Learner Control", loc: "650 150" },
|
||||
{ key: 31, text: "Overlearning", loc: "450 475" }
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: 1, to: 11, color: "gray" },
|
||||
{ from: 1, to: 12, color: "gray", dash: [3, 2] },
|
||||
{ from: 1, to: 13, color: "olive", width: 2 },
|
||||
{ from: 1, to: 21, color: "olive", width: 3 },
|
||||
{ from: 1, to: 22, color: "olive", width: 2 },
|
||||
{ from: 1, to: 23, color: "crimson", width: 2 },
|
||||
{ from: 1, to: 31 },
|
||||
{ from: 2, to: 11, color: "gray" },
|
||||
{ from: 2, to: 12, color: "olive", width: 2 },
|
||||
{ from: 2, to: 13, color: "gray", dash: [3, 2] },
|
||||
{ from: 2, to: 21, color: "crimson", width: 2 },
|
||||
{ from: 2, to: 22, color: "crimson", width: 2 },
|
||||
{ from: 2, to: 23, color: "black", width: 3 },
|
||||
{ from: 2, to: 31, color: "black", dash: [3, 2] }
|
||||
];
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p> A sample of a Euler diagram: that is, a means of representing various sets and their relationships with one another. Euler diagrams have much in common with Venn diagrams.
|
||||
This diagram is read-only, but clicking on a node will search Wikipedia
|
||||
with a query string generated from the "text" property of the node data.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,221 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Family Tree (British)</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A family tree diagram of British royalty." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<link href='https://fonts.googleapis.com/css?family=Droid+Serif:400,700' rel='stylesheet' type='text/css'>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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
|
||||
{
|
||||
"toolManager.hoverDelay": 100, // 100 milliseconds instead of the default 850
|
||||
allowCopy: false,
|
||||
layout: // create a TreeLayout for the family tree
|
||||
$(go.TreeLayout,
|
||||
{ angle: 90, nodeSpacing: 10, layerSpacing: 40, layerStyle: go.TreeLayout.LayerUniform })
|
||||
});
|
||||
|
||||
var bluegrad = '#90CAF9';
|
||||
var pinkgrad = '#F48FB1';
|
||||
|
||||
// Set up a Part as a legend, and place it directly on the diagram
|
||||
myDiagram.add(
|
||||
$(go.Part, "Table",
|
||||
{ position: new go.Point(300, 10), selectable: false },
|
||||
$(go.TextBlock, "Key",
|
||||
{ row: 0, font: "700 14px Droid Serif, sans-serif" }), // end row 0
|
||||
$(go.Panel, "Horizontal",
|
||||
{ row: 1, alignment: go.Spot.Left },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ desiredSize: new go.Size(30, 30), fill: bluegrad, margin: 5 }),
|
||||
$(go.TextBlock, "Males",
|
||||
{ font: "700 13px Droid Serif, sans-serif" })
|
||||
), // end row 1
|
||||
$(go.Panel, "Horizontal",
|
||||
{ row: 2, alignment: go.Spot.Left },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ desiredSize: new go.Size(30, 30), fill: pinkgrad, margin: 5 }),
|
||||
$(go.TextBlock, "Females",
|
||||
{ font: "700 13px Droid Serif, sans-serif" })
|
||||
) // end row 2
|
||||
));
|
||||
|
||||
// get tooltip text from the object's data
|
||||
function tooltipTextConverter(person) {
|
||||
var str = "";
|
||||
str += "Born: " + person.birthYear;
|
||||
if (person.deathYear !== undefined) str += "\nDied: " + person.deathYear;
|
||||
if (person.reign !== undefined) str += "\nReign: " + person.reign;
|
||||
return str;
|
||||
}
|
||||
|
||||
// define tooltips for nodes
|
||||
var tooltiptemplate =
|
||||
$("ToolTip",
|
||||
{ "Border.fill": "whitesmoke", "Border.stroke": "black" },
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "bold 8pt Helvetica, bold Arial, sans-serif",
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
margin: 5
|
||||
},
|
||||
new go.Binding("text", "", tooltipTextConverter))
|
||||
);
|
||||
|
||||
// define Converters to be used for Bindings
|
||||
function genderBrushConverter(gender) {
|
||||
if (gender === "M") return bluegrad;
|
||||
if (gender === "F") return pinkgrad;
|
||||
return "orange";
|
||||
}
|
||||
|
||||
// replace the default Node template in the nodeTemplateMap
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ deletable: false, toolTip: tooltiptemplate },
|
||||
new go.Binding("text", "name"),
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
fill: "lightgray",
|
||||
stroke: null, strokeWidth: 0,
|
||||
stretch: go.GraphObject.Fill,
|
||||
alignment: go.Spot.Center
|
||||
},
|
||||
new go.Binding("fill", "gender", genderBrushConverter)),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "700 12px Droid Serif, sans-serif",
|
||||
textAlign: "center",
|
||||
margin: 10, maxSize: new go.Size(80, NaN)
|
||||
},
|
||||
new go.Binding("text", "name"))
|
||||
);
|
||||
|
||||
// define the Link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ routing: go.Link.Orthogonal, corner: 5, selectable: false },
|
||||
$(go.Shape, { strokeWidth: 3, stroke: '#424242' })); // the gray link shape
|
||||
|
||||
// here's the family data
|
||||
var nodeDataArray = [
|
||||
{ key: 0, name: "George V", gender: "M", birthYear: "1865", deathYear: "1936", reign: "1910-1936" },
|
||||
{ key: 1, parent: 0, name: "Edward VIII", gender: "M", birthYear: "1894", deathYear: "1972", reign: "1936" },
|
||||
{ key: 2, parent: 0, name: "George VI", gender: "M", birthYear: "1895", deathYear: "1952", reign: "1936-1952" },
|
||||
{ key: 7, parent: 2, name: "Elizabeth II", gender: "F", birthYear: "1926", reign: "1952-" },
|
||||
{ key: 16, parent: 7, name: "Charles, Prince of Wales", gender: "M", birthYear: "1948" },
|
||||
{ key: 38, parent: 16, name: "Prince William", gender: "M", birthYear: "1982" },
|
||||
{ key: 39, parent: 16, name: "Prince Harry of Wales", gender: "M", birthYear: "1984" },
|
||||
{ key: 17, parent: 7, name: "Anne, Princess Royal", gender: "F", birthYear: "1950" },
|
||||
{ key: 40, parent: 17, name: "Peter Phillips", gender: "M", birthYear: "1977" },
|
||||
{ key: 82, parent: 40, name: "Savannah Phillips", gender: "F", birthYear: "2010" },
|
||||
{ key: 41, parent: 17, name: "Zara Phillips", gender: "F", birthYear: "1981" },
|
||||
{ key: 18, parent: 7, name: "Prince Andrew", gender: "M", birthYear: "1960" },
|
||||
{ key: 42, parent: 18, name: "Princess Beatrice of York", gender: "F", birthYear: "1988" },
|
||||
{ key: 43, parent: 18, name: "Princess Eugenie of York", gender: "F", birthYear: "1990" },
|
||||
{ key: 19, parent: 7, name: "Prince Edward", gender: "M", birthYear: "1964" },
|
||||
{ key: 44, parent: 19, name: "Lady Louise Windsor", gender: "F", birthYear: "2003" },
|
||||
{ key: 45, parent: 19, name: "James, Viscount Severn", gender: "M", birthYear: "2007" },
|
||||
{ key: 8, parent: 2, name: "Princess Margaret", gender: "F", birthYear: "1930", deathYear: "2002" },
|
||||
{ key: 20, parent: 8, name: "David Armstrong-Jones", gender: "M", birthYear: "1961" },
|
||||
{ key: 21, parent: 8, name: "Lady Sarah Chatto", gender: "F", birthYear: "1964" },
|
||||
{ key: 46, parent: 21, name: "Samuel Chatto", gender: "M", birthYear: "1996" },
|
||||
{ key: 47, parent: 21, name: "Arthur Chatto", gender: "M", birthYear: "1999" },
|
||||
{ key: 3, parent: 0, name: "Mary, Princess Royal", gender: "F", birthYear: "1897", deathYear: "1965" },
|
||||
{ key: 9, parent: 3, name: "George Lascelles", gender: "M", birthYear: "1923", deathYear: "2011" },
|
||||
{ key: 22, parent: 9, name: "David Lascelles", gender: "M", birthYear: "1950" },
|
||||
{ key: 48, parent: 22, name: "Emily Shard", gender: "F", birthYear: "1975" },
|
||||
{ key: 49, parent: 22, name: "Benjamin Lascelles", gender: "M", birthYear: "1978" },
|
||||
{ key: 50, parent: 22, name: "Alexander Lascelles", gender: "M", birthYear: "1980" },
|
||||
{ key: 51, parent: 22, name: "Edward Lascelles", gender: "M", birthYear: "1982" },
|
||||
{ key: 23, parent: 9, name: "James Lascelles", gender: "M", birthYear: "1953" },
|
||||
{ key: 52, parent: 23, name: "Sophie Lascelles", gender: "F", birthYear: "1973" },
|
||||
{ key: 53, parent: 23, name: "Rowan Lascelles", gender: "M", birthYear: "1977" },
|
||||
{ key: 54, parent: 23, name: "Tanit Lascelles", gender: "F", birthYear: "1981" },
|
||||
{ key: 55, parent: 23, name: "Tewa Lascelles", gender: "M", birthYear: "1985" },
|
||||
{ key: 24, parent: 9, name: "Jeremy Lascelles", gender: "M", birthYear: "1955" },
|
||||
{ key: 56, parent: 24, name: "Thomas Lascelles", gender: "M", birthYear: "1982" },
|
||||
{ key: 57, parent: 24, name: "Ellen Lascelles", gender: "F", birthYear: "1984" },
|
||||
{ key: 58, parent: 24, name: "Amy Lascelles", gender: "F", birthYear: "1986" },
|
||||
{ key: 59, parent: 24, name: "Tallulah Lascelles", gender: "F", birthYear: "2005" },
|
||||
{ key: 25, parent: 9, name: "Mark Lascelles", gender: "M", birthYear: "1964" },
|
||||
{ key: 60, parent: 25, name: "Charlotte Lascelles", gender: "F", birthYear: "1996" },
|
||||
{ key: 61, parent: 25, name: "Imogen Lascelles", gender: "F", birthYear: "1998" },
|
||||
{ key: 62, parent: 25, name: "Miranda Lascelles", gender: "F", birthYear: "2000" },
|
||||
{ key: 10, parent: 3, name: "Gerald Lascelles", gender: "M", birthYear: "1924", deathYear: "1998" },
|
||||
{ key: 26, parent: 10, name: "Henry Lascelles", gender: "M", birthYear: "1953" },
|
||||
{ key: 63, parent: 26, name: "Maximilian Lascelles", gender: "M", birthYear: "1991" },
|
||||
{ key: 27, parent: 10, name: "Martin David Lascelles", gender: "M", birthYear: "1962" },
|
||||
{ key: 64, parent: 27, name: "Alexander Lascelles", gender: "M", birthYear: "2002" },
|
||||
{ key: 4, parent: 0, name: "Prince Henry", gender: "M", birthYear: "1900", deathYear: "1974" },
|
||||
{ key: 11, parent: 4, name: "Prince William of Gloucester", gender: "M", birthYear: "1941", deathYear: "1972" },
|
||||
{ key: 12, parent: 4, name: "Prince Richard", gender: "M", birthYear: "1944" },
|
||||
{ key: 28, parent: 12, name: "Alexander Windsor", gender: "M", birthYear: "1974" },
|
||||
{ key: 65, parent: 28, name: "Xan Windsor", gender: "M", birthYear: "2007" },
|
||||
{ key: 66, parent: 28, name: "Lady Cosima Windsor", gender: "F", birthYear: "2010" },
|
||||
{ key: 29, parent: 12, name: "Lady Davina Lewis", gender: "F", birthYear: "1977" },
|
||||
{ key: 67, parent: 29, name: "Senna Lewis", gender: "F", birthYear: "2010" },
|
||||
{ key: 30, parent: 12, name: "Lady Rose Gilman", gender: "F", birthYear: "1980" },
|
||||
{ key: 68, parent: 30, name: "Lyla Gilman", gender: "F", birthYear: "2010" },
|
||||
{ key: 5, parent: 0, name: "Prince George", gender: "M", birthYear: "1902", deathYear: "1942" },
|
||||
{ key: 13, parent: 5, name: "Prince Edward", gender: "M", birthYear: "1935" },
|
||||
{ key: 31, parent: 13, name: "George Windsor", gender: "M", birthYear: "1962" },
|
||||
{ key: 69, parent: 31, name: "Edward Windsor", gender: "M", birthYear: "1988" },
|
||||
{ key: 70, parent: 31, name: "Lady Marina-Charlotte Windsor", gender: "F", birthYear: "1992" },
|
||||
{ key: 71, parent: 31, name: "Lady Amelia Windsor", gender: "F", birthYear: "1995" },
|
||||
{ key: 32, parent: 13, name: "Lady Helen Taylor", gender: "F", birthYear: "1964" },
|
||||
{ key: 72, parent: 32, name: "Columbus Taylor", gender: "M", birthYear: "1994" },
|
||||
{ key: 73, parent: 32, name: "Cassius Taylor", gender: "M", birthYear: "1996" },
|
||||
{ key: 74, parent: 32, name: "Eloise Taylor", gender: "F", birthYear: "2003" },
|
||||
{ key: 75, parent: 32, name: "Estella Taylor", gender: "F", birthYear: "2004" },
|
||||
{ key: 33, parent: 13, name: "Lord Nicholas Windsor", gender: "M", birthYear: "1970" },
|
||||
{ key: 76, parent: 33, name: "Albert Windsor", gender: "M", birthYear: "2007" },
|
||||
{ key: 77, parent: 33, name: "Leopold Windsor", gender: "M", birthYear: "2009" },
|
||||
{ key: 14, parent: 5, name: "Princess Alexandra", gender: "F", birthYear: "1936" },
|
||||
{ key: 34, parent: 14, name: "James Ogilvy", gender: "M", birthYear: "1964" },
|
||||
{ key: 78, parent: 34, name: "Flora Ogilvy", gender: "F", birthYear: "1994" },
|
||||
{ key: 79, parent: 34, name: "Alexander Ogilvy", gender: "M", birthYear: "1996" },
|
||||
{ key: 35, parent: 14, name: "Marina Ogilvy", gender: "F", birthYear: "1966" },
|
||||
{ key: 80, parent: 35, name: "Zenouska Mowatt", gender: "F", birthYear: "1990" },
|
||||
{ key: 81, parent: 35, name: "Christian Mowatt", gender: "M", birthYear: "1993" },
|
||||
{ key: 15, parent: 5, name: "Prince Michael of Kent", gender: "M", birthYear: "1942" },
|
||||
{ key: 36, parent: 15, name: "Lord Frederick Windsor", gender: "M", birthYear: "1979" },
|
||||
{ key: 37, parent: 15, name: "Lady Gabriella Windsor", gender: "F", birthYear: "1981" },
|
||||
{ key: 6, parent: 0, name: "Prince John", gender: "M", birthYear: "1905", deathYear: "1919" }
|
||||
];
|
||||
|
||||
// create the model for the family tree
|
||||
myDiagram.model = new go.TreeModel(nodeDataArray);
|
||||
|
||||
document.getElementById('zoomToFit').addEventListener('click', function() {
|
||||
myDiagram.commandHandler.zoomToFit();
|
||||
});
|
||||
|
||||
document.getElementById('centerRoot').addEventListener('click', function() {
|
||||
myDiagram.scale = 1;
|
||||
myDiagram.scrollToRect(myDiagram.findNodeForKey(0).actualBounds);
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 550px"></div>
|
||||
<p><button id="zoomToFit">Zoom to Fit</button> <button id="centerRoot">Center on root</button></p>
|
||||
<p>This family tree diagram shows several generations of British royalty beginning with George V (1865-1936).</p>
|
||||
<p><a>Node</a> data contains information about gender, and a data binding assigns a corresponding color. Additional data is displayed with a tooltip. A key is placed on the diagram using a <a>Panel,Table</a>.</p>
|
||||
<p>For a variation of this tree, see the <a href="familyTreeJP.html">Japanese family tree sample</a>.</p>
|
||||
<p>For a more complex family tree see the <a href="genogram.html">genogram sample</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,207 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Family Tree (Japanese)</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A family tree diagram showing Japanese royalty." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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
|
||||
{
|
||||
allowCopy: false,
|
||||
layout: // create a TreeLayout for the family tree
|
||||
$(go.TreeLayout,
|
||||
{ angle: 90, nodeSpacing: 5 })
|
||||
});
|
||||
|
||||
var bluegrad = $(go.Brush, "Linear", { 0: "rgb(60, 204, 254)", 1: "rgb(70, 172, 254)" });
|
||||
var pinkgrad = $(go.Brush, "Linear", { 0: "rgb(255, 192, 203)", 1: "rgb(255, 142, 203)" });
|
||||
|
||||
// Set up a Part as a legend, and place it directly on the diagram
|
||||
myDiagram.add(
|
||||
$(go.Part, "Table",
|
||||
{ position: new go.Point(10, 10), selectable: false },
|
||||
$(go.TextBlock, "Key",
|
||||
{ row: 0, font: "bold 10pt Helvetica, Arial, sans-serif" }), // end row 0
|
||||
$(go.Panel, "Horizontal",
|
||||
{ row: 1, alignment: go.Spot.Left },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ desiredSize: new go.Size(30, 30), fill: bluegrad, margin: 5 }),
|
||||
$(go.TextBlock, "Males",
|
||||
{ font: "bold 8pt Helvetica, bold Arial, sans-serif" })
|
||||
), // end row 1
|
||||
$(go.Panel, "Horizontal",
|
||||
{ row: 2, alignment: go.Spot.Left },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ desiredSize: new go.Size(30, 30), fill: pinkgrad, margin: 5 }),
|
||||
$(go.TextBlock, "Females",
|
||||
{ font: "bold 8pt Helvetica, bold Arial, sans-serif" })
|
||||
) // end row 2
|
||||
));
|
||||
|
||||
// get tooltip text from the object's data
|
||||
function tooltipTextConverter(person) {
|
||||
var str = "";
|
||||
str += "Born: " + person.birthYear;
|
||||
if (person.deathYear !== undefined) str += "\nDied: " + person.deathYear;
|
||||
if (person.reign !== undefined) str += "\nReign: " + person.reign;
|
||||
return str;
|
||||
}
|
||||
|
||||
// define tooltips for nodes
|
||||
var tooltiptemplate =
|
||||
$("ToolTip",
|
||||
{ "Border.fill": "whitesmoke", "Border.stroke": "black" },
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "bold 8pt Helvetica, bold Arial, sans-serif",
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
margin: 5
|
||||
},
|
||||
new go.Binding("text", "", tooltipTextConverter))
|
||||
);
|
||||
|
||||
// define Converters to be used for Bindings
|
||||
function genderBrushConverter(gender) {
|
||||
if (gender === "M") return bluegrad;
|
||||
if (gender === "F") return pinkgrad;
|
||||
return "orange";
|
||||
}
|
||||
|
||||
// replace the default Node template in the nodeTemplateMap
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ deletable: false, toolTip: tooltiptemplate },
|
||||
new go.Binding("text", "name"),
|
||||
$(go.Shape, "Rectangle",
|
||||
{
|
||||
fill: "orange",
|
||||
stroke: "black",
|
||||
stretch: go.GraphObject.Fill,
|
||||
alignment: go.Spot.Center
|
||||
},
|
||||
new go.Binding("fill", "gender", genderBrushConverter)),
|
||||
$(go.Panel, "Vertical",
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "bold 8pt Helvetica, bold Arial, sans-serif",
|
||||
alignment: go.Spot.Center,
|
||||
margin: 6
|
||||
},
|
||||
new go.Binding("text", "name")),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "kanjiName"))
|
||||
)
|
||||
);
|
||||
|
||||
// define the Link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ routing: go.Link.Orthogonal, corner: 5, selectable: false },
|
||||
$(go.Shape)); // the default black link shape
|
||||
|
||||
// here's the family data
|
||||
var nodeDataArray = [
|
||||
{ key: 0, name: "Osahito", gender: "M", fullTitle: "Emperor Kōmei", kanjiName: "統仁 孝明天皇", posthumousName: "Komei", birthYear: "1831", deathYear: "1867" },
|
||||
{ key: 1, parent: 0, name: "Matsuhito", gender: "M", fullTitle: "Emperor Meiji", kanjiName: "睦仁 明治天皇", posthumousName: "Meiji", birthYear: "1852", deathYear: "1912" },
|
||||
{ key: 2, parent: 1, name: "Toshiko", gender: "F", fullTitle: "Princess Yasu-no-Miya Toshiko", birthYear: "1896", deathYear: "1978", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 3, parent: 2, name: "Higashikuni Morihiro", gender: "M", fullTitle: "Prince Higashikuni Morihiro", kanjiName: "東久邇宮 盛厚王", birthYear: "1916", deathYear: "1969", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 4, parent: 3, name: "See spouse for descendants" },
|
||||
{ key: 5, parent: 2, name: "Moromasa", gender: "M", fullTitle: "Prince Moromasa", kanjiName: "師正王", birthYear: "1917", deathYear: "1923" },
|
||||
{ key: 6, parent: 2, name: "Akitsune", gender: "M", fullTitle: "Prince Akitsune", kanjiName: "彰常王", birthYear: "1920", deathYear: "2006", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 7, parent: 2, name: "Toshihiko", gender: "M", fullTitle: "Prince Toshihiko", kanjiName: "俊彦王", birthYear: "1929", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 8, parent: 1, name: "Yoshihito", gender: "M", fullTitle: "Emperor Taishō", kanjiName: "嘉仁 大正天皇,", posthumousName: "Taisho", birthYear: "1879", deathYear: "1926" },
|
||||
{ key: 9, parent: 8, name: "Hirohito", gender: "M", fullTitle: "Emperor Showa", kanjiName: "裕仁 昭和天皇", posthumousName: "Showa", birthYear: "1901", deathYear: "1989" },
|
||||
{ key: 10, parent: 9, name: "Higashikuni Shigeko", gender: "F", spouse: "Higashikuni Morihiro", spouseKanji: "東久邇宮 盛厚王", fullTitle: "Princess Shigeko Higashikuni", kanjiName: "東久邇成子", birthYear: "1925", deathYear: "1961", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 11, parent: 10, name: "Higashikuni Nobuhiko", gender: "M", fullTitle: "Prince Higashikuni Nobuhiko", kanjiName: "東久邇宮 信彦王", birthYear: "1945", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 12, parent: 11, name: "Higashikuni Yukihiko", gender: "M", fullTitle: "No Title", birthYear: "1974" },
|
||||
{ key: 13, parent: 10, name: "Higashikuni Fumiko", gender: "F", fullTitle: "Princess Higashikuni Fumiko", kanjiName: "文子女王", birthYear: "1946", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 14, parent: 10, name: "Higashikuni Naohiko", gender: "M", fullTitle: "No Title", kanjiName: "東久邇真彦", birthYear: "1948" },
|
||||
{ key: 15, parent: 14, name: "Higashikuni Teruhiko", gender: "M", fullTitle: "No Title" },
|
||||
{ key: 16, parent: 14, name: "Higashikuni Matsuhiko", gender: "M", fullTitle: "No Title" },
|
||||
{ key: 17, parent: 10, name: "Higashikuni Hidehiko", gender: "M", fullTitle: "No Title", kanjiName: "東久邇基博", birthYear: "1949" },
|
||||
{ key: 18, parent: 10, name: "Higashikuni Yuko", gender: "F", fullTitle: "No Title", kanjiName: "東久邇優子", birthYear: "1950" },
|
||||
{ key: 19, parent: 9, name: "Sachiko", gender: "F", fullTitle: "Princess Sachiko", kanjiName: "久宮祐子", birthYear: "1927", deathYear: "1928" },
|
||||
{ key: 20, parent: 9, name: "Kazuko Takatsukasa", gender: "F", fullTitle: "Kazuko, Princess Taka", kanjiName: "鷹司 和子", birthYear: "1929", deathYear: "1989", statusChange: "In 1950, lost imperial family status by marrying a commoner" },
|
||||
{ key: 21, parent: 9, name: "Atsuko Ikeda", gender: "F", fullTitle: "Atsuko, Princess Yori", kanjiName: "池田厚子", birthYear: "1931", statusChange: "In 1952, lost imperial family status by marrying a commoner" },
|
||||
{ key: 22, parent: 9, name: "Akihito", gender: "M", fullTitle: "Reigning Emperor of Japan; Tennō", kanjiName: "明仁 今上天皇", posthumousName: "Heisei", birthYear: "1933" },
|
||||
{ key: 23, parent: 22, name: "Naruhito", gender: "M", fullTitle: "Naruhito, Crown Prince of Japan", kanjiName: "皇太子徳仁親王", orderInSuccession: "1", birthYear: "1960" },
|
||||
{ key: 24, parent: 23, name: "Aiko", gender: "F", fullTitle: "Aiko, Princess Toshi", kanjiName: "敬宮愛子内親王", birthYear: "2001" },
|
||||
{ key: 25, parent: 22, name: "Fumihito", gender: "M", fullTitle: "Fumihito, Prince Akishino", kanjiName: "秋篠宮文仁親王", orderInSuccession: "2", birthYear: "1965" },
|
||||
{ key: 26, parent: 25, name: "Mako", gender: "F", fullTitle: "Princess Mako of Akishino", kanjiName: "眞子内親王", birthYear: "1991" },
|
||||
{ key: 27, parent: 25, name: "Kako", gender: "F", fullTitle: "Princess Kako of Akishino", kanjiName: "佳子内親王", birthYear: "1994" },
|
||||
{ key: 28, parent: 25, name: "Hisahito", gender: "M", fullTitle: "Prince Hisahito of Akishino", kanjiName: "悠仁親王", orderInSuccession: "3", birthYear: "2006" },
|
||||
{ key: 29, parent: 22, name: "Sayako Kuroda", gender: "F", fullTitle: "Princess Sayako of Japan", kanjiName: "黒田清子", birthYear: "1969", statusChange: "In 2005, lost imperial family status by marrying a commoner" },
|
||||
{ key: 30, parent: 9, name: "Masahito", gender: "M", fullTitle: "Masahito, Prince Hitachi", kanjiName: "常陸宮正仁親王", orderInSuccession: "4", birthYear: "1935" },
|
||||
{ key: 31, parent: 9, name: "Takako Shimazu", gender: "F", fullTitle: "Princess Takako", kanjiName: "島津貴子", birthYear: "1939", statusChange: "In 1960, lost imperial family status by marrying a commoner" },
|
||||
{ key: 32, parent: 31, name: "Yorihisa Shimazu", gender: "M", fullTitle: "No Title", birthYear: "1962" },
|
||||
{ key: 33, parent: 8, name: "Yasuhito", gender: "M", fullTitle: "Yasuhito, Prince Chichibu of Japan", kanjiName: "秩父宮 雍仁", birthYear: "1902", deathYear: "1953" },
|
||||
{ key: 34, parent: 8, name: "Nobuhito", gender: "M", fullTitle: "Nobuhito, Prince Takamatsu", kanjiName: "高松宮宣仁親王", birthYear: "1905", deathYear: "1987" },
|
||||
{ key: 35, parent: 8, name: "Takahito", gender: "M", fullTitle: "Takahito, Prince Mikasa", kanjiName: "三笠宮崇仁親王", orderInSuccession: "5", birthYear: "1915" },
|
||||
{ key: 36, parent: 35, name: "Yasuko Konoe", gender: "F", fullTitle: "Princess Yasuko of Mikasa", kanjiName: "甯子内親王", birthYear: "1944", statusChange: "In 1966, lost imperial family stutus by marrying a commoner" },
|
||||
{ key: 37, parent: 36, name: "Tadahiro", gender: "M", fullTitle: "None" },
|
||||
{ key: 38, parent: 35, name: "Tomihito", gender: "M", fullTitle: "Prince Tomohito of Mikasa", kanjiName: "三笠宮寬仁", orderInSuccession: "6", birthYear: "1946" },
|
||||
{ key: 39, parent: 38, name: "Akiko", gender: "F", fullTitle: "Princess Akiko of Mikasa", kanjiName: "彬子女王", birthYear: "1981" },
|
||||
{ key: 40, parent: 38, name: "Yoko", gender: "F", fullTitle: "Princess Yoko of Mikasa", kanjiName: "瑶子女王", birthYear: "1983" },
|
||||
{ key: 41, parent: 35, name: "Yoshihito", gender: "M", fullTitle: "Yoshihito, Prince Katsura", kanjiName: "桂宮 宜仁親王", orderInSuccession: "7", birthYear: "1948" },
|
||||
{ key: 42, parent: 35, name: "Masako Sen", gender: "F", fullTitle: "Princess Masako of Mikasa", kanjiName: "容子内親王", birthYear: "1951", statusChange: "In 1983, lost imperial family status by marrying a commoner" },
|
||||
{ key: 43, parent: 42, name: "Akifumi", gender: "M", fullTitle: "No Title" },
|
||||
{ key: 44, parent: 42, name: "Takafumi", gender: "M", fullTitle: "No Title" },
|
||||
{ key: 45, parent: 42, name: "Makiko", gender: "F", fullTitle: "No Title" },
|
||||
{ key: 46, parent: 35, name: "Norihito", gender: "M", fullTitle: "Norihito, Prince Takamado", kanjiName: "高円宮憲仁親王", birthYear: "1954", deathYear: "2002" },
|
||||
{ key: 47, parent: 46, name: "Tsuguko", gender: "F", fullTitle: "Princess Tsuguko of Takamado", kanjiName: "承子女王", birthYear: "1986" },
|
||||
{ key: 48, parent: 46, name: "Noriko", gender: "F", fullTitle: "Princess Noriko of Takamado", kanjiName: "典子女王", birthYear: "1988" },
|
||||
{ key: 49, parent: 46, name: "Ayako", gender: "F", fullTitle: "Princess Ayako of Takamado", kanjiName: "絢子女王", birthYear: "1990" },
|
||||
{ key: 50, parent: 1, name: "Masako", gender: "F", fullTitle: "Princess Masako of Tsune", birthYear: "1888", deathYear: "1940" },
|
||||
{ key: 51, parent: 50, name: "Takeda Tsuneyoshi", gender: "M", fullTitle: "Prince Takeda Tsunehisa", kanjiName: "竹田宮恒徳王", birthYear: "1909", deathYear: "1992", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 52, parent: 51, name: "Takeda Tsunetada", gender: "M", fullTitle: "Prince Takeda Tsunetada", kanjiName: "竹田恒正王", birthYear: "1940", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 53, parent: 52, name: "Takeda Tsunetaka", gender: "M", fullTitle: "No Title", birthYear: "1967" },
|
||||
{ key: 54, parent: 52, name: "Takeda Hiroko", gender: "M", fullTitle: "No Title", birthYear: "1971" },
|
||||
{ key: 55, parent: 51, name: "Takeda Motoko", gender: "F", fullTitle: "Princess Takeda Motoko", kanjiName: "素子女王", birthYear: "1942", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 56, parent: 51, name: "Takeda Tsunekazu", gender: "M", fullTitle: "No Title", kanjiName: "竹田恒和王", birthYear: "1944", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 57, parent: 51, name: "Takeda Noriko", gender: "F", fullTitle: "Princess Takeda Noriko", kanjiName: "紀子女王", birthYear: "1943", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 58, parent: 51, name: "Tsuneharu Takeda", gender: "M", fullTitle: "Prince Tsuneharu Takeda", kanjiName: "竹田恒治王", birthYear: "1945", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 59, parent: 50, name: "Takeda Ayako", gender: "F", fullTitle: "Princess Tsune-no-Miya Takeda Ayako", kanjiName: "禮子女王", birthYear: "1911", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 60, parent: 1, name: "Fusako", gender: "F", fullTitle: "Princess Fusako of Kane", birthYear: "1890", deathYear: "1974" },
|
||||
{ key: 61, parent: 60, name: "Kitashirakawa Nagahisa", gender: "M", fullTitle: "Prince Kitashirakawa Nagahisa", kanjiName: "北白川宮永久王", birthYear: "1910", deathYear: "1940" },
|
||||
{ key: 62, parent: 61, name: "Kitashirakawa Michihisa", gender: "M", fullTitle: "Prince Kitashirakawa Michihisa", birthYear: "1937", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 63, parent: 62, name: "Kitashirakawa Naoko", gender: "F", fullTitle: "No Title", birthYear: "1969" },
|
||||
{ key: 64, parent: 62, name: "Kitashirakawa Nobuko", gender: "F", fullTitle: "No Title", birthYear: "1971" },
|
||||
{ key: 65, parent: 62, name: "Kitashirakawa Akiko", gender: "F", fullTitle: "No Title", birthYear: "1973" },
|
||||
{ key: 66, parent: 61, name: "Hatsuko", gender: "F", fullTitle: "Princess Hatsuko", birthYear: "1939", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 67, parent: 60, name: "Kitashirakawa Mineko", gender: "F", fullTitle: "Princess Kitashirakawa Mineko", kanjiName: "美年子女王", birthYear: "1910", deathYear: "1970", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 68, parent: 60, name: "Kitashirakawa Sawako", gender: "F", fullTitle: "Princess Kitashirakawa Sawako", kanjiName: "佐和子女王", birthYear: "1913", deathYear: "2001", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 69, parent: 60, name: "Kitashirakawa Taeko", gender: "F", fullTitle: "Princess Kitashirakawa Taeko", kanjiName: "多惠子女王", birthYear: "1920", deathYear: "1954", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 70, parent: 1, name: "Nobuko", gender: "F", fullTitle: "Princess Fumi-no-Miya Nobuko", birthYear: "1891", deathYear: "1933" },
|
||||
{ key: 71, parent: 70, name: "Asaka Kikuko", gender: "F", fullTitle: "Princess Asaka Kikuko", kanjiName: "紀久子", birthYear: "1911", deathYear: "1989", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 72, parent: 70, name: "Asaka Takahiko", gender: "M", fullTitle: "Prince Asaka Takahiko", kanjiName: "朝香 孚彦", birthYear: "1913", deathYear: "1994", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 73, parent: 72, name: "Fukuko", gender: "F", fullTitle: "No Title" },
|
||||
{ key: 74, parent: 72, name: "Minoko", gender: "F", fullTitle: "No Title" },
|
||||
{ key: 75, parent: 72, name: "Tomohiko", gender: "M", fullTitle: "No Title" },
|
||||
{ key: 76, parent: 70, name: "Asaka Tadahito", gender: "M", fullTitle: "Prince Asaka Tadahito", kanjiName: "朝香正彦", birthYear: "1914", deathYear: "1944" },
|
||||
{ key: 77, parent: 70, name: "Asaka Kiyoko", gender: "F", fullTitle: "Princess Asaka Kiyoko", kanjiName: "湛子", birthYear: "1919", statusChange: "In 1947, lost imperial family status due to American abrogation of Japanese nobility" },
|
||||
{ key: 78, parent: 1, name: "Ten Other Children Not Surviving Infancy" },
|
||||
{ key: 79, parent: 0, name: "Five Other Children Not Surviving Infancy" }
|
||||
];
|
||||
|
||||
// create the model for the family tree
|
||||
myDiagram.model = new go.TreeModel(nodeDataArray);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 600px"></div>
|
||||
<p>For a variation of this tree, see the <a href="familyTree.html">British family tree sample</a>.</p>
|
||||
<p>For a more complex family tree see the <a href="genogram.html">genogram sample</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Fault Tree</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A Fault Tree diagram showing gate shapes at each non-root node." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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",
|
||||
{
|
||||
allowCopy: false,
|
||||
allowDelete: false,
|
||||
"draggingTool.dragsTree": true,
|
||||
layout:
|
||||
$(go.TreeLayout,
|
||||
{ angle: 90, layerSpacing: 30 }),
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
function nodeFillConverter(figure) {
|
||||
switch (figure) {
|
||||
case "AndGate":
|
||||
// right to left so when it's rotated, it goes from top to bottom
|
||||
return $(go.Brush, "Linear", { 0: "#EA8100", 1: "#C66D00", start: go.Spot.Right, end: go.Spot.Left });
|
||||
case "OrGate":
|
||||
return $(go.Brush, "Linear", { 0: "#0058D3", 1: "#004FB7", start: go.Spot.Right, end: go.Spot.Left });
|
||||
case "Circle":
|
||||
return $(go.Brush, "Linear", { 0: "#009620", 1: "#007717" });
|
||||
case "Triangle":
|
||||
return $(go.Brush, "Linear", { 0: "#7A0099", 1: "#63007F" });
|
||||
default:
|
||||
return "whitesmoke";
|
||||
}
|
||||
}
|
||||
|
||||
myDiagram.nodeTemplate = // the default node template
|
||||
$(go.Node, "Spot",
|
||||
{ selectionObjectName: "BODY", locationSpot: go.Spot.Center, locationObjectName: "BODY" },
|
||||
// the main "BODY" consists of a Rectangle surrounding some text
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "BODY", portId: "" },
|
||||
$(go.Shape,
|
||||
{ fill: $(go.Brush, "Linear", { 0: "#770000", 1: "#600000" }), stroke: null }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
margin: new go.Margin(2, 10, 1, 10), maxSize: new go.Size(100, NaN),
|
||||
stroke: "whitesmoke", font: "10pt Segoe UI, sans-serif"
|
||||
},
|
||||
new go.Binding("text"))
|
||||
), // end "BODY", an Auto Panel
|
||||
$("TreeExpanderButton", { alignment: go.Spot.Right, alignmentFocus: go.Spot.Left, "ButtonBorder.figure": "Rectangle" }),
|
||||
$(go.Shape, "LineV",
|
||||
new go.Binding("visible", "figure", function(f) { return f !== "None"; }),
|
||||
{ strokeWidth: 1.5, height: 20, alignment: new go.Spot(0.5, 1, 0, -1), alignmentFocus: go.Spot.Top }),
|
||||
$(go.Shape,
|
||||
new go.Binding("visible", "figure", function(f) { return f !== "None"; }),
|
||||
{
|
||||
alignment: new go.Spot(0.5, 1, 0, 5), alignmentFocus: go.Spot.Top, width: 30, height: 30,
|
||||
stroke: null
|
||||
},
|
||||
new go.Binding("figure"),
|
||||
new go.Binding("fill", "figure", nodeFillConverter),
|
||||
new go.Binding("angle", "figure", function(f) { return (f === "OrGate" || f === "AndGate") ? -90 : 0; })), // ORs and ANDs should point upwards
|
||||
$(go.TextBlock,
|
||||
new go.Binding("visible", "figure", function(f) { return f !== "None"; }), // if we don't have a figure, don't display any choice text
|
||||
{
|
||||
alignment: new go.Spot(0.5, 1, 20, 20), alignmentFocus: go.Spot.Left,
|
||||
stroke: "black", font: "10pt Segoe UI, sans-serif"
|
||||
},
|
||||
new go.Binding("text", "choice"))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, go.Link.Orthogonal,
|
||||
{ layerName: "Background", curviness: 20, corner: 5 },
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 1.5 })
|
||||
);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>
|
||||
<em>Fault trees</em> are used to conduct deductive failure analysis in which an undesired state of a
|
||||
system is analyzed using Boolean logic to combine a series of lower-level events.
|
||||
</p>
|
||||
<p>
|
||||
This diagram uses a basic <a>TreeModel</a> and <a>TreeLayout</a> to layout nodes in a tree structure.
|
||||
The <a>Diagram.nodeTemplate</a> definition allows for text describing the undesirable states and,
|
||||
when necessary, a figure indicating an event/gate.
|
||||
</p>
|
||||
<p>
|
||||
The <b>visible</b> property on some of the node template's <a>Shape</a>s is set based on
|
||||
whether a figure is chosen for the node in the <a>Model.nodeDataArray</a>. The nodes also
|
||||
display a <b>TreeExpanderButton</b> allowing for expanding/collapsing of subtrees.
|
||||
See the <a href="../intro/buttons.html">Intro page on Buttons</a> for more GoJS button information.
|
||||
</p>
|
||||
<p>
|
||||
Related to deductive failure analysis is root cause analysis, or RCA. See the <a href="../extensions/Fishbone.html">fishbone layout</a>
|
||||
extension page for a diagram format typically used in root cause analysis.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.TreeModel",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "text":"No flow to receiver", "figure":"None"},
|
||||
{"key":2, "text":"No flow from Component B", "parent":1, "figure":"OrGate", "choice":"G02"},
|
||||
{"key":3, "text":"No flow into Component B", "parent":2, "figure":"AndGate", "choice":"G03"},
|
||||
{"key":4, "text":"Component B blocks flow", "parent":2, "figure":"Circle", "choice":"B01"},
|
||||
{"key":5, "text":"No flow from Component A1", "parent":3, "figure":"OrGate", "choice":"G04"},
|
||||
{"key":6, "text":"No flow from Component A2", "parent":3, "figure":"OrGate", "choice":"G05"},
|
||||
{"key":7, "text":"No flow from source1", "parent":5, "figure":"Triangle", "choice":"T01"},
|
||||
{"key":8, "text":"Component A1 blocks flow", "parent":5, "figure":"Circle", "fill":"green", "choice":"B02"},
|
||||
{"key":9, "text":"No flow from source2", "parent":6, "figure":"Triangle", "choice":"T02"},
|
||||
{"key":10, "text":"Component A2 blocks flow", "parent":6, "figure":"Circle", "choice":"B03"}
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,236 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Force Directed Layout</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive demonstration of physics layout features by the ForceDirectedLayout class." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
// define a custom ForceDirectedLayout for this sample
|
||||
function DemoForceDirectedLayout() {
|
||||
go.ForceDirectedLayout.call(this);
|
||||
}
|
||||
go.Diagram.inherit(DemoForceDirectedLayout, go.ForceDirectedLayout);
|
||||
|
||||
// Override the makeNetwork method to also initialize
|
||||
// ForceDirectedVertex.isFixed from the corresponding Node.isSelected.
|
||||
DemoForceDirectedLayout.prototype.makeNetwork = function(coll) {
|
||||
// call base method for standard behavior
|
||||
var net = go.ForceDirectedLayout.prototype.makeNetwork.call(this, coll);
|
||||
net.vertexes.each(function(vertex) {
|
||||
var node = vertex.node;
|
||||
if (node !== null) vertex.isFixed = node.isSelected;
|
||||
});
|
||||
return net;
|
||||
};
|
||||
// end DemoForceDirectedLayout class
|
||||
|
||||
|
||||
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.Uniform, // zoom to make everything fit in the viewport
|
||||
layout: new DemoForceDirectedLayout() // use custom layout
|
||||
// other Layout properties are set by the layout function, defined below
|
||||
});
|
||||
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Spot",
|
||||
// make sure the Node.location is different from the Node.position
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("text", "text"), // for sorting
|
||||
$(go.Shape, "Ellipse",
|
||||
{
|
||||
fill: "lightgray",
|
||||
stroke: null,
|
||||
desiredSize: new go.Size(30, 30)
|
||||
},
|
||||
new go.Binding("fill", "fill")),
|
||||
$(go.TextBlock,
|
||||
new go.Binding("text", "text"))
|
||||
);
|
||||
|
||||
// define the Link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ selectable: false },
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 3, stroke: "#333" }));
|
||||
|
||||
// generate a tree using 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);
|
||||
|
||||
generateTree(minNodes, maxNodes, minChil, maxChil);
|
||||
}
|
||||
|
||||
function generateTree(minNodes, maxNodes, minChil, maxChil) {
|
||||
myDiagram.startTransaction("generateTree");
|
||||
// replace the diagram's model's nodeDataArray
|
||||
generateNodes(minNodes, maxNodes);
|
||||
// replace the diagram's model's linkDataArray
|
||||
generateLinks(minChil, maxChil);
|
||||
// perform a diagram layout with the latest parameters
|
||||
layout();
|
||||
myDiagram.commitTransaction("generateTree");
|
||||
}
|
||||
|
||||
// Creates a random number of randomly colored nodes.
|
||||
function generateNodes(min, max) {
|
||||
var nodeArray = [];
|
||||
if (isNaN(min) || min < 0) min = 0;
|
||||
if (isNaN(max) || max < min) max = min;
|
||||
var numNodes = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
for (var i = 0; i < numNodes; i++) {
|
||||
nodeArray.push({
|
||||
key: i,
|
||||
text: i.toString(),
|
||||
fill: go.Brush.randomColor()
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// set the nodeDataArray to this array of objects
|
||||
myDiagram.model.nodeDataArray = nodeArray;
|
||||
}
|
||||
|
||||
// Takes the random collection of nodes 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)
|
||||
function generateLinks(min, max) {
|
||||
if (myDiagram.nodes.count < 2) return;
|
||||
if (isNaN(min) || min < 1) min = 1;
|
||||
if (isNaN(max) || max < min) max = min;
|
||||
var linkArray = [];
|
||||
// make two Lists of nodes to keep track of where links already exist
|
||||
var nit = myDiagram.nodes;
|
||||
var nodes = new go.List(/*go.Node*/);
|
||||
nodes.addAll(nit);
|
||||
var available = new go.List(/*go.Node*/);
|
||||
available.addAll(nodes);
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var next = nodes.get(i);
|
||||
available.remove(next)
|
||||
var children = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
for (var j = 1; j <= children; j++) {
|
||||
if (available.length === 0) break;
|
||||
var to = available.get(0);
|
||||
available.remove(to);
|
||||
// get keys from the Node.text strings
|
||||
var nextKey = parseInt(next.text, 10);
|
||||
var toKey = parseInt(to.text, 10);
|
||||
linkArray.push({ from: nextKey, to: toKey });
|
||||
}
|
||||
}
|
||||
myDiagram.model.linkDataArray = linkArray;
|
||||
}
|
||||
|
||||
// Update the layout from the controls.
|
||||
// Changing the properties will invalidate the layout.
|
||||
function layout() {
|
||||
myDiagram.startTransaction("changed Layout");
|
||||
var lay = myDiagram.layout;
|
||||
|
||||
var maxIter = document.getElementById("maxIter").value;
|
||||
maxIter = parseInt(maxIter, 10);
|
||||
lay.maxIterations = maxIter;
|
||||
|
||||
var epsilon = document.getElementById("epsilon").value;
|
||||
epsilon = parseFloat(epsilon, 10);
|
||||
lay.epsilonDistance = epsilon;
|
||||
|
||||
var infinity = document.getElementById("infinity").value;
|
||||
infinity = parseFloat(infinity, 10);
|
||||
lay.infinityDistance = infinity;
|
||||
|
||||
var arrangement = document.getElementById("arrangement").value;
|
||||
var arrangementSpacing = new go.Size();
|
||||
arrangement = arrangement.split(" ", 2);
|
||||
arrangementSpacing.width = parseFloat(arrangement[0], 10);
|
||||
arrangementSpacing.height = parseFloat(arrangement[1], 10);
|
||||
lay.arrangementSpacing = arrangementSpacing;
|
||||
|
||||
var charge = document.getElementById("charge").value;
|
||||
charge = parseFloat(charge, 10);
|
||||
lay.defaultElectricalCharge = charge;
|
||||
|
||||
var mass = document.getElementById("mass").value;
|
||||
mass = parseFloat(mass, 10);
|
||||
lay.defaultGravitationalMass = mass;
|
||||
|
||||
var stiffness = document.getElementById("stiffness").value;
|
||||
stiffness = parseFloat(stiffness, 10);
|
||||
lay.defaultSpringStiffness = stiffness;
|
||||
|
||||
var length = document.getElementById("length").value;
|
||||
length = parseFloat(length, 10);
|
||||
lay.defaultSpringLength = length;
|
||||
|
||||
myDiagram.commitTransaction("changed Layout");
|
||||
}
|
||||
</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>New Tree</b><br />
|
||||
MinNodes: <input type="text" size="3" id="minNodes" value="20" /><br />
|
||||
MaxNodes: <input type="text" size="3" id="maxNodes" value="100" /><br />
|
||||
MinChildren: <input type="text" size="3" id="minChil" value="1" /><br />
|
||||
MaxChildren: <input type="text" size="3" id="maxChil" value="10" /><br />
|
||||
<button type="button" onclick="rebuildGraph()">Generate Tree</button>
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: top; padding: 5px">
|
||||
<b>ForceDirectedLayout Properties</b><br />
|
||||
Max Iterations: <input type="text" size="5" id="maxIter" value="100" onchange="layout()" /><br />
|
||||
Epsilon: <input type="text" size="5" id="epsilon" value="1" onchange="layout()" /><br />
|
||||
Infinity: <input type="text" size="5" id="infinity" value="1000" onchange="layout()" /><br />
|
||||
ArrangementSpacing: <input type="text" size="8" id="arrangement" value="100 100" onchange="layout()" /><br />
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: top; padding: 5px">
|
||||
<b>Vertex Properties</b><br />
|
||||
Electrical Charge: <input type="text" size="5" id="charge" value="150" onchange="layout()" /><br />
|
||||
Gravitational Mass: <input type="text" size="5" id="mass" value="0" onchange="layout()" /><br />
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: top; padding: 5px">
|
||||
<b>Edge Properties</b><br />
|
||||
Spring Stiffness: <input type="text" size="5" id="stiffness" value="0.05" onchange="layout()" /><br />
|
||||
Spring Length: <input type="text" size="5" id="length" value="50" onchange="layout()" /><br />
|
||||
</span>
|
||||
</div>
|
||||
<div id="myDiagramDiv" style="background: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
||||
<p>
|
||||
For information on <b>ForceDirectedLayout</b> and its properties, see the <a>ForceDirectedLayout</a> documentation page.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,389 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Flow Builder</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="An editor of flow diagrams that supports deletion by dropping onto a particular node and relinking by dragging a link." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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",
|
||||
{
|
||||
allowCopy: false,
|
||||
layout:
|
||||
$(go.LayeredDigraphLayout,
|
||||
{
|
||||
setsPortSpots: false, // Links already know their fromSpot and toSpot
|
||||
columnSpacing: 5,
|
||||
isInitial: false,
|
||||
isOngoing: false
|
||||
}),
|
||||
validCycle: go.Diagram.CycleNotDirected,
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
var graygrad = $(go.Brush, "Linear",
|
||||
{ 0: "white", 0.1: "whitesmoke", 0.9: "whitesmoke", 1: "lightgray" });
|
||||
|
||||
myDiagram.nodeTemplate = // the default node template
|
||||
$(go.Node, "Spot",
|
||||
{ selectionAdorned: false, textEditable: true, locationObjectName: "BODY" },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// the main body consists of a Rectangle surrounding the text
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "BODY" },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: graygrad, stroke: "gray", minSize: new go.Size(120, 21) },
|
||||
new go.Binding("fill", "isSelected", function(s) { return s ? "dodgerblue" : graygrad; }).ofObject()),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
stroke: "black", font: "12px sans-serif", editable: true,
|
||||
margin: new go.Margin(3, 3 + 11, 3, 3 + 4), alignment: go.Spot.Left
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
),
|
||||
// output port
|
||||
$(go.Panel, "Auto",
|
||||
{ alignment: go.Spot.Right, portId: "from", fromLinkable: true, cursor: "pointer", click: addNodeAndLink },
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 22, height: 22, fill: "white", stroke: "dodgerblue", strokeWidth: 3 }),
|
||||
$(go.Shape, "PlusLine",
|
||||
{ width: 11, height: 11, fill: null, stroke: "dodgerblue", strokeWidth: 3 })
|
||||
),
|
||||
// input port
|
||||
$(go.Panel, "Auto",
|
||||
{ alignment: go.Spot.Left, portId: "to", toLinkable: true },
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 8, height: 8, fill: "white", stroke: "gray" }),
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 4, height: 4, fill: "dodgerblue", stroke: null })
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate.contextMenu =
|
||||
$("ContextMenu",
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Rename"),
|
||||
{ click: function(e, obj) { e.diagram.commandHandler.editTextBlock(); } },
|
||||
new go.Binding("visible", "", function(o) { return o.diagram && o.diagram.commandHandler.canEditTextBlock(); }).ofObject()),
|
||||
// add one for Editing...
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Delete"),
|
||||
{ click: function(e, obj) { e.diagram.commandHandler.deleteSelection(); } },
|
||||
new go.Binding("visible", "", function(o) { return o.diagram && o.diagram.commandHandler.canDeleteSelection(); }).ofObject())
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Loading",
|
||||
$(go.Node, "Spot",
|
||||
{ selectionAdorned: false, textEditable: true, locationObjectName: "BODY" },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// the main body consists of a Rectangle surrounding the text
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "BODY" },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: graygrad, stroke: "gray", minSize: new go.Size(120, 21) },
|
||||
new go.Binding("fill", "isSelected", function(s) { return s ? "dodgerblue" : graygrad; }).ofObject()),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
stroke: "black", font: "12px sans-serif", editable: true,
|
||||
margin: new go.Margin(3, 3 + 11, 3, 3 + 4), alignment: go.Spot.Left
|
||||
},
|
||||
new go.Binding("text", "text"))
|
||||
),
|
||||
// output port
|
||||
$(go.Panel, "Auto",
|
||||
{ alignment: go.Spot.Right, portId: "from", fromLinkable: true, click: addNodeAndLink },
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 22, height: 22, fill: "white", stroke: "dodgerblue", strokeWidth: 3 }),
|
||||
$(go.Shape, "PlusLine",
|
||||
{ width: 11, height: 11, fill: null, stroke: "dodgerblue", strokeWidth: 3 })
|
||||
)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("End",
|
||||
$(go.Node, "Spot",
|
||||
{ selectionAdorned: false, textEditable: true, locationObjectName: "BODY" },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// the main body consists of a Rectangle surrounding the text
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "BODY" },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: graygrad, stroke: "gray", minSize: new go.Size(120, 21) },
|
||||
new go.Binding("fill", "isSelected", function(s) { return s ? "dodgerblue" : graygrad; }).ofObject()),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
stroke: "black", font: "12px sans-serif", editable: true,
|
||||
margin: new go.Margin(3, 3 + 11, 3, 3 + 4), alignment: go.Spot.Left
|
||||
},
|
||||
new go.Binding("text", "text"))
|
||||
),
|
||||
// input port
|
||||
$(go.Panel, "Auto",
|
||||
{ alignment: go.Spot.Left, portId: "to", toLinkable: true },
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 8, height: 8, fill: "white", stroke: "gray" }),
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 4, height: 4, fill: "dodgerblue", stroke: null })
|
||||
)
|
||||
));
|
||||
|
||||
|
||||
// dropping a node on this special node will cause the selection to be deleted;
|
||||
// linking or relinking to this special node will cause the link to be deleted
|
||||
myDiagram.nodeTemplateMap.add("Recycle",
|
||||
$(go.Node, "Auto",
|
||||
{
|
||||
portId: "to", toLinkable: true, deletable: false,
|
||||
layerName: "Background", locationSpot: go.Spot.Center
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{ dragComputation: function(node, pt, gridpt) { return pt; } },
|
||||
{ mouseDrop: function(e, obj) { myDiagram.commandHandler.deleteSelection(); } },
|
||||
$(go.Shape,
|
||||
{ fill: "lightgray", stroke: "gray" }),
|
||||
$(go.TextBlock, "Drop Here\nTo Delete",
|
||||
{ margin: 5, textAlign: "center" })
|
||||
));
|
||||
|
||||
// this is a click event handler that adds a node and a link to the diagram,
|
||||
// connecting with the node on which the click occurred
|
||||
function addNodeAndLink(e, obj) {
|
||||
var fromNode = obj.part;
|
||||
var diagram = fromNode.diagram;
|
||||
diagram.startTransaction("Add State");
|
||||
// get the node data for which the user clicked the button
|
||||
var fromData = fromNode.data;
|
||||
// create a new "State" data object, positioned off to the right of the fromNode
|
||||
var p = fromNode.location.copy();
|
||||
p.x += diagram.toolManager.draggingTool.gridSnapCellSize.width;
|
||||
var toData = {
|
||||
text: "new",
|
||||
loc: go.Point.stringify(p)
|
||||
};
|
||||
// add the new node data to the model
|
||||
var model = diagram.model;
|
||||
model.addNodeData(toData);
|
||||
// create a link data from the old node data to the new node data
|
||||
var linkdata = {
|
||||
from: model.getKeyForNodeData(fromData),
|
||||
to: model.getKeyForNodeData(toData)
|
||||
};
|
||||
// and add the link data to the model
|
||||
model.addLinkData(linkdata);
|
||||
// select the new Node
|
||||
var newnode = diagram.findNodeForData(toData);
|
||||
diagram.select(newnode);
|
||||
// snap the new node to a valid location
|
||||
newnode.location = diagram.toolManager.draggingTool.computeMove(newnode, p);
|
||||
// then account for any overlap
|
||||
shiftNodesToEmptySpaces();
|
||||
diagram.commitTransaction("Add State");
|
||||
}
|
||||
|
||||
// Highlight ports when they are targets for linking or relinking.
|
||||
var OldTarget = null; // remember the last highlit port
|
||||
function highlight(port) {
|
||||
if (OldTarget !== port) {
|
||||
lowlight(); // remove highlight from any old port
|
||||
OldTarget = port;
|
||||
port.scale = 1.3; // highlight by enlarging
|
||||
}
|
||||
}
|
||||
function lowlight() { // remove any highlight
|
||||
if (OldTarget) {
|
||||
OldTarget.scale = 1.0;
|
||||
OldTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Connecting a link with the Recycle node removes the link
|
||||
myDiagram.addDiagramListener("LinkDrawn", function(e) {
|
||||
var link = e.subject;
|
||||
if (link.toNode.category === "Recycle") myDiagram.remove(link);
|
||||
lowlight();
|
||||
});
|
||||
myDiagram.addDiagramListener("LinkRelinked", function(e) {
|
||||
var link = e.subject;
|
||||
if (link.toNode.category === "Recycle") myDiagram.remove(link);
|
||||
lowlight();
|
||||
});
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ selectionAdorned: false, fromPortId: "from", toPortId: "to", relinkableTo: true },
|
||||
$(go.Shape,
|
||||
{ stroke: "gray", strokeWidth: 2 },
|
||||
{
|
||||
mouseEnter: function(e, obj) { obj.strokeWidth = 5; obj.stroke = "dodgerblue"; },
|
||||
mouseLeave: function(e, obj) { obj.strokeWidth = 2; obj.stroke = "gray"; }
|
||||
})
|
||||
);
|
||||
|
||||
function commonLinkingToolInit(tool) {
|
||||
// the temporary link drawn during a link drawing operation (LinkingTool) is thick and blue
|
||||
tool.temporaryLink =
|
||||
$(go.Link, { layerName: "Tool" },
|
||||
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 5 }));
|
||||
|
||||
// change the standard proposed ports feedback from blue rectangles to transparent circles
|
||||
tool.temporaryFromPort.figure = "Circle";
|
||||
tool.temporaryFromPort.stroke = null;
|
||||
tool.temporaryFromPort.strokeWidth = 0;
|
||||
tool.temporaryToPort.figure = "Circle";
|
||||
tool.temporaryToPort.stroke = null;
|
||||
tool.temporaryToPort.strokeWidth = 0;
|
||||
|
||||
// provide customized visual feedback as ports are targeted or not
|
||||
tool.portTargeted = function(realnode, realport, tempnode, tempport, toend) {
|
||||
if (realport === null) { // no valid port nearby
|
||||
lowlight();
|
||||
} else if (toend) {
|
||||
highlight(realport);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var ltool = myDiagram.toolManager.linkingTool;
|
||||
commonLinkingToolInit(ltool);
|
||||
// do not allow links to be drawn starting at the "to" port
|
||||
ltool.direction = go.LinkingTool.ForwardsOnly;
|
||||
|
||||
var rtool = myDiagram.toolManager.relinkingTool;
|
||||
commonLinkingToolInit(rtool);
|
||||
// change the standard relink handle to be a shape that takes the shape of the link
|
||||
rtool.toHandleArchetype =
|
||||
$(go.Shape,
|
||||
{ isPanelMain: true, fill: null, stroke: "dodgerblue", strokeWidth: 5 });
|
||||
|
||||
// use a special DraggingTool to cause the dragging of a Link to start relinking it
|
||||
myDiagram.toolManager.draggingTool = new DragLinkingTool();
|
||||
|
||||
// detect when dropped onto an occupied cell
|
||||
myDiagram.addDiagramListener("SelectionMoved", shiftNodesToEmptySpaces);
|
||||
|
||||
function shiftNodesToEmptySpaces() {
|
||||
myDiagram.selection.each(function(node) {
|
||||
if (!(node instanceof go.Node)) return;
|
||||
// look for Parts overlapping the node
|
||||
while (true) {
|
||||
var exist = myDiagram.findObjectsIn(node.actualBounds,
|
||||
// only consider Parts
|
||||
function(obj) { return obj.part; },
|
||||
// ignore Links and the dropped node itself
|
||||
function(part) { return part instanceof go.Node && part !== node; },
|
||||
// check for any overlap, not complete containment
|
||||
true).first();
|
||||
if (exist === null) break;
|
||||
// try shifting down beyond the existing node to see if there's empty space
|
||||
node.position = new go.Point(node.actualBounds.x, exist.actualBounds.bottom + 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// prevent nodes from being dragged to the left of where the layout placed them
|
||||
myDiagram.addDiagramListener("LayoutCompleted", function(e) {
|
||||
myDiagram.nodes.each(function(node) {
|
||||
if (node.category === "Recycle") return;
|
||||
node.minLocation = new go.Point(node.location.x, -Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
load(); // load initial diagram from the mySavedModel textarea
|
||||
}
|
||||
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
// if any nodes don't have a real location, explicitly do a layout
|
||||
if (myDiagram.nodes.any(function(n) { return !n.location.isReal(); })) layout();
|
||||
}
|
||||
|
||||
function layout() {
|
||||
myDiagram.layoutDiagram(true);
|
||||
}
|
||||
|
||||
|
||||
// Define a custom tool that changes a drag operation on a Link to a relinking operation,
|
||||
// but that operates like a normal DraggingTool otherwise.
|
||||
function DragLinkingTool() {
|
||||
go.DraggingTool.call(this);
|
||||
this.isGridSnapEnabled = true;
|
||||
this.isGridSnapRealtime = false;
|
||||
this.gridSnapCellSize = new go.Size(182, 1);
|
||||
this.gridSnapOrigin = new go.Point(5.5, 0);
|
||||
}
|
||||
go.Diagram.inherit(DragLinkingTool, go.DraggingTool);
|
||||
|
||||
// Handle dragging a link specially -- by starting the RelinkingTool on that Link
|
||||
DragLinkingTool.prototype.doActivate = function() {
|
||||
var diagram = this.diagram;
|
||||
if (diagram === null) return;
|
||||
this.standardMouseSelect();
|
||||
var main = this.currentPart; // this is set by the standardMouseSelect
|
||||
if (main instanceof go.Link) { // maybe start relinking instead of dragging
|
||||
var relinkingtool = diagram.toolManager.relinkingTool;
|
||||
// tell the RelinkingTool to work on this Link, not what is under the mouse
|
||||
relinkingtool.originalLink = main;
|
||||
// start the RelinkingTool
|
||||
diagram.currentTool = relinkingtool;
|
||||
// can activate it right now, because it already has the originalLink to reconnect
|
||||
relinkingtool.doActivate();
|
||||
relinkingtool.doMouseMove();
|
||||
} else {
|
||||
go.DraggingTool.prototype.doActivate.call(this);
|
||||
}
|
||||
};
|
||||
// end DragLinkingTool
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:500px"></div>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
<button onclick="layout()">Do Layout</button>
|
||||
<br />
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"nodeDataArray": [
|
||||
{ "key":1, "text":"Loading Screen", "category":"Loading" },
|
||||
{ "key":2, "text":"Beginning" },
|
||||
{ "key":3, "text":"Segment 1" },
|
||||
{ "key":4, "text":"Segment 2" },
|
||||
{ "key":5, "text":"Segment 3"},
|
||||
{ "key":6, "text":"End Screen", "category":"End" },
|
||||
{ "key":-2, "category": "Recycle" }
|
||||
],
|
||||
"linkDataArray": [
|
||||
{ "from":1, "to":2 },
|
||||
{ "from":2, "to":3 },
|
||||
{ "from":2, "to":5 },
|
||||
{ "from":3, "to":4 },
|
||||
{ "from":4, "to":6 }
|
||||
]
|
||||
}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,381 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Flowchart</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive flowchart diagram implemented by GoJS in JavaScript for HTML." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<link href='https://fonts.googleapis.com/css?family=Lato:300,400,700' rel='stylesheet' type='text/css'>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<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 name or refer to the DIV HTML element
|
||||
{
|
||||
"LinkDrawn": showLinkLabel, // this DiagramEvent listener is defined below
|
||||
"LinkRelinked": showLinkLabel,
|
||||
"undoManager.isEnabled": true // enable undo & redo
|
||||
});
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("SaveButton");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
// helper definitions for node templates
|
||||
|
||||
function nodeStyle() {
|
||||
return [
|
||||
// The Node.location comes from the "loc" property of the node data,
|
||||
// converted by the Point.parse static method.
|
||||
// If the Node.location is changed, it updates the "loc" property of the node data,
|
||||
// converting back using the Point.stringify static method.
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{
|
||||
// the Node.location is at the center of each node
|
||||
locationSpot: go.Spot.Center
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Define a function for creating a "port" that is normally transparent.
|
||||
// The "name" is used as the GraphObject.portId,
|
||||
// the "align" is used to determine where to position the port relative to the body of the node,
|
||||
// the "spot" is used to control how links connect with the port and whether the port
|
||||
// stretches along the side of the node,
|
||||
// and the boolean "output" and "input" arguments control whether the user can draw links from or to the port.
|
||||
function makePort(name, align, spot, output, input) {
|
||||
var horizontal = align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom);
|
||||
// the port is basically just a transparent rectangle that stretches along the side of the node,
|
||||
// and becomes colored when the mouse passes over it
|
||||
return $(go.Shape,
|
||||
{
|
||||
fill: "transparent", // changed to a color in the mouseEnter event handler
|
||||
strokeWidth: 0, // no stroke
|
||||
width: horizontal ? NaN : 8, // if not stretching horizontally, just 8 wide
|
||||
height: !horizontal ? NaN : 8, // if not stretching vertically, just 8 tall
|
||||
alignment: align, // align the port on the main Shape
|
||||
stretch: (horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical),
|
||||
portId: name, // declare this object to be a "port"
|
||||
fromSpot: spot, // declare where links may connect at this port
|
||||
fromLinkable: output, // declare whether the user may draw links from here
|
||||
toSpot: spot, // declare where links may connect at this port
|
||||
toLinkable: input, // declare whether the user may draw links to here
|
||||
cursor: "pointer", // show a different cursor to indicate potential link point
|
||||
mouseEnter: function(e, port) { // the PORT argument will be this Shape
|
||||
if (!e.diagram.isReadOnly) port.fill = "rgba(255,0,255,0.5)";
|
||||
},
|
||||
mouseLeave: function(e, port) {
|
||||
port.fill = "transparent";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function textStyle() {
|
||||
return {
|
||||
font: "bold 11pt Lato, Helvetica, Arial, sans-serif",
|
||||
stroke: "#F8F8F8"
|
||||
}
|
||||
}
|
||||
|
||||
// define the Node templates for regular nodes
|
||||
|
||||
myDiagram.nodeTemplateMap.add("", // the default category
|
||||
$(go.Node, "Table", nodeStyle(),
|
||||
// the main object is a Panel that surrounds a TextBlock with a rectangular Shape
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: "#282c34", stroke: "#00A9C9", strokeWidth: 3.5 },
|
||||
new go.Binding("figure", "figure")),
|
||||
$(go.TextBlock, textStyle(),
|
||||
{
|
||||
margin: 8,
|
||||
maxSize: new go.Size(160, NaN),
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
editable: true
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
),
|
||||
// four named ports, one on each side:
|
||||
makePort("T", go.Spot.Top, go.Spot.TopSide, false, true),
|
||||
makePort("L", go.Spot.Left, go.Spot.LeftSide, true, true),
|
||||
makePort("R", go.Spot.Right, go.Spot.RightSide, true, true),
|
||||
makePort("B", go.Spot.Bottom, go.Spot.BottomSide, true, false)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Conditional",
|
||||
$(go.Node, "Table", nodeStyle(),
|
||||
// the main object is a Panel that surrounds a TextBlock with a rectangular Shape
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, "Diamond",
|
||||
{ fill: "#282c34", stroke: "#00A9C9", strokeWidth: 3.5 },
|
||||
new go.Binding("figure", "figure")),
|
||||
$(go.TextBlock, textStyle(),
|
||||
{
|
||||
margin: 8,
|
||||
maxSize: new go.Size(160, NaN),
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
editable: true
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
),
|
||||
// four named ports, one on each side:
|
||||
makePort("T", go.Spot.Top, go.Spot.Top, false, true),
|
||||
makePort("L", go.Spot.Left, go.Spot.Left, true, true),
|
||||
makePort("R", go.Spot.Right, go.Spot.Right, true, true),
|
||||
makePort("B", go.Spot.Bottom, go.Spot.Bottom, true, false)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Start",
|
||||
$(go.Node, "Table", nodeStyle(),
|
||||
$(go.Panel, "Spot",
|
||||
$(go.Shape, "Circle",
|
||||
{ desiredSize: new go.Size(70, 70), fill: "#282c34", stroke: "#09d3ac", strokeWidth: 3.5 }),
|
||||
$(go.TextBlock, "Start", textStyle(),
|
||||
new go.Binding("text"))
|
||||
),
|
||||
// three named ports, one on each side except the top, all output only:
|
||||
makePort("L", go.Spot.Left, go.Spot.Left, true, false),
|
||||
makePort("R", go.Spot.Right, go.Spot.Right, true, false),
|
||||
makePort("B", go.Spot.Bottom, go.Spot.Bottom, true, false)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("End",
|
||||
$(go.Node, "Table", nodeStyle(),
|
||||
$(go.Panel, "Spot",
|
||||
$(go.Shape, "Circle",
|
||||
{ desiredSize: new go.Size(60, 60), fill: "#282c34", stroke: "#DC3C00", strokeWidth: 3.5 }),
|
||||
$(go.TextBlock, "End", textStyle(),
|
||||
new go.Binding("text"))
|
||||
),
|
||||
// three named ports, one on each side except the bottom, all input only:
|
||||
makePort("T", go.Spot.Top, go.Spot.Top, false, true),
|
||||
makePort("L", go.Spot.Left, go.Spot.Left, false, true),
|
||||
makePort("R", go.Spot.Right, go.Spot.Right, false, true)
|
||||
));
|
||||
|
||||
// taken from ../extensions/Figures.js:
|
||||
go.Shape.defineFigureGenerator("File", function(shape, w, h) {
|
||||
var geo = new go.Geometry();
|
||||
var fig = new go.PathFigure(0, 0, true); // starting point
|
||||
geo.add(fig);
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, .75 * w, 0));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, w, .25 * h));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, w, h));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, 0, h).close());
|
||||
var fig2 = new go.PathFigure(.75 * w, 0, false);
|
||||
geo.add(fig2);
|
||||
// The Fold
|
||||
fig2.add(new go.PathSegment(go.PathSegment.Line, .75 * w, .25 * h));
|
||||
fig2.add(new go.PathSegment(go.PathSegment.Line, w, .25 * h));
|
||||
geo.spot1 = new go.Spot(0, .25);
|
||||
geo.spot2 = go.Spot.BottomRight;
|
||||
return geo;
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Comment",
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
$(go.Shape, "File",
|
||||
{ fill: "#282c34", stroke: "#DEE0A3", strokeWidth: 3 }),
|
||||
$(go.TextBlock, textStyle(),
|
||||
{
|
||||
margin: 8,
|
||||
maxSize: new go.Size(200, NaN),
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
textAlign: "center",
|
||||
editable: true
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
// no ports, because no links are allowed to connect with a comment
|
||||
));
|
||||
|
||||
|
||||
// replace the default Link template in the linkTemplateMap
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{
|
||||
routing: go.Link.AvoidsNodes,
|
||||
curve: go.Link.JumpOver,
|
||||
corner: 5, toShortLength: 4,
|
||||
relinkableFrom: true,
|
||||
relinkableTo: true,
|
||||
reshapable: true,
|
||||
resegmentable: true,
|
||||
// mouse-overs subtly highlight links:
|
||||
mouseEnter: function(e, link) { link.findObject("HIGHLIGHT").stroke = "rgba(30,144,255,0.2)"; },
|
||||
mouseLeave: function(e, link) { link.findObject("HIGHLIGHT").stroke = "transparent"; },
|
||||
selectionAdorned: false
|
||||
},
|
||||
new go.Binding("points").makeTwoWay(),
|
||||
$(go.Shape, // the highlight shape, normally transparent
|
||||
{ isPanelMain: true, strokeWidth: 8, stroke: "transparent", name: "HIGHLIGHT" }),
|
||||
$(go.Shape, // the link path shape
|
||||
{ isPanelMain: true, stroke: "gray", strokeWidth: 2 },
|
||||
new go.Binding("stroke", "isSelected", function(sel) { return sel ? "dodgerblue" : "gray"; }).ofObject()),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: "standard", strokeWidth: 0, fill: "gray" }),
|
||||
$(go.Panel, "Auto", // the link label, normally not visible
|
||||
{ visible: false, name: "LABEL", segmentIndex: 2, segmentFraction: 0.5 },
|
||||
new go.Binding("visible", "visible").makeTwoWay(),
|
||||
$(go.Shape, "RoundedRectangle", // the label shape
|
||||
{ fill: "#F8F8F8", strokeWidth: 0 }),
|
||||
$(go.TextBlock, "Yes", // the label
|
||||
{
|
||||
textAlign: "center",
|
||||
font: "10pt helvetica, arial, sans-serif",
|
||||
stroke: "#333333",
|
||||
editable: true
|
||||
},
|
||||
new go.Binding("text").makeTwoWay())
|
||||
)
|
||||
);
|
||||
|
||||
// Make link labels visible if coming out of a "conditional" node.
|
||||
// This listener is called by the "LinkDrawn" and "LinkRelinked" DiagramEvents.
|
||||
function showLinkLabel(e) {
|
||||
var label = e.subject.findObject("LABEL");
|
||||
if (label !== null) label.visible = (e.subject.fromNode.data.category === "Conditional");
|
||||
}
|
||||
|
||||
// temporary links used by LinkingTool and RelinkingTool are also orthogonal:
|
||||
myDiagram.toolManager.linkingTool.temporaryLink.routing = go.Link.Orthogonal;
|
||||
myDiagram.toolManager.relinkingTool.temporaryLink.routing = go.Link.Orthogonal;
|
||||
|
||||
load(); // load an initial diagram from some JSON text
|
||||
|
||||
// initialize the Palette that is on the left side of the page
|
||||
myPalette =
|
||||
$(go.Palette, "myPaletteDiv", // must name or refer to the DIV HTML element
|
||||
{
|
||||
// Instead of the default animation, use a custom fade-down
|
||||
"animationManager.initialAnimationStyle": go.AnimationManager.None,
|
||||
"InitialAnimationStarting": animateFadeDown, // Instead, animate with this function
|
||||
|
||||
nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram
|
||||
model: new go.GraphLinksModel([ // specify the contents of the Palette
|
||||
{ category: "Start", text: "Start" },
|
||||
{ text: "Step" },
|
||||
{ category: "Conditional", text: "???" },
|
||||
{ category: "End", text: "End" },
|
||||
{ category: "Comment", text: "Comment" }
|
||||
])
|
||||
});
|
||||
|
||||
// This is a re-implementation of the default animation, except it fades in from downwards, instead of upwards.
|
||||
function animateFadeDown(e) {
|
||||
var diagram = e.diagram;
|
||||
var animation = new go.Animation();
|
||||
animation.isViewportUnconstrained = true; // So Diagram positioning rules let the animation start off-screen
|
||||
animation.easing = go.Animation.EaseOutExpo;
|
||||
animation.duration = 900;
|
||||
// Fade "down", in other words, fade in from above
|
||||
animation.add(diagram, 'position', diagram.position.copy().offset(0, 200), diagram.position);
|
||||
animation.add(diagram, 'opacity', 0, 1);
|
||||
animation.start();
|
||||
}
|
||||
|
||||
} // end init
|
||||
|
||||
|
||||
// Show the diagram's model in JSON format that the user may edit
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
}
|
||||
|
||||
// print the diagram by opening a new window holding SVG images of the diagram contents for each page
|
||||
function printDiagram() {
|
||||
var svgWindow = window.open();
|
||||
if (!svgWindow) return; // failure to open a new Window
|
||||
var printSize = new go.Size(700, 960);
|
||||
var bnds = myDiagram.documentBounds;
|
||||
var x = bnds.x;
|
||||
var y = bnds.y;
|
||||
while (y < bnds.bottom) {
|
||||
while (x < bnds.right) {
|
||||
var svg = myDiagram.makeSvg({ scale: 1.0, position: new go.Point(x, y), size: printSize });
|
||||
svgWindow.document.body.appendChild(svg);
|
||||
x += printSize.width;
|
||||
}
|
||||
x = bnds.x;
|
||||
y += printSize.height;
|
||||
}
|
||||
setTimeout(function() { svgWindow.print(); }, 1);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: #282c34;"></div>
|
||||
<div id="myDiagramDiv" style="flex-grow: 1; height: 750px; background-color: #282c34;"></div>
|
||||
</div>
|
||||
<p>
|
||||
The FlowChart sample demonstrates several key features of GoJS,
|
||||
namely <a href="../intro/palette.html">Palette</a>s,
|
||||
<a href="../intro/links.html">Linkable nodes</a>, Drag/Drop behavior,
|
||||
<a href="../intro/textBlocks.html">Text Editing</a>, and the use of
|
||||
<a href="../intro/templateMaps.html">Node Template Maps</a> in Diagrams.
|
||||
</p>
|
||||
<p>
|
||||
Mouse-over a Node to view its ports.
|
||||
Drag from these ports to create new Links.
|
||||
Selecting Links allows you to re-shape and re-link them.
|
||||
Selecting a Node and then clicking its TextBlock will allow
|
||||
you to edit text (except on the Start and End Nodes).
|
||||
</p>
|
||||
<button id="SaveButton" onclick="save()">Save</button>
|
||||
<button onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"linkFromPortIdProperty": "fromPort",
|
||||
"linkToPortIdProperty": "toPort",
|
||||
"nodeDataArray": [
|
||||
{"category":"Comment", "loc":"360 -10", "text":"Kookie Brittle", "key":-13},
|
||||
{"key":-1, "category":"Start", "loc":"175 0", "text":"Start"},
|
||||
{"key":0, "loc":"-5 75", "text":"Preheat oven to 375 F"},
|
||||
{"key":1, "loc":"175 100", "text":"In a bowl, blend: 1 cup margarine, 1.5 teaspoon vanilla, 1 teaspoon salt"},
|
||||
{"key":2, "loc":"175 200", "text":"Gradually beat in 1 cup sugar and 2 cups sifted flour"},
|
||||
{"key":3, "loc":"175 290", "text":"Mix in 6 oz (1 cup) Nestle's Semi-Sweet Chocolate Morsels"},
|
||||
{"key":4, "loc":"175 380", "text":"Press evenly into ungreased 15x10x1 pan"},
|
||||
{"key":5, "loc":"355 85", "text":"Finely chop 1/2 cup of your choice of nuts"},
|
||||
{"key":6, "loc":"175 450", "text":"Sprinkle nuts on top"},
|
||||
{"key":7, "loc":"175 515", "text":"Bake for 25 minutes and let cool"},
|
||||
{"key":8, "loc":"175 585", "text":"Cut into rectangular grid"},
|
||||
{"key":-2, "category":"End", "loc":"175 660", "text":"Enjoy!"}
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":1, "to":2, "fromPort":"B", "toPort":"T"},
|
||||
{"from":2, "to":3, "fromPort":"B", "toPort":"T"},
|
||||
{"from":3, "to":4, "fromPort":"B", "toPort":"T"},
|
||||
{"from":4, "to":6, "fromPort":"B", "toPort":"T"},
|
||||
{"from":6, "to":7, "fromPort":"B", "toPort":"T"},
|
||||
{"from":7, "to":8, "fromPort":"B", "toPort":"T"},
|
||||
{"from":8, "to":-2, "fromPort":"B", "toPort":"T"},
|
||||
{"from":-1, "to":0, "fromPort":"B", "toPort":"T"},
|
||||
{"from":-1, "to":1, "fromPort":"B", "toPort":"T"},
|
||||
{"from":-1, "to":5, "fromPort":"B", "toPort":"T"},
|
||||
{"from":5, "to":4, "fromPort":"B", "toPort":"T"},
|
||||
{"from":0, "to":4, "fromPort":"B", "toPort":"T"}
|
||||
]}
|
||||
</textarea>
|
||||
<button onclick="printDiagram()">Print Diagram Using SVG</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,601 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Flowgrammer</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="An editor for a flowchart-like diagram with a restricted syntax -- add nodes by dropping them onto existing nodes or links." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/ParallelLayout.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script>
|
||||
// two custom figures, for "For Each" loops
|
||||
go.Shape.defineFigureGenerator("ForEach", function(shape, w, h) {
|
||||
var param1 = shape ? shape.parameter1 : NaN; // length of triangular area in direction that it is pointing
|
||||
if (isNaN(param1)) param1 = 10;
|
||||
var d = Math.min(h/2, param1);
|
||||
var geo = new go.Geometry();
|
||||
var fig = new go.PathFigure(w, h-d, true);
|
||||
geo.add(fig);
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, w/2, h));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, 0, h-d));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, 0, 0));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, w, 0).close());
|
||||
geo.spot1 = go.Spot.TopLeft;
|
||||
geo.spot2 = new go.Spot(1, 1, 0, Math.min(-d+2, 0));
|
||||
return geo;
|
||||
});
|
||||
|
||||
go.Shape.defineFigureGenerator("EndForEach", function(shape, w, h) {
|
||||
var param1 = shape ? shape.parameter1 : NaN; // length of triangular area in direction that it is pointing
|
||||
if (isNaN(param1)) param1 = 10;
|
||||
var d = Math.min(h/2, param1);
|
||||
var geo = new go.Geometry();
|
||||
var fig = new go.PathFigure(w, d, true);
|
||||
geo.add(fig);
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, w, h));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, 0, h));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, 0, d));
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, w/2, 0).close());
|
||||
geo.spot1 = new go.Spot(0, 0, 0, Math.min(d, 0));
|
||||
geo.spot2 = go.Spot.BottomRight;
|
||||
return geo;
|
||||
});
|
||||
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
// initialize main Diagram
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
allowMove: false,
|
||||
allowCopy: false,
|
||||
"SelectionDeleting": function(e) { // before a delete happens
|
||||
// handle deletions by excising the node and reconnecting the link where the node had been
|
||||
new go.List(e.diagram.selection).each(function(part) { deletingNode(part); });
|
||||
},
|
||||
layout: $(ParallelLayout, { angle: 90, layerSpacing: 21, nodeSpacing: 30 }),
|
||||
"ExternalObjectsDropped": function(e) { // handle drops from the Palette
|
||||
var newnode = e.diagram.selection.first();
|
||||
if (!newnode) return;
|
||||
if (!(newnode instanceof go.Group) && newnode.linksConnected.count === 0) {
|
||||
// when the selection is dropped but not hooked up to the rest of the graph, delete it
|
||||
e.diagram.removeParts(e.diagram.selection, false);
|
||||
} else {
|
||||
e.diagram.commandHandler.scrollToPart(newnode);
|
||||
}
|
||||
},
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// dragged nodes are translucent so that the user can see highlighting of links and nodes
|
||||
myDiagram.findLayer("Tool").opacity = 0.5;
|
||||
|
||||
// some common styles for most of the node templates
|
||||
function nodeStyle() {
|
||||
return {
|
||||
deletable: false,
|
||||
locationSpot: go.Spot.Center,
|
||||
mouseDragEnter: function(e, node) {
|
||||
var sh = node.findObject("SHAPE");
|
||||
if (sh) sh.fill = "lime";
|
||||
},
|
||||
mouseDragLeave: function(e, node) {
|
||||
var sh = node.findObject("SHAPE");
|
||||
if (sh) sh.fill = "white";
|
||||
},
|
||||
mouseDrop: dropOntoNode
|
||||
};
|
||||
}
|
||||
|
||||
function shapeStyle() {
|
||||
return { name: "SHAPE", fill: "white" };
|
||||
}
|
||||
|
||||
function textStyle() {
|
||||
return [
|
||||
{ name: "TEXTBLOCK", textAlign: "center", editable: true },
|
||||
new go.Binding("text").makeTwoWay()
|
||||
];
|
||||
}
|
||||
|
||||
// define the Node templates
|
||||
myDiagram.nodeTemplate = // regular action steps
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
{ deletable: true }, // override nodeStyle()
|
||||
{ minSize: new go.Size(10, 20) },
|
||||
$(go.Shape, shapeStyle()),
|
||||
$(go.TextBlock, textStyle(),
|
||||
{ margin: 4 })
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Start",
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
{ desiredSize: new go.Size(32, 32) },
|
||||
$(go.Shape, "Circle", shapeStyle()),
|
||||
$(go.TextBlock, textStyle(), "Start")
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("End",
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
{ desiredSize: new go.Size(32, 32) },
|
||||
$(go.Shape, "Circle", shapeStyle()),
|
||||
$(go.TextBlock, textStyle(), "End")
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("For",
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
{ minSize: new go.Size(64, 32) },
|
||||
$(go.Shape, "ForEach", shapeStyle()),
|
||||
$(go.TextBlock, textStyle(), "For Each",
|
||||
{ margin: 4 })
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("EndFor",
|
||||
$(go.Node, nodeStyle(),
|
||||
$(go.Shape, "EndForEach", shapeStyle(),
|
||||
{ desiredSize: new go.Size(4, 4) })
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("While",
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
{ minSize: new go.Size(32, 32) },
|
||||
$(go.Shape, "ForEach", shapeStyle(),
|
||||
{ angle: -90, spot2: new go.Spot(1, 1, -6, 0) }),
|
||||
$(go.TextBlock, textStyle(), "While",
|
||||
{ margin: 4 })
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("EndWhile",
|
||||
$(go.Node, nodeStyle(),
|
||||
$(go.Shape, "Circle", shapeStyle(),
|
||||
{ desiredSize: new go.Size(4, 4) })
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("If",
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
{ minSize: new go.Size(64, 32) },
|
||||
$(go.Shape, "Diamond", shapeStyle()),
|
||||
$(go.TextBlock, textStyle(), "If")
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("EndIf",
|
||||
$(go.Node, nodeStyle(),
|
||||
$(go.Shape, "Diamond", shapeStyle(),
|
||||
{ desiredSize: new go.Size(4, 4) })
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Switch",
|
||||
$(go.Node, "Auto", nodeStyle(),
|
||||
{ minSize: new go.Size(64, 32) },
|
||||
$(go.Shape, "TriangleUp", shapeStyle()),
|
||||
$(go.TextBlock, textStyle(), "Switch")
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Merge",
|
||||
$(go.Node, nodeStyle(),
|
||||
$(go.Shape, "TriangleDown", shapeStyle(),
|
||||
{ desiredSize: new go.Size(4, 4) })
|
||||
));
|
||||
|
||||
function groupColor(cat) {
|
||||
switch (cat) {
|
||||
case "If": return "rgba(255,0,0,0.05)";
|
||||
case "For": return "rgba(0,255,0,0.05)";
|
||||
case "While": return "rgba(0,0,255,0.05)";
|
||||
default: return "rgba(0,0,0,0.05)";
|
||||
}
|
||||
}
|
||||
|
||||
// define the Group template, required but unseen
|
||||
myDiagram.groupTemplate =
|
||||
$(go.Group, "Auto",
|
||||
{
|
||||
locationSpot: go.Spot.Center,
|
||||
avoidableMargin: 10, // extra space on the sides
|
||||
layout: $(ParallelLayout, { angle: 90, layerSpacing: 24, nodeSpacing: 30 }),
|
||||
mouseDragEnter: function(e, group) {
|
||||
var sh = group.findObject("SHAPE");
|
||||
if (sh) { sh.width = Math.max(20, group.actualBounds.width-20); sh.stroke = "lime"; }
|
||||
},
|
||||
mouseDragLeave: function(e, group) {
|
||||
var sh = group.findObject("SHAPE");
|
||||
if (sh) sh.stroke = null;
|
||||
},
|
||||
mouseDrop: dropOntoNode
|
||||
},
|
||||
$(go.Shape, "RoundedRectangle",
|
||||
{ fill: "rgba(0,0,0,0.05)", strokeWidth: 0, spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight },
|
||||
new go.Binding("fill", "cat", groupColor)),
|
||||
$(go.Placeholder),
|
||||
$(go.Shape, "LineH",
|
||||
{
|
||||
name: "SHAPE",
|
||||
height: 0, alignment: go.Spot.Bottom,
|
||||
stroke: null, strokeWidth: 8
|
||||
})
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
selectable: false,
|
||||
deletable: false,
|
||||
routing: go.Link.Orthogonal, corner: 5,
|
||||
toShortLength: 2,
|
||||
// links cannot be deleted
|
||||
// If a node from the Palette is dragged over this node, its outline will turn green
|
||||
mouseDragEnter: function(e, link) { if (!isLoopBack(link)) link.isHighlighted = true; },
|
||||
mouseDragLeave: function(e, link) { link.isHighlighted = false; },
|
||||
// if a node from the Palette is dropped on a link, the link is replaced by links to and from the new node
|
||||
mouseDrop: dropOntoLink
|
||||
},
|
||||
$(go.Shape, { isPanelMain: true, stroke: "transparent", strokeWidth: 8 },
|
||||
new go.Binding("stroke", "isHighlighted", function(h) { return h ? "lime" : "transparent"; }).ofObject()),
|
||||
$(go.Shape, { isPanelMain: true, stroke: "black", strokeWidth: 1.5 }),
|
||||
$(go.Shape, { toArrow: "Standard", strokeWidth: 0 }),
|
||||
// $(go.TextBlock, { segmentIndex: -2, segmentFraction: 0.75, editable: true },
|
||||
// new go.Binding("text").makeTwoWay(),
|
||||
// new go.Binding("background", "text", function(t) { return t ? "white" : null; }))
|
||||
);
|
||||
|
||||
function isLoopBack(link) {
|
||||
if (!link) return false;
|
||||
if (link.fromNode.containingGroup !== link.toNode.containingGroup) return false;
|
||||
var cat = link.fromNode.category;
|
||||
return (cat === "EndFor" || cat === "EndWhile" || cat === "EndIf");
|
||||
}
|
||||
|
||||
// A node dropped onto a Merge node is spliced into a link coming into that node;
|
||||
// otherwise it is spliced into a link that is coming out of that node.
|
||||
function dropOntoNode(e, oldnode) {
|
||||
if (oldnode instanceof go.Group) {
|
||||
var merge = oldnode.layout.mergeNode;
|
||||
if (merge) {
|
||||
var it = merge.findLinksOutOf();
|
||||
while (it.next()) {
|
||||
var link = it.value;
|
||||
if (link.fromNode.containingGroup !== link.toNode.containingGroup) {
|
||||
dropOntoLink(e, link);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (oldnode instanceof go.Node) {
|
||||
var cat = oldnode.category;
|
||||
if (cat === "Merge" || cat === "End" || cat === "EndFor" || cat === "EndWhile" || cat === "EndIf") {
|
||||
var link = oldnode.findLinksInto().first();
|
||||
if (link) dropOntoLink(e, link);
|
||||
} else {
|
||||
var link = oldnode.findLinksOutOf().first();
|
||||
if (link) dropOntoLink(e, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Splice a node into a link.
|
||||
// If the new node is of category "For" or "While" or "If", create a Group and splice it in,
|
||||
// and add the new node to that group, and add any other desired nodes and links to that group.
|
||||
function dropOntoLink(e, oldlink) {
|
||||
if (!(oldlink instanceof go.Link)) return;
|
||||
var diagram = e.diagram;
|
||||
var newnode = diagram.selection.first();
|
||||
if (!(newnode instanceof go.Node)) return;
|
||||
if (!newnode.isTopLevel) return;
|
||||
if (isLoopBack(oldlink)) {
|
||||
// can't add nodes into links going back to the "For" node
|
||||
diagram.remove(newnode);
|
||||
return;
|
||||
}
|
||||
|
||||
var fromnode = oldlink.fromNode;
|
||||
var tonode = oldlink.toNode;
|
||||
if (newnode.category === "") { // add simple step into chain of actions
|
||||
newnode.containingGroup = oldlink.containingGroup;
|
||||
// Reconnect the existing link to the new node
|
||||
oldlink.toNode = newnode;
|
||||
// Then add links from the new node to the old node
|
||||
if (newnode.category === "If") {
|
||||
diagram.model.addLinkData({ from: newnode.key, to: tonode.key });
|
||||
diagram.model.addLinkData({ from: newnode.key, to: tonode.key });
|
||||
} else {
|
||||
diagram.model.addLinkData({ from: newnode.key, to: tonode.key });
|
||||
}
|
||||
} else if (newnode.category === "For" || newnode.category === "While") { // add loop group
|
||||
// add group for loop
|
||||
var groupdata = { isGroup: true, cat: newnode.category };
|
||||
diagram.model.addNodeData(groupdata);
|
||||
var group = diagram.findNodeForData(groupdata);
|
||||
group.containingGroup = oldlink.containingGroup;
|
||||
diagram.select(group);
|
||||
|
||||
newnode.containingGroup = group;
|
||||
|
||||
var enddata = { category: "End" + newnode.category };
|
||||
diagram.model.addNodeData(enddata);
|
||||
var endnode = diagram.findNodeForData(enddata);
|
||||
endnode.containingGroup = group;
|
||||
endnode.location = e.documentPoint;
|
||||
|
||||
diagram.model.addLinkData({ from: newnode.key, to: endnode.key });
|
||||
diagram.model.addLinkData({ from: endnode.key, to: newnode.key });
|
||||
|
||||
// Reconnect the existing link to the new node
|
||||
oldlink.toNode = newnode;
|
||||
// Then add a link from the end node to the old node
|
||||
diagram.model.addLinkData({ from: endnode.key, to: tonode.key });
|
||||
} else if (newnode.category === "If") { // add Conditional group
|
||||
// add group for conditional
|
||||
var groupdata = { isGroup: true, cat: newnode.category };
|
||||
diagram.model.addNodeData(groupdata);
|
||||
var group = diagram.findNodeForData(groupdata);
|
||||
group.containingGroup = oldlink.containingGroup;
|
||||
diagram.select(group);
|
||||
|
||||
newnode.containingGroup = group;
|
||||
|
||||
var enddata = { category: "EndIf" };
|
||||
diagram.model.addNodeData(enddata);
|
||||
var endnode = diagram.findNodeForData(enddata);
|
||||
endnode.containingGroup = group;
|
||||
endnode.location = e.documentPoint;
|
||||
|
||||
var truedata = { from: newnode.key, to: endnode.key, text: "true" };
|
||||
diagram.model.addLinkData(truedata);
|
||||
var truelink = diagram.findLinkForData(truedata);
|
||||
var falsedata = { from: newnode.key, to: endnode.key, text: "false" };
|
||||
diagram.model.addLinkData(falsedata);
|
||||
var falselink = diagram.findLinkForData(falsedata);
|
||||
|
||||
// Reconnect the existing link to the new node
|
||||
oldlink.toNode = newnode;
|
||||
// Then add a link from the new node to the old node
|
||||
diagram.model.addLinkData({ from: endnode.key, to: tonode.key });
|
||||
} else if (newnode.category === "Switch") { // add multi-way Switch group
|
||||
// add group for loop
|
||||
var groupdata = { isGroup: true, cat: newnode.category };
|
||||
diagram.model.addNodeData(groupdata);
|
||||
var group = diagram.findNodeForData(groupdata);
|
||||
group.containingGroup = oldlink.containingGroup;
|
||||
diagram.select(group);
|
||||
|
||||
newnode.containingGroup = group;
|
||||
|
||||
var enddata = { category: "Merge" };
|
||||
diagram.model.addNodeData(enddata);
|
||||
var endnode = diagram.findNodeForData(enddata);
|
||||
endnode.containingGroup = group;
|
||||
endnode.location = e.documentPoint;
|
||||
|
||||
var yesdata = { text: "yes,\ndo it" };
|
||||
diagram.model.addNodeData(yesdata);
|
||||
var yesnode = diagram.findNodeForData(yesdata);
|
||||
yesnode.containingGroup = group;
|
||||
yesnode.location = e.documentPoint;
|
||||
diagram.model.addLinkData({ from: newnode.key, to: yesnode.key, text: "yes" });
|
||||
diagram.model.addLinkData({ from: yesnode.key, to: endnode.key });
|
||||
|
||||
var nodata = { text: "no,\ndon't" };
|
||||
diagram.model.addNodeData(nodata);
|
||||
var nonode = diagram.findNodeForData(nodata);
|
||||
nonode.containingGroup = group;
|
||||
nonode.location = e.documentPoint;
|
||||
diagram.model.addLinkData({ from: newnode.key, to: nonode.key, text: "no" });
|
||||
diagram.model.addLinkData({ from: nonode.key, to: endnode.key });
|
||||
|
||||
var maybedata = { text: "??" };
|
||||
diagram.model.addNodeData(maybedata);
|
||||
var maybenode = diagram.findNodeForData(maybedata);
|
||||
maybenode.containingGroup = group;
|
||||
maybenode.location = e.documentPoint;
|
||||
diagram.model.addLinkData({ from: newnode.key, to: maybenode.key, text: "maybe" });
|
||||
diagram.model.addLinkData({ from: maybenode.key, to: endnode.key });
|
||||
|
||||
// Reconnect the existing link to the new node
|
||||
oldlink.toNode = newnode;
|
||||
// Then add a link from the end node to the old node
|
||||
diagram.model.addLinkData({ from: endnode.key, to: tonode.key });
|
||||
}
|
||||
diagram.layoutDiagram(true);
|
||||
}
|
||||
|
||||
function deletingNode(node) { // excise node from the chain that it is in
|
||||
if (!(node instanceof go.Node)) return;
|
||||
if (node instanceof go.Group) {
|
||||
var externals = node.findExternalLinksConnected();
|
||||
var next = null;
|
||||
externals.each(function(link) {
|
||||
if (link.fromNode.isMemberOf(node)) next = link.toNode;
|
||||
});
|
||||
if (next) {
|
||||
externals.each(function(link) {
|
||||
if (link.toNode.isMemberOf(node)) link.toNode = next;
|
||||
});
|
||||
}
|
||||
} else if (node.category === "") {
|
||||
var next = node.findNodesOutOf().first();
|
||||
if (next) {
|
||||
new go.List(node.findLinksInto()).each(function(link) { link.toNode = next; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize Palette
|
||||
myPalette =
|
||||
$(go.Palette, "myPaletteDiv",
|
||||
{
|
||||
maxSelectionCount: 1,
|
||||
nodeTemplateMap: myDiagram.nodeTemplateMap,
|
||||
model: new go.GraphLinksModel([
|
||||
{ text: "Action" },
|
||||
{ text: "For Each", category: "For" },
|
||||
{ text: "While", category: "While" },
|
||||
{ text: "If", category: "If" },
|
||||
{ text: "Switch", category: "Switch" }
|
||||
])
|
||||
});
|
||||
|
||||
// initialize Overview
|
||||
myOverview =
|
||||
$(go.Overview, "myOverviewDiv",
|
||||
{
|
||||
observed: myDiagram,
|
||||
contentAlignment: go.Spot.Center
|
||||
});
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
// save a model to and load a model from Json text, displayed below the Diagram
|
||||
function save() {
|
||||
var str = myDiagram.model.toJson();
|
||||
document.getElementById("mySavedModel").value = str;
|
||||
}
|
||||
function load() {
|
||||
var str = document.getElementById("mySavedModel").value;
|
||||
myDiagram.model = go.Model.fromJson(str);
|
||||
}
|
||||
function newDiagram() {
|
||||
myDiagram.model = go.GraphObject.make(go.GraphLinksModel,
|
||||
{
|
||||
nodeDataArray:
|
||||
[
|
||||
{"key":1, "text":"S", "category":"Start"},
|
||||
{"key":2, "text":"E", "category":"End"}
|
||||
],
|
||||
linkDataArray:
|
||||
[
|
||||
{"from":1, "to":2}
|
||||
]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Use a Flexbox to make the Palette/Overview/Diagram responsive and size things relatively */
|
||||
#myFlexDiv {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
}
|
||||
#myPODiv {
|
||||
display: flex;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
#myFlexDiv {
|
||||
flex-flow: row;
|
||||
}
|
||||
#myPODiv {
|
||||
width: 105px;
|
||||
height: 100%;
|
||||
margin-right: 3px;
|
||||
flex-flow: column;
|
||||
}
|
||||
#myPaletteDiv {
|
||||
height: 80%;
|
||||
}
|
||||
#myOverviewDiv {
|
||||
margin-top: 3px;
|
||||
flex: 1;
|
||||
}
|
||||
#myDiagramDiv {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
#myFlexDiv {
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
#myPODiv {
|
||||
width: 90%;
|
||||
height: 105px;
|
||||
margin-bottom: 3px;
|
||||
flex-flow: row;
|
||||
}
|
||||
#myPaletteDiv {
|
||||
width: 75%;
|
||||
}
|
||||
#myOverviewDiv {
|
||||
margin-left: 3px;
|
||||
flex: 1;
|
||||
}
|
||||
#myDiagramDiv {
|
||||
width: 90%;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myFlexDiv">
|
||||
<div id="myPODiv">
|
||||
<div id="myPaletteDiv" style="background-color: floralwhite; border: solid 1px black"></div>
|
||||
<div id="myOverviewDiv" style="background-color: whitesmoke; border: solid 1px black"></div>
|
||||
</div>
|
||||
<div id="myDiagramDiv" style="border: solid 1px black"></div>
|
||||
</div>
|
||||
<p>
|
||||
The Flowgrammer sample demonstrates how one can build a flowchart with a constrained syntax.
|
||||
You can drag and drop Nodes onto Links and Nodes in the diagram in order to splice them into the graph.
|
||||
There is visual feedback during the dragging process.
|
||||
Nodes dropped onto the diagram's background are automatically deleted.
|
||||
Edit text by clicking on the text of selected nodes.
|
||||
Deleting an action or step Node excises it from the chain of steps that it is in.
|
||||
The "For", "While", and "If" are not deletable, but you can select and delete the Group holding the
|
||||
whole body of the loop or conditional.
|
||||
The "Start" and "End" nodes and Links are not deletable.
|
||||
</p>
|
||||
<p>
|
||||
The automatic layout of the diagram is accomplished with the <a>ParallelLayout</a> extension.
|
||||
</p>
|
||||
<div id="buttons">
|
||||
<button id="loadModel" onclick="load()">Load</button>
|
||||
<button id="saveModel" onclick="save()">Save</button>
|
||||
<button onclick="newDiagram()">New Diagram</button>
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:200px">
|
||||
{ "class": "GraphLinksModel",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "text":"S", "category":"Start"},
|
||||
{"key":-1, "isGroup":true, "cat":"For"},
|
||||
{"key":2, "text":"For Each", "category":"For", "group":-1},
|
||||
{"key":3, "text":"Action 1", "group":-1},
|
||||
{"key":-2, "isGroup":true, "cat":"If", "group":-1},
|
||||
{"key":4, "text":"If", "category":"If", "group":-2},
|
||||
{"key":5, "text":"Action 2", "group":-2},
|
||||
{"key":6, "text":"Action 3", "group":-2},
|
||||
{"key":-3, "isGroup":true, "cat":"For", "group":-2},
|
||||
{"key":7, "text":"For Each\n(nested)", "category":"For", "group":-3},
|
||||
{"key":8, "text":"Action 4", "group":-3},
|
||||
{"key":9, "text":"", "category":"EndFor", "group":-3},
|
||||
{"key":10, "text":"", "category":"EndIf", "group":-2},
|
||||
{"key":11, "text":"Action 5", "group":-1},
|
||||
{"key":12, "text":"", "category":"EndFor", "group":-1},
|
||||
{"key":13, "text":"E", "category":"End"}
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":1, "to":2},
|
||||
{"from":2, "to":3},
|
||||
{"from":3, "to":4},
|
||||
{"from":4, "to":5, "text":"true"},
|
||||
{"from":4, "to":6, "text":"false"},
|
||||
{"from":6, "to":7},
|
||||
{"from":7, "to":8},
|
||||
{"from":8, "to":9},
|
||||
{"from":5, "to":10},
|
||||
{"from":9, "to":10},
|
||||
{"from":9, "to":7},
|
||||
{"from":10, "to":11},
|
||||
{"from":11, "to":12},
|
||||
{"from":12, "to":2},
|
||||
{"from":12, "to":13}
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,218 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Friend Wheel</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Show the relationships between people using a friend wheel diagram, implemented using circular layout." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 WheelLayout() {
|
||||
go.CircularLayout.call(this);
|
||||
}
|
||||
go.Diagram.inherit(WheelLayout, go.CircularLayout);
|
||||
|
||||
// override makeNetwork to set the diameter of each node and ignore the TextBlock label
|
||||
WheelLayout.prototype.makeNetwork = function(coll) {
|
||||
var net = go.CircularLayout.prototype.makeNetwork.call(this, coll);
|
||||
net.vertexes.each(function(cv) {
|
||||
cv.diameter = 20; // because our desiredSize for nodes is (20, 20)
|
||||
});
|
||||
return net;
|
||||
}
|
||||
|
||||
// override commitNodes to rotate nodes so the text goes away from the center,
|
||||
// and flip text if it would be upside-down
|
||||
WheelLayout.prototype.commitNodes = function() {
|
||||
go.CircularLayout.prototype.commitNodes.call(this);
|
||||
this.network.vertexes.each(function(v) {
|
||||
var node = v.node;
|
||||
if (node === null) return;
|
||||
// get the angle of the node towards the center, and rotate it accordingly
|
||||
var a = v.actualAngle;
|
||||
if (a > 90 && a < 270) { // make sure the text isn't upside down
|
||||
var textBlock = node.findObject("TEXTBLOCK");
|
||||
textBlock.angle = 180;
|
||||
}
|
||||
node.angle = a;
|
||||
});
|
||||
};
|
||||
|
||||
// override commitLinks in order to make sure all of the Bezier links are "inside" the ellipse;
|
||||
// this helps avoid links crossing over any other nodes
|
||||
WheelLayout.prototype.commitLinks = function() {
|
||||
go.CircularLayout.prototype.commitLinks.call(this);
|
||||
if (this.network.vertexes.count > 4) {
|
||||
this.network.vertexes.each(function(v) {
|
||||
v.destinationEdges.each(function(de) {
|
||||
var dv = de.toVertex;
|
||||
var da = dv.actualAngle;
|
||||
var sa = v.actualAngle;
|
||||
if (da - sa > 180) da -= 360;
|
||||
else if (sa - da > 180) sa -= 360;
|
||||
de.link.curviness = (sa > da) ? 15 : -15;
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
// end WheelLayout class
|
||||
|
||||
|
||||
var highlightColor = "red"; // color parameterization
|
||||
|
||||
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.Uniform,
|
||||
padding: 10,
|
||||
contentAlignment: go.Spot.Center,
|
||||
layout:
|
||||
$(WheelLayout, // set up a custom CircularLayout
|
||||
// set some properties appropriate for this sample
|
||||
{
|
||||
arrangement: go.CircularLayout.ConstantDistance,
|
||||
nodeDiameterFormula: go.CircularLayout.Circular,
|
||||
spacing: 10,
|
||||
aspectRatio: 0.7,
|
||||
sorting: go.CircularLayout.Optimized
|
||||
}),
|
||||
isReadOnly: true,
|
||||
click: function(e) { // background click clears any remaining highlighteds
|
||||
e.diagram.startTransaction("clear");
|
||||
e.diagram.clearHighlighteds();
|
||||
e.diagram.commitTransaction("clear");
|
||||
}
|
||||
});
|
||||
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Horizontal",
|
||||
{
|
||||
selectionAdorned: false,
|
||||
locationSpot: go.Spot.Center, // Node.location is the center of the Shape
|
||||
locationObjectName: "SHAPE",
|
||||
mouseEnter: function(e, node) {
|
||||
node.diagram.clearHighlighteds();
|
||||
node.linksConnected.each(function(l) { highlightLink(l, true); });
|
||||
node.isHighlighted = true;
|
||||
var tb = node.findObject("TEXTBLOCK");
|
||||
if (tb !== null) tb.stroke = highlightColor;
|
||||
},
|
||||
mouseLeave: function(e, node) {
|
||||
node.diagram.clearHighlighteds();
|
||||
var tb = node.findObject("TEXTBLOCK");
|
||||
if (tb !== null) tb.stroke = "black";
|
||||
}
|
||||
},
|
||||
new go.Binding("text", "text"), // for sorting the nodes
|
||||
$(go.Shape, "Ellipse",
|
||||
{
|
||||
name: "SHAPE",
|
||||
fill: "lightgray", // default value, but also data-bound
|
||||
stroke: "transparent", // modified by highlighting
|
||||
strokeWidth: 2,
|
||||
desiredSize: new go.Size(20, 20),
|
||||
portId: ""
|
||||
}, // so links will go to the shape, not the whole node
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "isHighlighted",
|
||||
function(h) { return h ? highlightColor : "transparent"; })
|
||||
.ofObject()),
|
||||
$(go.TextBlock,
|
||||
{ name: "TEXTBLOCK" }, // for search
|
||||
new go.Binding("text", "text"))
|
||||
);
|
||||
|
||||
function highlightLink(link, show) {
|
||||
link.isHighlighted = show;
|
||||
link.fromNode.isHighlighted = show;
|
||||
link.toNode.isHighlighted = show;
|
||||
}
|
||||
|
||||
// define the Link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.Normal,
|
||||
curve: go.Link.Bezier,
|
||||
selectionAdorned: false,
|
||||
mouseEnter: function(e, link) { highlightLink(link, true); },
|
||||
mouseLeave: function(e, link) { highlightLink(link, false); }
|
||||
},
|
||||
$(go.Shape,
|
||||
new go.Binding("stroke", "isHighlighted",
|
||||
function(h, shape) { return h ? highlightColor : shape.part.data.color; })
|
||||
.ofObject(),
|
||||
new go.Binding("strokeWidth", "isHighlighted",
|
||||
function(h) { return h ? 2 : 1; })
|
||||
.ofObject())
|
||||
// no arrowhead -- assume directionality of relationship need not be shown
|
||||
);
|
||||
|
||||
generateGraph();
|
||||
}
|
||||
|
||||
function generateGraph() {
|
||||
var names = [
|
||||
"Joshua", "Daniel", "Robert", "Noah", "Anthony",
|
||||
"Elizabeth", "Addison", "Alexis", "Ella", "Samantha",
|
||||
"Joseph", "Scott", "James", "Ryan", "Benjamin",
|
||||
"Walter", "Gabriel", "Christian", "Nathan", "Simon",
|
||||
"Isabella", "Emma", "Olivia", "Sophia", "Ava",
|
||||
"Emily", "Madison", "Tina", "Elena", "Mia",
|
||||
"Jacob", "Ethan", "Michael", "Alexander", "William",
|
||||
"Natalie", "Grace", "Lily", "Alyssa", "Ashley",
|
||||
"Sarah", "Taylor", "Hannah", "Brianna", "Hailey",
|
||||
"Christopher", "Aiden", "Matthew", "David", "Andrew",
|
||||
"Kaylee", "Juliana", "Leah", "Anna", "Allison",
|
||||
"John", "Samuel", "Tyler", "Dylan", "Jonathan",
|
||||
];
|
||||
|
||||
var nodeDataArray = [];
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
nodeDataArray.push({ key: i, text: names[i], color: go.Brush.randomColor(128, 240) });
|
||||
}
|
||||
|
||||
var linkDataArray = [];
|
||||
var num = nodeDataArray.length;
|
||||
for (var i = 0; i < num * 2; i++) {
|
||||
var a = Math.floor(Math.random() * num);
|
||||
var b = Math.floor(Math.random() * num / 4) + 1;
|
||||
linkDataArray.push({ from: a, to: (a + b) % num, color: go.Brush.randomColor(0, 127) });
|
||||
}
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; background: white; width: 100%; height: 600px" ></div>
|
||||
<p>
|
||||
This "friend wheel" demonstrates the use of <a>CircularLayout</a>.
|
||||
The layout has been customized to make sure each node is considered to have a fixed diameter,
|
||||
ignoring the size of any <a>TextBlock</a>.
|
||||
</p>
|
||||
<p>
|
||||
The custom layout also rotates each <a>Node</a> according to the actual angle at which the node was positioned.
|
||||
This information is available on the <a>CircularVertex</a> used by the <a>LayoutNetwork</a> that
|
||||
the <a>CircularLayout</a> constructs from the nodes and links of the diagram.
|
||||
Furthermore, when laying out the nodes it also flips the angle of the <a>TextBlock</a> so that the
|
||||
text is not upside-down.
|
||||
</p>
|
||||
<p>
|
||||
<a>GraphObject.mouseEnter</a> and <a>GraphObject.mouseLeave</a> event handlers on the <a>Node</a> template
|
||||
highlight both the Node and all of the Links that connect with the Node.
|
||||
The same event handlers on the <a>Link</a>s highlight that Link and both connected Nodes.
|
||||
Changes made in these event handlers automatically are not recorded in the <a>UndoManager</a>,
|
||||
although this sample does not enable the UndoManager anyway.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,165 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Grid Layout</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Interactive demonstration of layout-on-a-grid features by the GridLayout class." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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
|
||||
{
|
||||
layout: $(go.GridLayout,
|
||||
{ comparer: go.GridLayout.smartComparer })
|
||||
// other properties are set by the layout function, defined below
|
||||
});
|
||||
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Spot",
|
||||
// make sure the Node.location is different from the Node.position
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("text", "text"), // for sorting
|
||||
$(go.Shape, "Ellipse",
|
||||
{
|
||||
fill: "lightgray",
|
||||
stroke: null,
|
||||
desiredSize: new go.Size(30, 30)
|
||||
},
|
||||
new go.Binding("fill", "fill"),
|
||||
new go.Binding("desiredSize", "size")),
|
||||
$(go.TextBlock,
|
||||
// the default alignment is go.Spot.Center
|
||||
new go.Binding("text", "text"))
|
||||
);
|
||||
|
||||
// create an array of data describing randomly colored and sized nodes
|
||||
var nodeDataArray = [];
|
||||
for (var i = 0; i < 100; i++) {
|
||||
nodeDataArray.push({
|
||||
key: i,
|
||||
text: i.toString(),
|
||||
fill: go.Brush.randomColor(),
|
||||
size: new go.Size(30 + Math.floor(Math.random() * 50), 30 + Math.floor(Math.random() * 50))
|
||||
});
|
||||
}
|
||||
|
||||
// randomize the data
|
||||
for (i = 0; i < nodeDataArray.length; i++) {
|
||||
var swap = Math.floor(Math.random() * nodeDataArray.length);
|
||||
var temp = nodeDataArray[swap];
|
||||
nodeDataArray[swap] = nodeDataArray[i];
|
||||
nodeDataArray[i] = temp;
|
||||
}
|
||||
|
||||
// create a Model that does not know about link or group relationships
|
||||
myDiagram.model = new go.Model(nodeDataArray);
|
||||
|
||||
// layout using the latest parameters
|
||||
layout();
|
||||
}
|
||||
|
||||
// Update the layout from the controls, and then perform the layout again
|
||||
function layout() {
|
||||
myDiagram.startTransaction("change Layout");
|
||||
var lay = myDiagram.layout;
|
||||
|
||||
var wrappingColumn = document.getElementById("wrappingColumn").value;
|
||||
lay.wrappingColumn = parseFloat(wrappingColumn, 10);
|
||||
|
||||
var wrappingWidth = document.getElementById("wrappingWidth").value;
|
||||
lay.wrappingWidth = parseFloat(wrappingWidth, 10);
|
||||
|
||||
var cellSize = document.getElementById("cellSize").value;
|
||||
lay.cellSize = go.Size.parse(cellSize);
|
||||
|
||||
var spacing = document.getElementById("spacing").value;
|
||||
lay.spacing = go.Size.parse(spacing);
|
||||
|
||||
var alignment = getRadioValue("alignment");
|
||||
if (alignment === "Position") {
|
||||
lay.alignment = go.GridLayout.Position;
|
||||
} else {
|
||||
lay.alignment = go.GridLayout.Location;
|
||||
}
|
||||
|
||||
var arrangement = getRadioValue("arrangement");
|
||||
if (arrangement === "LeftToRight") {
|
||||
lay.arrangement = go.GridLayout.LeftToRight;
|
||||
} else {
|
||||
lay.arrangement = go.GridLayout.RightToLeft;
|
||||
}
|
||||
|
||||
var sorting = document.getElementById("sorting").value;
|
||||
switch (sorting) {
|
||||
default:
|
||||
case "Forward": lay.sorting = go.GridLayout.Forward; break;
|
||||
case "Reverse": lay.sorting = go.GridLayout.Reverse; break;
|
||||
case "Ascending": lay.sorting = go.GridLayout.Ascending; break;
|
||||
case "Descending": lay.sorting = go.GridLayout.Descending; break;
|
||||
}
|
||||
|
||||
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>GridLayout Properties</b>
|
||||
<br />
|
||||
Wrapping Column:
|
||||
<input type="text" size="3" id="wrappingColumn" value="NaN" onchange="layout()" />
|
||||
(NaN means there's no limit)
|
||||
<br />
|
||||
Wrapping Width:
|
||||
<input type="text" size="3" id="wrappingWidth" value="NaN" onchange="layout()" />
|
||||
(NaN means use the diagram's viewport width)
|
||||
<br />
|
||||
Cell Size:
|
||||
<input type="text" size="8" id="cellSize" value="NaN NaN" onchange="layout()" />
|
||||
(NaN x NaN means use a cell size big enough to hold any node)
|
||||
<br />
|
||||
Spacing:
|
||||
<input type="text" size="8" id="spacing" value="10 10" onchange="layout()" />
|
||||
(the minimum space between the nodes)
|
||||
<br />
|
||||
Alignment:
|
||||
<input type="radio" name="alignment" onclick="layout()" value="Position" /> Position
|
||||
<input type="radio" name="alignment" onclick="layout()" value="Location" checked="checked" /> Location
|
||||
<br />
|
||||
Arrangement:
|
||||
<input type="radio" name="arrangement" onclick="layout()" value="LeftToRight" checked="checked" /> LeftToRight
|
||||
<input type="radio" name="arrangement" onclick="layout()" value="RightToLeft" /> RightToLeft
|
||||
<br />
|
||||
Sorting:
|
||||
<select name="sorting" id="sorting" onchange="layout()">
|
||||
<option value="Forward" selected="selected">Forward</option>
|
||||
<option value="Reverse">Reverse</option>
|
||||
<option value="Ascending">Ascending</option>
|
||||
<option value="Descending">Descending</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
||||
<p>
|
||||
For information on <b>GridLayout</b> and its properties, see the <a>GridLayout</a> documentation page.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,406 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Conway's Game of Life</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A cellular automation simulation in GoJS">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
// two dimensional array which will represent the board state
|
||||
// this will be filled with Nodes once they are created
|
||||
var goLGrid = [];
|
||||
|
||||
// the size of the board
|
||||
var rows = 40;
|
||||
var cols = 40;
|
||||
|
||||
var interval = 15; // the interval between steps in ms when the simulation is enabled
|
||||
var initializing = true;
|
||||
var enabled = false; // flag to turn the simulation on or off
|
||||
|
||||
var myDiagram;
|
||||
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"animationManager.isEnabled": false,
|
||||
// disable movement/edit controls, since the myDiagram is a static grid
|
||||
"panningTool.isEnabled": false,
|
||||
isReadOnly: true,
|
||||
allowZoom: false,
|
||||
allowSelect: false,
|
||||
hasHorizontalScrollbar: false,
|
||||
hasVerticalScrollbar: false,
|
||||
initialAutoScale: go.Diagram.Uniform
|
||||
});
|
||||
|
||||
var nodeSize = 25;
|
||||
var nodeDataArray = []; // array to hold Node data for the model
|
||||
// populate data array by initializing Nodes in a grid and setting their "isAlive" state to false
|
||||
for (var i = 0; i < rows; i++) {
|
||||
var row = new Array(cols);
|
||||
for (var j = 0; j < cols; j++) {
|
||||
nodeDataArray.push({ location: new go.Point(j * nodeSize, i * nodeSize), row: i, col: j, isAlive: false });
|
||||
}
|
||||
goLGrid.push(row);
|
||||
}
|
||||
|
||||
// stroke color and width of the grid lines
|
||||
var gridStroke = "#A2A2A2";
|
||||
var gridStrokeWidth = 1;
|
||||
|
||||
// define the node template, which also includes interactive functionality, such as clicking to toggle a node's state
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Part, {
|
||||
isLayoutPositioned: false,
|
||||
mouseEnter: function(e, part, prev) { // set mouseover border
|
||||
if (!initializing && !enabled) {
|
||||
var shape = part.elt(0);
|
||||
if (shape) {
|
||||
shape.stroke = "steelblue";
|
||||
shape.strokeWidth = 3;
|
||||
}
|
||||
part.zOrder = 2; // ensure that the selection border is in front of all other Nodes by increasing its zOrder
|
||||
|
||||
// drag with a button down to add or erase cells
|
||||
if ((e.buttons === 1 && !part.data.isAlive) || (e.buttons === 2 && part.data.isAlive)) {
|
||||
select(part);
|
||||
}
|
||||
}
|
||||
},
|
||||
mouseLeave: function(e, part, next) { // restore to original borders
|
||||
var shape = part.elt(0);
|
||||
if (shape) {
|
||||
shape.stroke = gridStroke;
|
||||
shape.strokeWidth = gridStrokeWidth
|
||||
}
|
||||
part.zOrder = 1;
|
||||
},
|
||||
click: function(e, part) { // left click to toggle cell
|
||||
initializing = false;
|
||||
if (!enabled) {
|
||||
select(part);
|
||||
}
|
||||
},
|
||||
contextClick: function(e, part) { // right click to clear cell
|
||||
initializing = false;
|
||||
if (!enabled && part.data.isAlive) {
|
||||
select(part);
|
||||
}
|
||||
},
|
||||
zOrder: 1
|
||||
},
|
||||
$(go.Shape,
|
||||
{
|
||||
figure: "Rectangle",
|
||||
fill: "white",
|
||||
stroke: gridStroke,
|
||||
strokeWidth: gridStrokeWidth,
|
||||
width: nodeSize,
|
||||
height: nodeSize
|
||||
}),
|
||||
new go.Binding("location")
|
||||
);
|
||||
|
||||
// use a simple model for our node data
|
||||
myDiagram.model = new go.Model(nodeDataArray);
|
||||
|
||||
// myDiagram.parts is populated after a model is assigned;
|
||||
// populate the internal gamestate array with the newly created nodes
|
||||
for (var it = myDiagram.parts.iterator; it.next(); ) {
|
||||
var part = it.value;
|
||||
goLGrid[part.data.row][part.data.col] = part;
|
||||
}
|
||||
|
||||
// load the default sample
|
||||
var e = document.getElementById("samplePatterns");
|
||||
loadSample(e.options[e.selectedIndex].value);
|
||||
}
|
||||
|
||||
// toggles a given node's state, both visually and in the internal gamestate
|
||||
function select(part) {
|
||||
var shape = part.elt(0);
|
||||
if (shape) {
|
||||
if (shape.fill === "white") {
|
||||
shape.fill = "steelblue";
|
||||
} else {
|
||||
shape.fill = "white";
|
||||
}
|
||||
};
|
||||
part.data.isAlive = !part.data.isAlive;
|
||||
}
|
||||
|
||||
// toggles the state of the simulation, changing the button text from "Start" to "Pause" or back again
|
||||
function toggleSimulation() {
|
||||
initializing = false;
|
||||
var button = document.getElementById("start");
|
||||
if (!enabled) {
|
||||
button.value = "Pause";
|
||||
enabled = true;
|
||||
goLStep();
|
||||
} else {
|
||||
button.value = "Start";
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// the callback for the step button, only steps forward if the simulation is currently stopped
|
||||
function stepOnclick() {
|
||||
initializing = false;
|
||||
if (!enabled) {
|
||||
goLStep(true);
|
||||
}
|
||||
}
|
||||
|
||||
// performs a single step in the Game of Life
|
||||
function goLStep(isManualStep) {
|
||||
if (goLGrid.length === 0) {
|
||||
return; // don't do anything if things aren't initialized yet
|
||||
}
|
||||
|
||||
var isAlive = false;
|
||||
var toSelect = [];
|
||||
var liveCellCount = 0; // count the number of live cells to determine if there are no more left
|
||||
for (var i = 0; i < rows; i++) {
|
||||
for (var j = 0; j < cols; j++) {
|
||||
if (isAlive) {
|
||||
liveCellCount++;
|
||||
}
|
||||
|
||||
// count the number of cells in the 8 squares adjacent to this one
|
||||
var total = 0;
|
||||
|
||||
var above = goLGrid[i > 0 ? i - 1 : rows - 1];
|
||||
var below = goLGrid[i + 1 < rows ? i + 1 : 0];
|
||||
var left = j > 0 ? j - 1 : cols - 1;
|
||||
var right = j + 1 < cols ? j + 1 : 0;
|
||||
|
||||
total += above[left].data.isAlive;
|
||||
total += above[j].data.isAlive;
|
||||
total += above[right].data.isAlive;
|
||||
total += goLGrid[i][left].data.isAlive;
|
||||
total += goLGrid[i][right].data.isAlive;
|
||||
total += below[left].data.isAlive;
|
||||
total += below[j].data.isAlive;
|
||||
total += below[right].data.isAlive;
|
||||
|
||||
// toggle the cell if necessary according to the three rules
|
||||
var part = goLGrid[i][j];
|
||||
isAlive = part.data.isAlive;
|
||||
if ((total <= 1 && isAlive)
|
||||
|| (total > 3 && isAlive)
|
||||
|| (total === 3 && !isAlive)) {
|
||||
if (!isAlive) {
|
||||
liveCellCount++;
|
||||
} else {
|
||||
liveCellCount--;
|
||||
}
|
||||
toSelect.push(part); // don't actually toggle the cell yet, this happens all at once after everything is done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// change the board state according the earlier loop
|
||||
if (enabled || isManualStep) {
|
||||
for (var i = 0; i < toSelect.length; i++) {
|
||||
select(toSelect[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (liveCellCount === 0) {
|
||||
toggleSimulation(); // stop the simulation if there are no more live cells
|
||||
} else {
|
||||
setTimeout(goLStep, interval); // queue another step if the simuation is still enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear the board of all live cells, stopping the simulation if it's currently enabled
|
||||
function goLClear() {
|
||||
if (enabled) {
|
||||
toggleSimulation();
|
||||
}
|
||||
for (var i = 0; i < rows; i++) {
|
||||
for (var j = 0; j < cols; j++) {
|
||||
var part = goLGrid[i][j];
|
||||
if (part.data.isAlive) {
|
||||
select(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this function contains all of the data for the four included sample patterns as well as the logic for drawing them
|
||||
function loadSample(value) {
|
||||
goLClear(); // clear the board first, stopping the simulation if it's enabled
|
||||
// select the correct sample data based on the option value passed to the function
|
||||
var sampleData = [];
|
||||
switch (value) {
|
||||
case "symm4":
|
||||
sampleData = [
|
||||
[ , , , , , , , , , , 1, 1, 1, , , , , , , , 0],
|
||||
[ , , , , , , , , , , 1, , , 1, , , , , , , 0],
|
||||
[ , , , , , , , , , , 1, , , 1, , , , , , , 0],
|
||||
[ , , , , , , , , , , , , 1, 1, , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , 1, 1, 1, , , , , , , , , , , , , , , , , 0],
|
||||
[1, , , 1, , , , , , , , , , , , , , , , , 0],
|
||||
[1, , , , , , , , , , , , , , , , , , , , 0],
|
||||
[1, 1, 1, , , , , , , , , , , , , , , , 1, 1, 1],
|
||||
[ , , , , , , , , , , , , , , , , , , , , 1],
|
||||
[ , , , , , , , , , , , , , , , , , 1, , , 1],
|
||||
[ , , , , , , , , , , , , , , , , , 1, 1, 1, 0],
|
||||
[ , , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , 1, 1, , , , , , , , , , , , 0],
|
||||
[ , , , , , , , 1, , , 1, , , , , , , , , , 0],
|
||||
[ , , , , , , , 1, , , 1, , , , , , , , , , 0],
|
||||
[ , , , , , , , , 1, 1, 1, , , , , , , , , , 0]
|
||||
];
|
||||
break;
|
||||
case "pulsar":
|
||||
sampleData = [
|
||||
[ , 1, 1, 1, , , , 1, 1, 1, 0],
|
||||
[1, , , , 1, , 1, , , , 1],
|
||||
[1, , , , 1, , 1, , , , 1],
|
||||
[1, , , , 1, , 1, , , , 1],
|
||||
[ , 1, 1, 1, , , , 1, 1, 1, 0],
|
||||
[ , , , , , , , , , , 0],
|
||||
[ , 1, 1, 1, , , , 1, 1, 1, 0],
|
||||
[1, , , , 1, , 1, , , , 1],
|
||||
[1, , , , 1, , 1, , , , 1],
|
||||
[1, , , , 1, , 1, , , , 1],
|
||||
[ , 1, 1, 1, , , , 1, 1, 1, 0]
|
||||
]
|
||||
break;
|
||||
case "spaceships":
|
||||
sampleData = [
|
||||
[1, , , 1, , , , , , , , , , , , , 0],
|
||||
[ , , , , 1, , , , , , , , , , , , 0],
|
||||
[1, , , , 1, , , , , , , , , , , , 0],
|
||||
[ , 1, 1, 1, 1, , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , 1, , 0],
|
||||
[ , , , , , , , , , , , , 1, , , , 1],
|
||||
[ , , , , , , , , , , , 1, , , , , 0],
|
||||
[ , , , , , , , , , , , 1, , , , , 1],
|
||||
[ , , , , , , , , , , , 1, 1, 1, 1, 1, 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , 1, , 1, 1, , , , , , , , 0],
|
||||
[ , , , , 1, , , , , , , 1, , , , , 0],
|
||||
[ , , , 1, 1, , , , 1, , , 1, , , , , 0],
|
||||
[1, 1, , 1, , , , , , 1, 1, , , , , , 0],
|
||||
[1, 1, , 1, , , , , , 1, 1, , , , , , 0],
|
||||
[ , , , 1, 1, , , , 1, , , 1, , , , , 0],
|
||||
[ , , , , 1, , , , , , , 1, , , , , 0],
|
||||
[ , , , , , 1, , 1, 1, , , , , , , , 0]
|
||||
]
|
||||
break;
|
||||
case "bigGliders":
|
||||
sampleData = [
|
||||
[ , , , , , , , , , , , , , 1, 1, 1, , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , 1, , , 1, 1, 1, , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , 1, , 1, , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , 1, 1, , , , , , , , 1, , , , , , , , 0],
|
||||
[ , , , , , , , , , , 1, , 1, , , , , 1, , , 1, , , , , , , 0],
|
||||
[ , , , , , , , , , , 1, , , , , , , , , 1, 1, , , , , , , 0],
|
||||
[ , , , , , , , , , , , 1, 1, , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , 1, , , 1, , , , , , 1, , 1, 1, , , , 0],
|
||||
[ , , , , , , , , , , , 1, , , , , , , , , , 1, 1, , 1, , , 0],
|
||||
[ , , , , , , , , , , , , , 1, , 1, , , , , , , 1, 1, , , 1, 0],
|
||||
[ , , , , , , , , , , , , , , 1, 1, , 1, , , , , 1, 1, , , , 1],
|
||||
[ , , , , , , , , , , , , , , , , , , 1, , , , , , , , 1, 0],
|
||||
[ , , , , , , , , , , , , , , , , , 1, 1, 1, 1, , , , 1, , 1, 0],
|
||||
[ , , , , , , , , , , , , , , , , , 1, , 1, 1, , , , 1, 1, 1, 1],
|
||||
[ , , , , , , , , , , , , , , , , , , 1, , , , 1, 1, , 1, , 0],
|
||||
[ , , , , , , , , 1, 1, , , , , , , , , , , , , , 1, 1, , , 0],
|
||||
[ , , , , , , , 1, 1, , , , , , , , , , , 1, , 1, 1, 1, , , , 0],
|
||||
[ , , , , , , , , , 1, , , , , , , , , , , 1, , , 1, , , , 0],
|
||||
[ , , , , , , , , , , , 1, 1, , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , 1, , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , , , , , , 1, , , 1, , , , , , , , , , , , , , , 0],
|
||||
[ , 1, 1, , , , , , 1, 1, , , , , , , , , , , , , , , , , , 0],
|
||||
[1, 1, , , , , , 1, , , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , 1, , , , , 1, , 1, , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , 1, 1, , , 1, , , , , , , , , , , , , , , , , , , 0],
|
||||
[ , , , , 1, 1, , , , , , , , , , , , , , , , , , , , , , 0],
|
||||
]
|
||||
break;
|
||||
}
|
||||
|
||||
// draw the sample pattern in the middle of the board
|
||||
var startRow = Math.floor((rows / 2) - (sampleData.length / 2));
|
||||
var startCol = Math.floor((cols / 2) - (sampleData[0].length / 2));
|
||||
for (var i = startRow; i < startRow + sampleData.length; i++) {
|
||||
for (var j = startCol; j < startCol + sampleData[0].length; j++) {
|
||||
if (sampleData[i - startRow][j - startCol] === 1) {
|
||||
select(goLGrid[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:500px; height:500px"></div>
|
||||
<p>
|
||||
Game Controls:
|
||||
<input id="start" onclick="toggleSimulation()" type="button" value="Start" />
|
||||
<input id="step" onclick="stepOnclick()" type="button" value="Step" />
|
||||
<input id="clear" onclick="goLClear()" type="button" value="Clear" style="margin-bottom: 10px" />
|
||||
Sample patterns:
|
||||
<select id="samplePatterns" onchange="loadSample(this.options[this.selectedIndex].value)" style="margin-bottom: 10px">
|
||||
<option value="symm4">Symmetry</option>
|
||||
<option value="pulsar">Pulsar</option>
|
||||
<option value="spaceships">Spaceships</option>
|
||||
<option value="bigGliders">Big gliders</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
This sample shows an implementation of <a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway's Game of Life</a> in GoJS.
|
||||
Conway's Game of Life is a simple cellular automaton devised by British mathematician John Horton Conway in 1970. To start or advance the simulation,
|
||||
use the controls above.
|
||||
</p>
|
||||
<p>
|
||||
Whether or not a given cell lives, dies, or is born in a step is determined by the number of live cells in the 8 squares adjacent to it. For a cell <i>x</i> with <i>n</i> adjacent live cells:
|
||||
</p>
|
||||
<ul>
|
||||
<li>If <i>n</i> <= 1, cell <i>x</i> dies or stays dead (from underpopulation).</li>
|
||||
<li>If <i>n</i> > 3, cell <i>x</i> dies or stays dead (from overpopulation).</li>
|
||||
<li>If <i>n</i> = 3, then <i>x</i> is born or stays alive.</li>
|
||||
<li>If <i>n</i> = 2, then <i>x</i> maintains its status.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Though the rules are simple, they can produce complex patterns, some of which are shown in the dropdown above.
|
||||
To create your own patterns, click or drag anywhere on the grid when the simulation is not running.
|
||||
</p>
|
||||
<p>
|
||||
Each cell is implemented by a simple <a>Part</a> holding a small square <a>Shape</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Gantt chart</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A Gantt chart that supports zooming into the timeline." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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", // Diagram refers to its DIV HTML element by id
|
||||
{
|
||||
_widthFactor: 1, // a scale for the nodes' positions and widths
|
||||
isReadOnly: true, // deny the user permission to alter the diagram or zoom in or out
|
||||
allowZoom: false,
|
||||
"grid.visible": true, // display a grid in the background of the diagram
|
||||
"grid.gridCellSize": new go.Size(30, 150)
|
||||
});
|
||||
|
||||
// create the template for the standard nodes
|
||||
myDiagram.nodeTemplateMap.add("",
|
||||
$(go.Node, "Auto",
|
||||
// links come from the right and go to the left side of the top of the node
|
||||
{ fromSpot: go.Spot.Right, toSpot: new go.Spot(0.001, 0, 11, 0) },
|
||||
$(go.Shape, "Rectangle",
|
||||
{ height: 15 },
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("width", "width", function(w) { return scaleWidth(w); })),
|
||||
$(go.TextBlock,
|
||||
{ margin: 2, alignment: go.Spot.Left },
|
||||
new go.Binding("text", "key")),
|
||||
// using a function in the Binding allows the value to
|
||||
// change when Diagram.updateAllTargetBindings is called
|
||||
new go.Binding("location", "loc",
|
||||
function(l) { return new go.Point(scaleWidth(l.x), l.y); })
|
||||
));
|
||||
|
||||
// create the template for the start node
|
||||
myDiagram.nodeTemplateMap.add("start",
|
||||
$(go.Node,
|
||||
{ fromSpot: go.Spot.Right, toSpot: go.Spot.Top, selectable: false },
|
||||
$(go.Shape, "Diamond",
|
||||
{ height: 15, width: 15 }),
|
||||
// make the location of the start node is not scalable
|
||||
new go.Binding("location", "loc")
|
||||
));
|
||||
|
||||
// create the template for the end node
|
||||
myDiagram.nodeTemplateMap.add("end",
|
||||
$(go.Node,
|
||||
{ fromSpot: go.Spot.Right, toSpot: go.Spot.Top, selectable: false },
|
||||
$(go.Shape, "Diamond",
|
||||
{ height: 15, width: 15 }),
|
||||
// make the location of the end node (with location.x < 0) scalable
|
||||
new go.Binding("location", "loc",
|
||||
function(l) {
|
||||
if (l.x >= 0) return new go.Point(scaleWidth(l.x), l.y);
|
||||
else return l;
|
||||
})
|
||||
));
|
||||
|
||||
// create the link template
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.Orthogonal,
|
||||
corner: 3, toShortLength: 2,
|
||||
selectable: false
|
||||
},
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 2 }),
|
||||
$(go.Shape,
|
||||
{ toArrow: "OpenTriangle" })
|
||||
);
|
||||
|
||||
// add the nodes and links to the model
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[ // node data
|
||||
{ key: "a", color: "coral", width: 120, loc: new go.Point(scaleWidth(0), 40) },
|
||||
{ key: "b", color: "turquoise", width: 160, loc: new go.Point(scaleWidth(0), 60) },
|
||||
{ key: "c", color: "coral", width: 150, loc: new go.Point(scaleWidth(120), 80) },
|
||||
{ key: "d", color: "turquoise", width: 190, loc: new go.Point(scaleWidth(120), 100) },
|
||||
{ key: "e", color: "coral", width: 150, loc: new go.Point(scaleWidth(270), 120) },
|
||||
{ key: "f", color: "turquoise", width: 130, loc: new go.Point(scaleWidth(310), 140) },
|
||||
{ key: "g", color: "coral", width: 155, loc: new go.Point(scaleWidth(420), 160) },
|
||||
{ key: "begin", category: "start", loc: new go.Point(-15, 20) },
|
||||
{ key: "end", category: "end", loc: new go.Point(scaleWidth(575), 180) }
|
||||
],
|
||||
[ // link data
|
||||
{ from: "begin", to: "a" },
|
||||
{ from: "begin", to: "b" },
|
||||
{ from: "a", to: "c" },
|
||||
{ from: "a", to: "d" },
|
||||
{ from: "b", to: "e" },
|
||||
{ from: "c", to: "e" },
|
||||
{ from: "d", to: "f" },
|
||||
{ from: "e", to: "g" },
|
||||
{ from: "f", to: "end" },
|
||||
{ from: "g", to: "end" }
|
||||
]);
|
||||
|
||||
// add a Graduated panel to show the dates, globally scoped
|
||||
dateScale =
|
||||
$(go.Part, "Graduated",
|
||||
{
|
||||
graduatedTickUnit: 1, graduatedMin: 0, graduatedMax: 3,
|
||||
pickable: false, location: new go.Point(0, 0)
|
||||
},
|
||||
$(go.Shape,
|
||||
{ name: "line", strokeWidth: 0, geometryString: "M0 0 H" + scaleWidth(450) }
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
name: "labels",
|
||||
font: "10pt sans-serif",
|
||||
alignmentFocus: new go.Spot(0, 0, -3, -3),
|
||||
graduatedFunction: function(v) {
|
||||
var d = new Date(2017, 6, 23);
|
||||
d.setDate(d.getDate() + v * 7);
|
||||
// format date output to string
|
||||
var options = { month: "short", day: "2-digit" };
|
||||
return d.toLocaleDateString("en-US", options);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
myDiagram.add(dateScale);
|
||||
}
|
||||
|
||||
// scale the number according to the current widthFactor
|
||||
function scaleWidth(num) {
|
||||
return num * myDiagram._widthFactor;
|
||||
}
|
||||
|
||||
// change the grid's cell size and the widthFactor,
|
||||
// then update Bindings to scale the widths and positions of nodes,
|
||||
// as well as the width of the date scale
|
||||
function rescale() {
|
||||
var val = parseFloat(document.getElementById("widthSlider").value);
|
||||
myDiagram.startTransaction("rescale");
|
||||
myDiagram.grid.gridCellSize = new go.Size(val, 150);
|
||||
myDiagram._widthFactor = val / 30;
|
||||
myDiagram.updateAllTargetBindings();
|
||||
// update width of date scale and maybe change interval of labels if too small
|
||||
var width = scaleWidth(450);
|
||||
dateScale.findObject("line").geometryString = "M0 0 H" + width;
|
||||
if (width >= 140) dateScale.findObject("labels").interval = 1;
|
||||
if (width < 140) dateScale.findObject("labels").interval = 2;
|
||||
if (width < 70) dateScale.findObject("labels").interval = 4;
|
||||
myDiagram.commitTransaction("rescale");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="height:600px;width:100%;border:1px solid black"></div>
|
||||
<div id="slider">
|
||||
<label>Spacing:</label>
|
||||
<input id="widthSlider" type="range" min="2" max="90" value="30" onchange="rescale()"/>
|
||||
</div>
|
||||
<p>
|
||||
This sample demonstrates a simple Gantt chart. Gantt charts are used to illustrate project schedules, denoting the start and end dates for terminal and summary elements of the project.
|
||||
</p>
|
||||
<p>
|
||||
You can zoom in on the diagram by changing the "Spacing" value,
|
||||
which scales the diagram using a data binding function for nodes' widths and locations.
|
||||
This is in place of changing the <a>Diagram.scale</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,661 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Genogram</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A genogram is a family tree diagram for visualizing hereditary patterns." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
initialAutoScale: go.Diagram.Uniform,
|
||||
"undoManager.isEnabled": true,
|
||||
// when a node is selected, draw a big yellow circle behind it
|
||||
nodeSelectionAdornmentTemplate:
|
||||
$(go.Adornment, "Auto",
|
||||
{ layerName: "Grid" }, // the predefined layer that is behind everything else
|
||||
$(go.Shape, "Circle", { fill: "#c1cee3", stroke: null }),
|
||||
$(go.Placeholder, { margin: 2 })
|
||||
),
|
||||
layout: // use a custom layout, defined below
|
||||
$(GenogramLayout, { direction: 90, layerSpacing: 30, columnSpacing: 10 })
|
||||
});
|
||||
|
||||
// determine the color for each attribute shape
|
||||
function attrFill(a) {
|
||||
switch (a) {
|
||||
case "A": return "#00af54"; // green
|
||||
case "B": return "#f27935"; // orange
|
||||
case "C": return "#d4071c"; // red
|
||||
case "D": return "#70bdc2"; // cyan
|
||||
case "E": return "#fcf384"; // gold
|
||||
case "F": return "#e69aaf"; // pink
|
||||
case "G": return "#08488f"; // blue
|
||||
case "H": return "#866310"; // brown
|
||||
case "I": return "#9270c2"; // purple
|
||||
case "J": return "#a3cf62"; // chartreuse
|
||||
case "K": return "#91a4c2"; // lightgray bluish
|
||||
case "L": return "#af70c2"; // magenta
|
||||
case "S": return "#d4071c"; // red
|
||||
default: return "transparent";
|
||||
}
|
||||
}
|
||||
|
||||
// determine the geometry for each attribute shape in a male;
|
||||
// except for the slash these are all squares at each of the four corners of the overall square
|
||||
var tlsq = go.Geometry.parse("F M1 1 l19 0 0 19 -19 0z");
|
||||
var trsq = go.Geometry.parse("F M20 1 l19 0 0 19 -19 0z");
|
||||
var brsq = go.Geometry.parse("F M20 20 l19 0 0 19 -19 0z");
|
||||
var blsq = go.Geometry.parse("F M1 20 l19 0 0 19 -19 0z");
|
||||
var slash = go.Geometry.parse("F M38 0 L40 0 40 2 2 40 0 40 0 38z");
|
||||
function maleGeometry(a) {
|
||||
switch (a) {
|
||||
case "A": return tlsq;
|
||||
case "B": return tlsq;
|
||||
case "C": return tlsq;
|
||||
case "D": return trsq;
|
||||
case "E": return trsq;
|
||||
case "F": return trsq;
|
||||
case "G": return brsq;
|
||||
case "H": return brsq;
|
||||
case "I": return brsq;
|
||||
case "J": return blsq;
|
||||
case "K": return blsq;
|
||||
case "L": return blsq;
|
||||
case "S": return slash;
|
||||
default: return tlsq;
|
||||
}
|
||||
}
|
||||
|
||||
// determine the geometry for each attribute shape in a female;
|
||||
// except for the slash these are all pie shapes at each of the four quadrants of the overall circle
|
||||
var tlarc = go.Geometry.parse("F M20 20 B 180 90 20 20 19 19 z");
|
||||
var trarc = go.Geometry.parse("F M20 20 B 270 90 20 20 19 19 z");
|
||||
var brarc = go.Geometry.parse("F M20 20 B 0 90 20 20 19 19 z");
|
||||
var blarc = go.Geometry.parse("F M20 20 B 90 90 20 20 19 19 z");
|
||||
function femaleGeometry(a) {
|
||||
switch (a) {
|
||||
case "A": return tlarc;
|
||||
case "B": return tlarc;
|
||||
case "C": return tlarc;
|
||||
case "D": return trarc;
|
||||
case "E": return trarc;
|
||||
case "F": return trarc;
|
||||
case "G": return brarc;
|
||||
case "H": return brarc;
|
||||
case "I": return brarc;
|
||||
case "J": return blarc;
|
||||
case "K": return blarc;
|
||||
case "L": return blarc;
|
||||
case "S": return slash;
|
||||
default: return tlarc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// two different node templates, one for each sex,
|
||||
// named by the category value in the node data object
|
||||
myDiagram.nodeTemplateMap.add("M", // male
|
||||
$(go.Node, "Vertical",
|
||||
{ locationSpot: go.Spot.Center, locationObjectName: "ICON", selectionObjectName: "ICON" },
|
||||
$(go.Panel,
|
||||
{ name: "ICON" },
|
||||
$(go.Shape, "Square",
|
||||
{ width: 40, height: 40, strokeWidth: 2, fill: "white", stroke: "#919191", portId: "" }),
|
||||
$(go.Panel,
|
||||
{ // for each attribute show a Shape at a particular place in the overall square
|
||||
itemTemplate:
|
||||
$(go.Panel,
|
||||
$(go.Shape,
|
||||
{ stroke: null, strokeWidth: 0 },
|
||||
new go.Binding("fill", "", attrFill),
|
||||
new go.Binding("geometry", "", maleGeometry))
|
||||
),
|
||||
margin: 1
|
||||
},
|
||||
new go.Binding("itemArray", "a")
|
||||
)
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ textAlign: "center", maxSize: new go.Size(80, NaN) },
|
||||
new go.Binding("text", "n"))
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("F", // female
|
||||
$(go.Node, "Vertical",
|
||||
{ locationSpot: go.Spot.Center, locationObjectName: "ICON", selectionObjectName: "ICON" },
|
||||
$(go.Panel,
|
||||
{ name: "ICON" },
|
||||
$(go.Shape, "Circle",
|
||||
{ width: 40, height: 40, strokeWidth: 2, fill: "white", stroke: "#a1a1a1", portId: "" }),
|
||||
$(go.Panel,
|
||||
{ // for each attribute show a Shape at a particular place in the overall circle
|
||||
itemTemplate:
|
||||
$(go.Panel,
|
||||
$(go.Shape,
|
||||
{ stroke: null, strokeWidth: 0 },
|
||||
new go.Binding("fill", "", attrFill),
|
||||
new go.Binding("geometry", "", femaleGeometry))
|
||||
),
|
||||
margin: 1
|
||||
},
|
||||
new go.Binding("itemArray", "a")
|
||||
)
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{ textAlign: "center", maxSize: new go.Size(80, NaN) },
|
||||
new go.Binding("text", "n"))
|
||||
));
|
||||
|
||||
// the representation of each label node -- nothing shows on a Marriage Link
|
||||
myDiagram.nodeTemplateMap.add("LinkLabel",
|
||||
$(go.Node, { selectable: false, width: 1, height: 1, fromEndSegmentLength: 20 }));
|
||||
|
||||
|
||||
myDiagram.linkTemplate = // for parent-child relationships
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.Orthogonal, corner: 5,
|
||||
layerName: "Background", selectable: false,
|
||||
fromSpot: go.Spot.Bottom, toSpot: go.Spot.Top
|
||||
},
|
||||
$(go.Shape, { stroke: "#424242", strokeWidth: 2 })
|
||||
);
|
||||
|
||||
myDiagram.linkTemplateMap.add("Marriage", // for marriage relationships
|
||||
$(go.Link,
|
||||
{ selectable: false },
|
||||
$(go.Shape, { strokeWidth: 2.5, stroke: "#5d8cc1" /* blue */ })
|
||||
));
|
||||
|
||||
|
||||
// n: name, s: sex, m: mother, f: father, ux: wife, vir: husband, a: attributes/markers
|
||||
setupDiagram(myDiagram, [
|
||||
{ key: 0, n: "Aaron", s: "M", m: -10, f: -11, ux: 1, a: ["C", "F", "K"] },
|
||||
{ key: 1, n: "Alice", s: "F", m: -12, f: -13, a: ["B", "H", "K"] },
|
||||
{ key: 2, n: "Bob", s: "M", m: 1, f: 0, ux: 3, a: ["C", "H", "L"] },
|
||||
{ key: 3, n: "Barbara", s: "F", a: ["C"] },
|
||||
{ key: 4, n: "Bill", s: "M", m: 1, f: 0, ux: 5, a: ["E", "H"] },
|
||||
{ key: 5, n: "Brooke", s: "F", a: ["B", "H", "L"] },
|
||||
{ key: 6, n: "Claire", s: "F", m: 1, f: 0, a: ["C"] },
|
||||
{ key: 7, n: "Carol", s: "F", m: 1, f: 0, a: ["C", "I"] },
|
||||
{ key: 8, n: "Chloe", s: "F", m: 1, f: 0, vir: 9, a: ["E"] },
|
||||
{ key: 9, n: "Chris", s: "M", a: ["B", "H"] },
|
||||
{ key: 10, n: "Ellie", s: "F", m: 3, f: 2, a: ["E", "G"] },
|
||||
{ key: 11, n: "Dan", s: "M", m: 3, f: 2, a: ["B", "J"] },
|
||||
{ key: 12, n: "Elizabeth", s: "F", vir: 13, a: ["J"] },
|
||||
{ key: 13, n: "David", s: "M", m: 5, f: 4, a: ["B", "H"] },
|
||||
{ key: 14, n: "Emma", s: "F", m: 5, f: 4, a: ["E", "G"] },
|
||||
{ key: 15, n: "Evan", s: "M", m: 8, f: 9, a: ["F", "H"] },
|
||||
{ key: 16, n: "Ethan", s: "M", m: 8, f: 9, a: ["D", "K"] },
|
||||
{ key: 17, n: "Eve", s: "F", vir: 16, a: ["B", "F", "L"] },
|
||||
{ key: 18, n: "Emily", s: "F", m: 8, f: 9 },
|
||||
{ key: 19, n: "Fred", s: "M", m: 17, f: 16, a: ["B"] },
|
||||
{ key: 20, n: "Faith", s: "F", m: 17, f: 16, a: ["L"] },
|
||||
{ key: 21, n: "Felicia", s: "F", m: 12, f: 13, a: ["H"] },
|
||||
{ key: 22, n: "Frank", s: "M", m: 12, f: 13, a: ["B", "H"] },
|
||||
|
||||
// "Aaron"'s ancestors
|
||||
{ key: -10, n: "Paternal Grandfather", s: "M", m: -33, f: -32, ux: -11, a: ["A", "S"] },
|
||||
{ key: -11, n: "Paternal Grandmother", s: "F", a: ["E", "S"] },
|
||||
{ key: -32, n: "Paternal Great", s: "M", ux: -33, a: ["F", "H", "S"] },
|
||||
{ key: -33, n: "Paternal Great", s: "F", a: ["S"] },
|
||||
{ key: -40, n: "Great Uncle", s: "M", m: -33, f: -32, a: ["F", "H", "S"] },
|
||||
{ key: -41, n: "Great Aunt", s: "F", m: -33, f: -32, a: ["B", "I", "S"] },
|
||||
{ key: -20, n: "Uncle", s: "M", m: -11, f: -10, a: ["A", "S"] },
|
||||
|
||||
// "Alice"'s ancestors
|
||||
{ key: -12, n: "Maternal Grandfather", s: "M", ux: -13, a: ["D", "L", "S"] },
|
||||
{ key: -13, n: "Maternal Grandmother", s: "F", m: -31, f: -30, a: ["H", "S"] },
|
||||
{ key: -21, n: "Aunt", s: "F", m: -13, f: -12, a: ["C", "I"] },
|
||||
{ key: -22, n: "Uncle", s: "M", ux: -21 },
|
||||
{ key: -23, n: "Cousin", s: "M", m: -21, f: -22 },
|
||||
{ key: -30, n: "Maternal Great", s: "M", ux: -31, a: ["D", "J", "S"] },
|
||||
{ key: -31, n: "Maternal Great", s: "F", m: -50, f: -51, a: ["B", "H", "L", "S"] },
|
||||
{ key: -42, n: "Great Uncle", s: "M", m: -30, f: -31, a: ["C", "J", "S"] },
|
||||
{ key: -43, n: "Great Aunt", s: "F", m: -30, f: -31, a: ["E", "G", "S"] },
|
||||
{ key: -50, n: "Maternal Great Great", s: "F", ux: -51, a: ["D", "I", "S"] },
|
||||
{ key: -51, n: "Maternal Great Great", s: "M", a: ["B", "H", "S"] }
|
||||
],
|
||||
4 /* focus on this person */);
|
||||
}
|
||||
|
||||
|
||||
// create and initialize the Diagram.model given an array of node data representing people
|
||||
function setupDiagram(diagram, array, focusId) {
|
||||
diagram.model =
|
||||
go.GraphObject.make(go.GraphLinksModel,
|
||||
{ // declare support for link label nodes
|
||||
linkLabelKeysProperty: "labelKeys",
|
||||
// this property determines which template is used
|
||||
nodeCategoryProperty: "s",
|
||||
// if a node data object is copied, copy its data.a Array
|
||||
copiesArrays: true,
|
||||
// create all of the nodes for people
|
||||
nodeDataArray: array
|
||||
});
|
||||
setupMarriages(diagram);
|
||||
setupParents(diagram);
|
||||
|
||||
var node = diagram.findNodeForKey(focusId);
|
||||
if (node !== null) {
|
||||
diagram.select(node);
|
||||
// remove any spouse for the person under focus:
|
||||
//node.linksConnected.each(function(l) {
|
||||
// if (!l.isLabeledLink) return;
|
||||
// l.opacity = 0;
|
||||
// var spouse = l.getOtherNode(node);
|
||||
// spouse.opacity = 0;
|
||||
// spouse.pickable = false;
|
||||
//});
|
||||
}
|
||||
}
|
||||
|
||||
function findMarriage(diagram, a, b) { // A and B are node keys
|
||||
var nodeA = diagram.findNodeForKey(a);
|
||||
var nodeB = diagram.findNodeForKey(b);
|
||||
if (nodeA !== null && nodeB !== null) {
|
||||
var it = nodeA.findLinksBetween(nodeB); // in either direction
|
||||
while (it.next()) {
|
||||
var link = it.value;
|
||||
// Link.data.category === "Marriage" means it's a marriage relationship
|
||||
if (link.data !== null && link.data.category === "Marriage") return link;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// now process the node data to determine marriages
|
||||
function setupMarriages(diagram) {
|
||||
var model = diagram.model;
|
||||
var nodeDataArray = model.nodeDataArray;
|
||||
for (var i = 0; i < nodeDataArray.length; i++) {
|
||||
var data = nodeDataArray[i];
|
||||
var key = data.key;
|
||||
var uxs = data.ux;
|
||||
if (uxs !== undefined) {
|
||||
if (typeof uxs === "number") uxs = [uxs];
|
||||
for (var j = 0; j < uxs.length; j++) {
|
||||
var wife = uxs[j];
|
||||
if (key === wife) {
|
||||
// or warn no reflexive marriages
|
||||
continue;
|
||||
}
|
||||
var link = findMarriage(diagram, key, wife);
|
||||
if (link === null) {
|
||||
// add a label node for the marriage link
|
||||
var mlab = { s: "LinkLabel" };
|
||||
model.addNodeData(mlab);
|
||||
// add the marriage link itself, also referring to the label node
|
||||
var mdata = { from: key, to: wife, labelKeys: [mlab.key], category: "Marriage" };
|
||||
model.addLinkData(mdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
var virs = data.vir;
|
||||
if (virs !== undefined) {
|
||||
if (typeof virs === "number") virs = [virs];
|
||||
for (var j = 0; j < virs.length; j++) {
|
||||
var husband = virs[j];
|
||||
if (key === husband) {
|
||||
// or warn no reflexive marriages
|
||||
continue;
|
||||
}
|
||||
var link = findMarriage(diagram, key, husband);
|
||||
if (link === null) {
|
||||
// add a label node for the marriage link
|
||||
var mlab = { s: "LinkLabel" };
|
||||
model.addNodeData(mlab);
|
||||
// add the marriage link itself, also referring to the label node
|
||||
var mdata = { from: key, to: husband, labelKeys: [mlab.key], category: "Marriage" };
|
||||
model.addLinkData(mdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process parent-child relationships once all marriages are known
|
||||
function setupParents(diagram) {
|
||||
var model = diagram.model;
|
||||
var nodeDataArray = model.nodeDataArray;
|
||||
for (var i = 0; i < nodeDataArray.length; i++) {
|
||||
var data = nodeDataArray[i];
|
||||
var key = data.key;
|
||||
var mother = data.m;
|
||||
var father = data.f;
|
||||
if (mother !== undefined && father !== undefined) {
|
||||
var link = findMarriage(diagram, mother, father);
|
||||
if (link === null) {
|
||||
// or warn no known mother or no known father or no known marriage between them
|
||||
if (window.console) window.console.log("unknown marriage: " + mother + " & " + father);
|
||||
continue;
|
||||
}
|
||||
var mdata = link.data;
|
||||
var mlabkey = mdata.labelKeys[0];
|
||||
var cdata = { from: mlabkey, to: key };
|
||||
myDiagram.model.addLinkData(cdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A custom layout that shows the two families related to a person's parents
|
||||
function GenogramLayout() {
|
||||
go.LayeredDigraphLayout.call(this);
|
||||
this.initializeOption = go.LayeredDigraphLayout.InitDepthFirstIn;
|
||||
this.spouseSpacing = 30; // minimum space between spouses
|
||||
}
|
||||
go.Diagram.inherit(GenogramLayout, go.LayeredDigraphLayout);
|
||||
|
||||
GenogramLayout.prototype.makeNetwork = function(coll) {
|
||||
// generate LayoutEdges for each parent-child Link
|
||||
var net = this.createNetwork();
|
||||
if (coll instanceof go.Diagram) {
|
||||
this.add(net, coll.nodes, true);
|
||||
this.add(net, coll.links, true);
|
||||
} else if (coll instanceof go.Group) {
|
||||
this.add(net, coll.memberParts, false);
|
||||
} else if (coll.iterator) {
|
||||
this.add(net, coll.iterator, false);
|
||||
}
|
||||
return net;
|
||||
};
|
||||
|
||||
// internal method for creating LayeredDigraphNetwork where husband/wife pairs are represented
|
||||
// by a single LayeredDigraphVertex corresponding to the label Node on the marriage Link
|
||||
GenogramLayout.prototype.add = function(net, coll, nonmemberonly) {
|
||||
var multiSpousePeople = new go.Set();
|
||||
// consider all Nodes in the given collection
|
||||
var it = coll.iterator;
|
||||
while (it.next()) {
|
||||
var node = it.value;
|
||||
if (!(node instanceof go.Node)) continue;
|
||||
if (!node.isLayoutPositioned || !node.isVisible()) continue;
|
||||
if (nonmemberonly && node.containingGroup !== null) continue;
|
||||
// if it's an unmarried Node, or if it's a Link Label Node, create a LayoutVertex for it
|
||||
if (node.isLinkLabel) {
|
||||
// get marriage Link
|
||||
var link = node.labeledLink;
|
||||
var spouseA = link.fromNode;
|
||||
var spouseB = link.toNode;
|
||||
// create vertex representing both husband and wife
|
||||
var vertex = net.addNode(node);
|
||||
// now define the vertex size to be big enough to hold both spouses
|
||||
vertex.width = spouseA.actualBounds.width + this.spouseSpacing + spouseB.actualBounds.width;
|
||||
vertex.height = Math.max(spouseA.actualBounds.height, spouseB.actualBounds.height);
|
||||
vertex.focus = new go.Point(spouseA.actualBounds.width + this.spouseSpacing / 2, vertex.height / 2);
|
||||
} else {
|
||||
// don't add a vertex for any married person!
|
||||
// instead, code above adds label node for marriage link
|
||||
// assume a marriage Link has a label Node
|
||||
var marriages = 0;
|
||||
node.linksConnected.each(function(l) { if (l.isLabeledLink) marriages++; });
|
||||
if (marriages === 0) {
|
||||
var vertex = net.addNode(node);
|
||||
} else if (marriages > 1) {
|
||||
multiSpousePeople.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
// now do all Links
|
||||
it.reset();
|
||||
while (it.next()) {
|
||||
var link = it.value;
|
||||
if (!(link instanceof go.Link)) continue;
|
||||
if (!link.isLayoutPositioned || !link.isVisible()) continue;
|
||||
if (nonmemberonly && link.containingGroup !== null) continue;
|
||||
// if it's a parent-child link, add a LayoutEdge for it
|
||||
if (!link.isLabeledLink) {
|
||||
var parent = net.findVertex(link.fromNode); // should be a label node
|
||||
var child = net.findVertex(link.toNode);
|
||||
if (child !== null) { // an unmarried child
|
||||
net.linkVertexes(parent, child, link);
|
||||
} else { // a married child
|
||||
link.toNode.linksConnected.each(function(l) {
|
||||
if (!l.isLabeledLink) return; // if it has no label node, it's a parent-child link
|
||||
// found the Marriage Link, now get its label Node
|
||||
var mlab = l.labelNodes.first();
|
||||
// parent-child link should connect with the label node,
|
||||
// so the LayoutEdge should connect with the LayoutVertex representing the label node
|
||||
var mlabvert = net.findVertex(mlab);
|
||||
if (mlabvert !== null) {
|
||||
net.linkVertexes(parent, mlabvert, link);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (multiSpousePeople.count > 0) {
|
||||
// find all collections of people that are indirectly married to each other
|
||||
var node = multiSpousePeople.first();
|
||||
var cohort = new go.Set();
|
||||
this.extendCohort(cohort, node);
|
||||
// then encourage them all to be the same generation by connecting them all with a common vertex
|
||||
var dummyvert = net.createVertex();
|
||||
net.addVertex(dummyvert);
|
||||
var marriages = new go.Set();
|
||||
cohort.each(function(n) {
|
||||
n.linksConnected.each(function(l) {
|
||||
marriages.add(l);
|
||||
})
|
||||
});
|
||||
marriages.each(function(link) {
|
||||
// find the vertex for the marriage link (i.e. for the label node)
|
||||
var mlab = link.labelNodes.first()
|
||||
var v = net.findVertex(mlab);
|
||||
if (v !== null) {
|
||||
net.linkVertexes(dummyvert, v, null);
|
||||
}
|
||||
});
|
||||
// done with these people, now see if there are any other multiple-married people
|
||||
multiSpousePeople.removeAll(cohort);
|
||||
}
|
||||
};
|
||||
|
||||
// collect all of the people indirectly married with a person
|
||||
GenogramLayout.prototype.extendCohort = function(coll, node) {
|
||||
if (coll.has(node)) return;
|
||||
coll.add(node);
|
||||
var lay = this;
|
||||
node.linksConnected.each(function(l) {
|
||||
if (l.isLabeledLink) { // if it's a marriage link, continue with both spouses
|
||||
lay.extendCohort(coll, l.fromNode);
|
||||
lay.extendCohort(coll, l.toNode);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
GenogramLayout.prototype.assignLayers = function() {
|
||||
go.LayeredDigraphLayout.prototype.assignLayers.call(this);
|
||||
var horiz = this.direction == 0.0 || this.direction == 180.0;
|
||||
// for every vertex, record the maximum vertex width or height for the vertex's layer
|
||||
var maxsizes = [];
|
||||
this.network.vertexes.each(function(v) {
|
||||
var lay = v.layer;
|
||||
var max = maxsizes[lay];
|
||||
if (max === undefined) max = 0;
|
||||
var sz = (horiz ? v.width : v.height);
|
||||
if (sz > max) maxsizes[lay] = sz;
|
||||
});
|
||||
// now make sure every vertex has the maximum width or height according to which layer it is in,
|
||||
// and aligned on the left (if horizontal) or the top (if vertical)
|
||||
this.network.vertexes.each(function(v) {
|
||||
var lay = v.layer;
|
||||
var max = maxsizes[lay];
|
||||
if (horiz) {
|
||||
v.focus = new go.Point(0, v.height / 2);
|
||||
v.width = max;
|
||||
} else {
|
||||
v.focus = new go.Point(v.width / 2, 0);
|
||||
v.height = max;
|
||||
}
|
||||
});
|
||||
// from now on, the LayeredDigraphLayout will think that the Node is bigger than it really is
|
||||
// (other than the ones that are the widest or tallest in their respective layer).
|
||||
};
|
||||
|
||||
GenogramLayout.prototype.commitNodes = function() {
|
||||
go.LayeredDigraphLayout.prototype.commitNodes.call(this);
|
||||
// position regular nodes
|
||||
this.network.vertexes.each(function(v) {
|
||||
if (v.node !== null && !v.node.isLinkLabel) {
|
||||
v.node.position = new go.Point(v.x, v.y);
|
||||
}
|
||||
});
|
||||
// position the spouses of each marriage vertex
|
||||
var layout = this;
|
||||
this.network.vertexes.each(function(v) {
|
||||
if (v.node === null) return;
|
||||
if (!v.node.isLinkLabel) return;
|
||||
var labnode = v.node;
|
||||
var lablink = labnode.labeledLink;
|
||||
// In case the spouses are not actually moved, we need to have the marriage link
|
||||
// position the label node, because LayoutVertex.commit() was called above on these vertexes.
|
||||
// Alternatively we could override LayoutVetex.commit to be a no-op for label node vertexes.
|
||||
lablink.invalidateRoute();
|
||||
var spouseA = lablink.fromNode;
|
||||
var spouseB = lablink.toNode;
|
||||
// prefer fathers on the left, mothers on the right
|
||||
if (spouseA.data.s === "F") { // sex is female
|
||||
var temp = spouseA;
|
||||
spouseA = spouseB;
|
||||
spouseB = temp;
|
||||
}
|
||||
// see if the parents are on the desired sides, to avoid a link crossing
|
||||
var aParentsNode = layout.findParentsMarriageLabelNode(spouseA);
|
||||
var bParentsNode = layout.findParentsMarriageLabelNode(spouseB);
|
||||
if (aParentsNode !== null && bParentsNode !== null && aParentsNode.position.x > bParentsNode.position.x) {
|
||||
// swap the spouses
|
||||
var temp = spouseA;
|
||||
spouseA = spouseB;
|
||||
spouseB = temp;
|
||||
}
|
||||
spouseA.position = new go.Point(v.x, v.y);
|
||||
spouseB.position = new go.Point(v.x + spouseA.actualBounds.width + layout.spouseSpacing, v.y);
|
||||
if (spouseA.opacity === 0) {
|
||||
var pos = new go.Point(v.centerX - spouseA.actualBounds.width / 2, v.y);
|
||||
spouseA.position = pos;
|
||||
spouseB.position = pos;
|
||||
} else if (spouseB.opacity === 0) {
|
||||
var pos = new go.Point(v.centerX - spouseB.actualBounds.width / 2, v.y);
|
||||
spouseA.position = pos;
|
||||
spouseB.position = pos;
|
||||
}
|
||||
});
|
||||
// position only-child nodes to be under the marriage label node
|
||||
this.network.vertexes.each(function(v) {
|
||||
if (v.node === null || v.node.linksConnected.count > 1) return;
|
||||
var mnode = layout.findParentsMarriageLabelNode(v.node);
|
||||
if (mnode !== null && mnode.linksConnected.count === 1) { // if only one child
|
||||
var mvert = layout.network.findVertex(mnode);
|
||||
var newbnds = v.node.actualBounds.copy();
|
||||
newbnds.x = mvert.centerX - v.node.actualBounds.width / 2;
|
||||
// see if there's any empty space at the horizontal mid-point in that layer
|
||||
var overlaps = layout.diagram.findObjectsIn(newbnds, function(x) { return x.part; }, function(p) { return p !== v.node; }, true);
|
||||
if (overlaps.count === 0) {
|
||||
v.node.move(newbnds.position);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
GenogramLayout.prototype.findParentsMarriageLabelNode = function(node) {
|
||||
var it = node.findNodesInto();
|
||||
while (it.next()) {
|
||||
var n = it.value;
|
||||
if (n.isLinkLabel) return n;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
// end GenogramLayout class
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: #F8F8F8; border: solid 1px black; width:100%; height:600px;"></div>
|
||||
<p>A <em>genogram</em> or <em>pedigree chart</em> is an extended family tree diagram that displays information about each person or each relationship.</p>
|
||||
<p>
|
||||
There are functions that convert an attribute value into a brush color or Shape geometry,
|
||||
to be added to the Node representing the person.
|
||||
</p>
|
||||
<p>
|
||||
A custom <a>LayeredDigraphLayout</a> does the layout, assuming there is a central person whose mother and father
|
||||
each have their own ancestors. In this case we focus on "Bill", but any of the children of "Alice" and "Aaron" would work.
|
||||
The overridden <b>add</b> function allows husband/wife pairs to be represented by a single <a>LayeredDigraphVertex</a>.
|
||||
</p>
|
||||
<p>For a simpler family tree, see the <a href="familyTree.html">family tree sample</a>.</p>
|
||||
<p>
|
||||
The node data representing the people, processed by the <code>setupDiagram</code> function is below.
|
||||
The properties are:
|
||||
<ul>
|
||||
<li><b>key</b>, the unique ID of the person</li>
|
||||
<li><b>n</b>, the person's name</li>
|
||||
<li><b>s</b>, the person's sex</li>
|
||||
<li><b>m</b>, the person's mother's key</li>
|
||||
<li><b>f</b>, the person's father's key</li>
|
||||
<li><b>ux</b>, the person's wife</li>
|
||||
<li><b>vir</b>, the person's husband</li>
|
||||
<li><b>a</b>, an Array of the attributes or markers that the person has</li>
|
||||
</ul>
|
||||
</p>
|
||||
<pre id="peopleData">
|
||||
[
|
||||
{ key: 0, n: "Aaron", s: "M", m:-10, f:-11, ux: 1, a: ["C", "F", "K"] },
|
||||
{ key: 1, n: "Alice", s: "F", m:-12, f:-13, a: ["B", "H", "K"] },
|
||||
{ key: 2, n: "Bob", s: "M", m: 1, f: 0, ux: 3, a: ["C", "H", "L"] },
|
||||
{ key: 3, n: "Barbara", s: "F", a: ["C"] },
|
||||
{ key: 4, n: "Bill", s: "M", m: 1, f: 0, ux: 5, a: ["E", "H"] },
|
||||
{ key: 5, n: "Brooke", s: "F", a: ["B", "H", "L"] },
|
||||
{ key: 6, n: "Claire", s: "F", m: 1, f: 0, a: ["C"] },
|
||||
{ key: 7, n: "Carol", s: "F", m: 1, f: 0, a: ["C", "I"] },
|
||||
{ key: 8, n: "Chloe", s: "F", m: 1, f: 0, vir: 9, a: ["E"] },
|
||||
{ key: 9, n: "Chris", s: "M", a: ["B", "H"] },
|
||||
{ key: 10, n: "Ellie", s: "F", m: 3, f: 2, a: ["E", "G"] },
|
||||
{ key: 11, n: "Dan", s: "M", m: 3, f: 2, a: ["B", "J"] },
|
||||
{ key: 12, n: "Elizabeth", s: "F", vir: 13, a: ["J"] },
|
||||
{ key: 13, n: "David", s: "M", m: 5, f: 4, a: ["B", "H"] },
|
||||
{ key: 14, n: "Emma", s: "F", m: 5, f: 4, a: ["E", "G"] },
|
||||
{ key: 15, n: "Evan", s: "M", m: 8, f: 9, a: ["F", "H"] },
|
||||
{ key: 16, n: "Ethan", s: "M", m: 8, f: 9, a: ["D", "K", "S"] },
|
||||
{ key: 17, n: "Eve", s: "F", vir: 16, a: ["B", "F", "L", "S"] },
|
||||
{ key: 18, n: "Emily", s: "F", m: 8, f: 9 },
|
||||
{ key: 19, n: "Fred", s: "M", m: 17, f: 16, a: ["B"] },
|
||||
{ key: 20, n: "Faith", s: "F", m: 17, f: 16, a: ["L"] },
|
||||
{ key: 21, n: "Felicia", s: "F", m: 12, f: 13, a: ["H"] },
|
||||
{ key: 22, n: "Frank", s: "M", m: 12, f: 13, a: ["B", "H"] },
|
||||
|
||||
// "Aaron"'s ancestors
|
||||
{ key: -10, n: "Paternal Grandfather", s: "M", m: -33, f: -32, ux: -11, a: ["A"] },
|
||||
{ key: -11, n: "Paternal Grandmother", s: "F", a: ["E"] },
|
||||
{ key: -32, n: "Paternal Great", s: "M", ux: -33, a: ["F", "H"] },
|
||||
{ key: -33, n: "Paternal Great", s: "F" },
|
||||
{ key: -40, n: "Great Uncle", s: "M", m: -33, f: -32, a: ["F", "H"] },
|
||||
{ key: -41, n: "Great Aunt", s: "F", m: -33, f: -32, a: ["B", "I"] },
|
||||
{ key: -20, n: "Uncle", s: "M", m: -11, f: -10, a: ["A"] },
|
||||
|
||||
// "Alice"'s ancestors
|
||||
{ key: -12, n: "Maternal Grandfather", s: "M", ux: -13, a: ["D", "L"] },
|
||||
{ key: -13, n: "Maternal Grandmother", s: "F", m: -31, f: -30, a: ["H"] },
|
||||
{ key: -21, n: "Aunt", s: "F", m: -13, f: -12, a: ["C", "I"] },
|
||||
{ key: -22, n: "uncle", s: "M", ux: -21 },
|
||||
{ key: -23, n: "cousin", s: "M", m: -21, f: -22 },
|
||||
{ key: -30, n: "Maternal Great", s: "M", ux: -31, a: ["D", "J"] },
|
||||
{ key: -31, n: "Maternal Great", s: "F", m: -50, f: -51, a: ["B", "H", "L"] },
|
||||
{ key: -42, n: "Great Uncle", s: "M", m: -30, f: -31, a: ["C", "J"] },
|
||||
{ key: -43, n: "Great Aunt", s: "F", m: -30, f: -31, a: ["E", "G"] },
|
||||
{ key: -50, n: "Maternal Great Great", s: "F", ux: -51, a: ["D", "I"] },
|
||||
{ key: -51, n: "Maternal Great Great", s: "M", a: ["B", "H"] }
|
||||
]
|
||||
</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ToolManager.gestureBehavior Sample</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Example of ToolManager.gestureBehavior property." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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
|
||||
|
||||
// define a simple Node template
|
||||
var myNodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 },
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
var myModel = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: "Alpha", color: "lightblue" },
|
||||
{ key: "Beta", color: "orange" },
|
||||
{ key: "Gamma", color: "lightgreen" },
|
||||
{ key: "Delta", color: "pink" }
|
||||
],
|
||||
[
|
||||
{ from: "Alpha", to: "Beta" },
|
||||
{ from: "Alpha", to: "Gamma" },
|
||||
{ from: "Beta", to: "Beta" },
|
||||
{ from: "Gamma", to: "Delta" },
|
||||
{ from: "Delta", to: "Alpha" }
|
||||
]);
|
||||
|
||||
myDiagram = $(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
allowHorizontalScroll: false, allowVerticalScroll: false,
|
||||
"panningTool.isEnabled": false,
|
||||
"toolManager.gestureBehavior": go.ToolManager.GestureCancel,
|
||||
model: myModel,
|
||||
nodeTemplate: myNodeTemplate
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function changegestureBehavior(id) {
|
||||
switch (id) {
|
||||
case "GestureZoom":
|
||||
myDiagram.toolManager.gestureBehavior = go.ToolManager.GestureZoom;
|
||||
break;
|
||||
case "GestureCancel":
|
||||
myDiagram.toolManager.gestureBehavior = go.ToolManager.GestureCancel;
|
||||
break;
|
||||
case "GestureNone":
|
||||
myDiagram.toolManager.gestureBehavior = go.ToolManager.GestureNone;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
|
||||
<p>
|
||||
This sample demonstrates the different values of <a>ToolManager.gestureBehavior</a>.
|
||||
</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li><a>ToolManager,GestureZoom</a> is the default value: Pinch gestures will zoom the Diagram.
|
||||
<li><a>ToolManager,GestureNone</a>: Pinch gestures zoom the browser page instead of the Diagram.
|
||||
<li><a>ToolManager,GestureCancel</a>: Pinch gestures will do nothing.
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
Set the value for the Diagram below:
|
||||
</p>
|
||||
|
||||
<p><label><input type="radio" id="GestureZoom" onclick="changegestureBehavior(this.id)" name="group1"><code>go.ToolManager.GestureZoom;</code></label>
|
||||
<p><label><input type="radio" id="GestureCancel" onclick="changegestureBehavior(this.id)" name="group1" checked="checked"><code>go.ToolManager.GestureCancel;</code></label>
|
||||
<p><label><input type="radio" id="GestureNone" onclick="changegestureBehavior(this.id)" name="group1"><code>go.ToolManager.GestureNone;</code></label>
|
||||
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,487 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Grafcet Diagrams</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A Grafcet diagram editor, showing buttons for creating new nodes and links related to the selected node." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
allowLink: false, // linking is only started via buttons, not modelessly;
|
||||
// see the "startLink..." functions and CustomLinkingTool defined below
|
||||
// double-click in the background creates a new "Start" node
|
||||
"clickCreatingTool.archetypeNodeData": { category: "Start", step: 1, text: "Action" },
|
||||
linkingTool: new CustomLinkingTool(), // defined below to automatically turn on allowLink
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener("Modified", function(e) {
|
||||
var button = document.getElementById("saveModel");
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf("*");
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += "*";
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
// This implements a selection Adornment that is a horizontal bar of command buttons
|
||||
// that appear when the user selects a node.
|
||||
// Each button has a click function to execute the command, a tooltip for a textual description,
|
||||
// and a Binding of "visible" to hide the button if it cannot be executed for that particular node.
|
||||
|
||||
var commandsAdornment =
|
||||
$("ContextMenu",
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: null, stroke: "deepskyblue", strokeWidth: 2, shadowVisible: false }),
|
||||
$(go.Placeholder)
|
||||
),
|
||||
$(go.Panel, "Horizontal",
|
||||
{ defaultStretch: go.GraphObject.Vertical },
|
||||
$("Button",
|
||||
$(go.Shape,
|
||||
{
|
||||
geometryString: "M0 0 L10 0",
|
||||
fill: null, stroke: "red", margin: 3
|
||||
}),
|
||||
{ click: addExclusive, toolTip: makeTooltip("Add Exclusive") },
|
||||
new go.Binding("visible", "", canAddSplit).ofObject()),
|
||||
$("Button",
|
||||
$(go.Shape,
|
||||
{
|
||||
geometryString: "M0 0 L10 0 M0 3 10 3",
|
||||
fill: null, stroke: "red", margin: 3
|
||||
}),
|
||||
{ click: addParallel, toolTip: makeTooltip("Add Parallel") },
|
||||
new go.Binding("visible", "", canAddSplit).ofObject()),
|
||||
$("Button",
|
||||
$(go.Shape,
|
||||
{
|
||||
geometryString: "M0 0 L10 0 10 6 0 6z",
|
||||
fill: "lightyellow", margin: 3
|
||||
}),
|
||||
{ click: addStep, toolTip: makeTooltip("Add Step") },
|
||||
new go.Binding("visible", "", canAddStep).ofObject()),
|
||||
$("Button",
|
||||
$(go.Shape,
|
||||
{
|
||||
geometryString: "M0 0 M5 0 L5 10 M3 8 5 10 7 8 M10 0",
|
||||
fill: null, margin: 3
|
||||
}),
|
||||
{ click: startLinkDown, toolTip: makeTooltip("Draw Link Down") },
|
||||
new go.Binding("visible", "", canStartLink).ofObject()),
|
||||
$("Button",
|
||||
$(go.Shape,
|
||||
{
|
||||
geometryString: "M0 0 M3 0 L3 2 7 2 7 6 3 6 3 10 M1 8 3 10 5 8 M10 0",
|
||||
fill: null, margin: 3
|
||||
}),
|
||||
{ click: startLinkAround, toolTip: makeTooltip("Draw Link Skip") },
|
||||
new go.Binding("visible", "", canStartLink).ofObject()),
|
||||
$("Button",
|
||||
$(go.Shape,
|
||||
{
|
||||
geometryString: "M0 0 M3 2 L3 0 7 0 7 10 3 10 3 8 M5 6 7 4 9 6 M10 0",
|
||||
fill: null, margin: 3
|
||||
}),
|
||||
{ click: startLinkUp, toolTip: makeTooltip("Draw Link Repeat") },
|
||||
new go.Binding("visible", "", canStartLink).ofObject())
|
||||
)
|
||||
);
|
||||
|
||||
function makeTooltip(str) { // a helper function for defining tooltips for buttons
|
||||
return $("ToolTip",
|
||||
$(go.TextBlock, str));
|
||||
}
|
||||
|
||||
// Commands for adding new Nodes
|
||||
|
||||
function addStep(e, obj) {
|
||||
var node = obj.part.adornedPart;
|
||||
var model = myDiagram.model;
|
||||
model.startTransaction("add Step");
|
||||
var loc = node.location.copy();
|
||||
loc.y += 50;
|
||||
var nodedata = { location: go.Point.stringify(loc) };
|
||||
model.addNodeData(nodedata);
|
||||
var nodekey = model.getKeyForNodeData(nodedata);
|
||||
var linkdata = { from: model.getKeyForNodeData(node.data), to: nodekey, text: "c" };
|
||||
model.addLinkData(linkdata);
|
||||
var newnode = myDiagram.findNodeForData(nodedata);
|
||||
myDiagram.select(newnode);
|
||||
model.commitTransaction("add Step");
|
||||
}
|
||||
|
||||
function canAddStep(adorn) {
|
||||
var node = adorn.adornedPart;
|
||||
if (node.category === "" || node.category === "Start") {
|
||||
return node.findLinksOutOf().count === 0;
|
||||
} else if (node.category === "Parallel" || node.category === "Exclusive") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addParallel(e, obj) { addSplit(obj.part.adornedPart, "Parallel"); }
|
||||
function addExclusive(e, obj) { addSplit(obj.part.adornedPart, "Exclusive"); }
|
||||
|
||||
function addSplit(node, type) {
|
||||
var model = myDiagram.model;
|
||||
model.startTransaction("add " + type);
|
||||
var loc = node.location.copy();
|
||||
loc.y += 50;
|
||||
var nodedata = { category: type, location: go.Point.stringify(loc) };
|
||||
model.addNodeData(nodedata);
|
||||
var nodekey = model.getKeyForNodeData(nodedata);
|
||||
var linkdata = { from: model.getKeyForNodeData(node.data), to: nodekey };
|
||||
model.addLinkData(linkdata);
|
||||
var newnode = myDiagram.findNodeForData(nodedata);
|
||||
myDiagram.select(newnode);
|
||||
model.commitTransaction("add " + type);
|
||||
}
|
||||
|
||||
function canAddSplit(adorn) {
|
||||
var node = adorn.adornedPart;
|
||||
if (node.category === "" || node.category === "Start") {
|
||||
return node.findLinksOutOf().count === 0;
|
||||
} else if (node.category === "Parallel" || node.category === "Exclusive") {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Commands for starting drawing new Links
|
||||
|
||||
function startLinkDown(e, obj) { startLink(obj.part.adornedPart, "", "c"); }
|
||||
function startLinkAround(e, obj) { startLink(obj.part.adornedPart, "Skip", "s"); }
|
||||
function startLinkUp(e, obj) { startLink(obj.part.adornedPart, "Repeat", "r"); }
|
||||
|
||||
function startLink(node, category, condition) {
|
||||
var tool = myDiagram.toolManager.linkingTool;
|
||||
// to control what kind of Link is created,
|
||||
// change the LinkingTool.archetypeLinkData's category
|
||||
myDiagram.model.setCategoryForLinkData(tool.archetypeLinkData, category);
|
||||
// also change the text indicating the condition, which the user can edit
|
||||
tool.archetypeLinkData.text = condition;
|
||||
tool.startObject = node.port;
|
||||
myDiagram.currentTool = tool;
|
||||
tool.doActivate();
|
||||
}
|
||||
|
||||
function canStartLink(adorn) {
|
||||
var node = adorn.adornedPart;
|
||||
return true; // this could be smarter
|
||||
}
|
||||
|
||||
|
||||
// The various kinds of Nodes
|
||||
|
||||
// a helper function that declares common properties for all kinds of nodes
|
||||
function commonNodeStyle() {
|
||||
return [
|
||||
{
|
||||
locationSpot: go.Spot.Center,
|
||||
selectionAdornmentTemplate: commandsAdornment // shared selection Adornment
|
||||
},
|
||||
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
];
|
||||
}
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Start",
|
||||
$(go.Node, "Horizontal", commonNodeStyle(),
|
||||
{ locationObjectName: "STEPPANEL", selectionObjectName: "STEPPANEL" },
|
||||
$(go.Panel, "Auto",
|
||||
{ // this is the port element, not the whole Node
|
||||
name: "STEPPANEL", portId: "",
|
||||
fromSpot: go.Spot.Bottom, fromLinkable: true
|
||||
},
|
||||
$(go.Shape, { fill: "lightgreen" }),
|
||||
$(go.Panel, "Auto",
|
||||
{ margin: 3 },
|
||||
$(go.Shape, { fill: null, minSize: new go.Size(20, 20) }),
|
||||
$(go.TextBlock, "Start",
|
||||
{ margin: 3, editable: true },
|
||||
new go.Binding("text", "step").makeTwoWay())
|
||||
)
|
||||
),
|
||||
// a connector line between the texts
|
||||
$(go.Shape, "LineH", { width: 10, height: 1 }),
|
||||
// the boxed, editable text on the side
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: "white" }),
|
||||
$(go.TextBlock, "Action",
|
||||
{ margin: 3, editable: true },
|
||||
new go.Binding("text", "text").makeTwoWay())
|
||||
)
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("",
|
||||
$(go.Node, "Horizontal", commonNodeStyle(),
|
||||
{ locationObjectName: "STEPPANEL", selectionObjectName: "STEPPANEL" },
|
||||
$(go.Panel, "Auto",
|
||||
{ // this is the port element, not the whole Node
|
||||
name: "STEPPANEL", portId: "",
|
||||
fromSpot: go.Spot.Bottom, fromLinkable: true,
|
||||
toSpot: go.Spot.Top, toLinkable: true
|
||||
},
|
||||
$(go.Shape, { fill: "lightyellow", minSize: new go.Size(20, 20) }),
|
||||
$(go.TextBlock, "Step",
|
||||
{ margin: 3, editable: true },
|
||||
new go.Binding("text", "step").makeTwoWay())
|
||||
),
|
||||
$(go.Shape, "LineH", { width: 10, height: 1 }),
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: "white" }),
|
||||
$(go.TextBlock, "Action",
|
||||
{ margin: 3, editable: true },
|
||||
new go.Binding("text", "text").makeTwoWay())
|
||||
)
|
||||
));
|
||||
|
||||
var resizeAdornment =
|
||||
$(go.Adornment, go.Panel.Spot,
|
||||
$(go.Placeholder),
|
||||
$(go.Shape, // left resize handle
|
||||
{
|
||||
alignment: go.Spot.Left, cursor: "col-resize",
|
||||
desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "dodgerblue"
|
||||
}),
|
||||
$(go.Shape, // right resize handle
|
||||
{
|
||||
alignment: go.Spot.Right, cursor: "col-resize",
|
||||
desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "dodgerblue"
|
||||
})
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Parallel",
|
||||
$(go.Node, commonNodeStyle(),
|
||||
{ // special resizing: just at the ends
|
||||
resizable: true, resizeObjectName: "SHAPE", resizeAdornmentTemplate: resizeAdornment,
|
||||
fromLinkable: true, toLinkable: true
|
||||
},
|
||||
$(go.Shape,
|
||||
{ // horizontal pair of lines stretched to an initial width of 200
|
||||
name: "SHAPE", geometryString: "M0 0 L100 0 M0 4 L100 4",
|
||||
fill: "transparent", stroke: "red", width: 200
|
||||
},
|
||||
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify))
|
||||
));
|
||||
|
||||
myDiagram.nodeTemplateMap.add("Exclusive",
|
||||
$(go.Node, commonNodeStyle(),
|
||||
{ // special resizing: just at the ends
|
||||
resizable: true, resizeObjectName: "SHAPE", resizeAdornmentTemplate: resizeAdornment,
|
||||
fromLinkable: true, toLinkable: true
|
||||
},
|
||||
$(go.Shape,
|
||||
{ // horizontal line stretched to an initial width of 200
|
||||
name: "SHAPE", geometryString: "M0 0 L100 0",
|
||||
fill: "transparent", stroke: "red", width: 200
|
||||
},
|
||||
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify))
|
||||
));
|
||||
|
||||
// the various kinds of Links
|
||||
|
||||
myDiagram.linkTemplateMap.add("",
|
||||
$(BarLink, // subclass defined below
|
||||
{ routing: go.Link.Orthogonal },
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 1.5 }),
|
||||
$(go.Shape, "LineH", // only visible when there is text
|
||||
{ width: 20, height: 1, visible: false },
|
||||
new go.Binding("visible", "text", function(t) { return t !== ""; })),
|
||||
$(go.TextBlock, // only visible when there is text
|
||||
{ alignmentFocus: new go.Spot(0, 0.5, -12, 0), editable: true },
|
||||
new go.Binding("text", "text").makeTwoWay(),
|
||||
new go.Binding("visible", "text", function(t) { return t !== ""; }))
|
||||
));
|
||||
|
||||
myDiagram.linkTemplateMap.add("Skip",
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.AvoidsNodes,
|
||||
fromSpot: go.Spot.Bottom, toSpot: go.Spot.Top,
|
||||
fromEndSegmentLength: 4, toEndSegmentLength: 4
|
||||
},
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 1.5 }),
|
||||
$(go.Shape, "LineH", // only visible when there is text
|
||||
{ width: 20, height: 1, visible: false },
|
||||
new go.Binding("visible", "text", function(t) { return t !== ""; })),
|
||||
$(go.TextBlock, // only visible when there is text
|
||||
{ alignmentFocus: new go.Spot(1, 0.5, 12, 0), editable: true },
|
||||
new go.Binding("text", "text").makeTwoWay(),
|
||||
new go.Binding("visible", "text", function(t) { return t !== ""; }))
|
||||
));
|
||||
|
||||
myDiagram.linkTemplateMap.add("Repeat",
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.AvoidsNodes,
|
||||
fromSpot: go.Spot.Bottom, toSpot: go.Spot.Top,
|
||||
fromEndSegmentLength: 4, toEndSegmentLength: 4
|
||||
},
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 1.5 }),
|
||||
$(go.Shape,
|
||||
{ toArrow: "OpenTriangle", segmentIndex: 3, segmentFraction: 0.75 }),
|
||||
$(go.Shape,
|
||||
{ toArrow: "OpenTriangle", segmentIndex: 3, segmentFraction: 0.25 }),
|
||||
$(go.Shape, "LineH", // only visible when there is text
|
||||
{ width: 20, height: 1, visible: false },
|
||||
new go.Binding("visible", "text", function(t) { return t !== ""; })),
|
||||
$(go.TextBlock, // only visible when there is text
|
||||
{ alignmentFocus: new go.Spot(1, 0.5, 12, 0), editable: true },
|
||||
new go.Binding("text", "text").makeTwoWay(),
|
||||
new go.Binding("visible", "text", function(t) { return t !== ""; }))
|
||||
));
|
||||
|
||||
// start off with a simple diagram
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
// This custom LinkingTool just turns on Diagram.allowLink when it starts,
|
||||
// and turns it off again when it stops so that users cannot draw new links modelessly.
|
||||
function CustomLinkingTool() {
|
||||
go.LinkingTool.call(this);
|
||||
}
|
||||
go.Diagram.inherit(CustomLinkingTool, go.LinkingTool);
|
||||
|
||||
// user-drawn linking is normally disabled,
|
||||
// but needs to be turned on when using this tool
|
||||
CustomLinkingTool.prototype.doStart = function() {
|
||||
this.diagram.allowLink = true;
|
||||
go.LinkingTool.prototype.doStart.call(this);
|
||||
};
|
||||
|
||||
CustomLinkingTool.prototype.doStop = function() {
|
||||
go.LinkingTool.prototype.doStop.call(this);
|
||||
this.diagram.allowLink = false;
|
||||
};
|
||||
// end CustomLinkingTool
|
||||
|
||||
|
||||
// This custom Link class is smart about computing the link point and direction
|
||||
// at "Parallel" and "Exclusive" nodes.
|
||||
function BarLink() {
|
||||
go.Link.call(this);
|
||||
}
|
||||
go.Diagram.inherit(BarLink, go.Link);
|
||||
|
||||
BarLink.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 op = otherport.getDocumentPoint(go.Spot.Center);
|
||||
var below = op.y > r.centerY;
|
||||
var y = below ? r.bottom : r.top;
|
||||
if (node.category === "Parallel" || node.category === "Exclusive") {
|
||||
if (op.x < r.left) return new go.Point(r.left, y);
|
||||
if (op.x > r.right) return new go.Point(r.right, y);
|
||||
return new go.Point(op.x, y);
|
||||
} else {
|
||||
return new go.Point(r.centerX, y);
|
||||
}
|
||||
};
|
||||
|
||||
BarLink.prototype.getLinkDirection = function(node, port, linkpoint, spot, from, ortho, othernode, otherport) {
|
||||
var p = port.getDocumentPoint(go.Spot.Center);
|
||||
var op = otherport.getDocumentPoint(go.Spot.Center);
|
||||
var below = op.y > p.y;
|
||||
return below ? 90 : 270;
|
||||
};
|
||||
// end BarLink class
|
||||
|
||||
// save a model to and load a model from JSON text, displayed below the Diagram
|
||||
function save() {
|
||||
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 800px; height: 600px"></div>
|
||||
<p>
|
||||
A grafcet diagram is similar to a <a href="sequentialFunction.html">sequential function chart</a>.
|
||||
</p>
|
||||
<p>
|
||||
Select a Node to show a list of Buttons that enable creating new Nodes or drawing new Links.
|
||||
These buttons are defined as an adornment that is used in a common <a>Part.selectionAdornmentTemplate</a>.
|
||||
This diagram uses many custom functions, including an overridden <a>LinkingTool</a> and a special
|
||||
Link class, <b>BarLink</b>.
|
||||
</p>
|
||||
<div id="buttons">
|
||||
<button id="saveModel" onclick="save()">Save</button>
|
||||
<button id="loadModel" onclick="load()">Load</button>
|
||||
Diagram Model saved in JSON format:
|
||||
</div>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "class": "go.GraphLinksModel",
|
||||
"nodeDataArray": [
|
||||
{"key":1, "category":"Start", "location":"300 50", "step":"1", "text":"Action 1"},
|
||||
{"key":2, "category":"Parallel", "location":"300 100"},
|
||||
{"key":3, "location":"225 125", "step":"3", "text":"Action 2"},
|
||||
{"key":4, "location":"325 150", "step":"4", "text":"Action 3"},
|
||||
{"key":5, "location":"225 175", "step":"5", "text":"Action 4"},
|
||||
{"key":6, "category":"Parallel", "location":"300 200"},
|
||||
{"key":7, "location":"300 250", "step":"7", "text":"Action 6"},
|
||||
{"key":11, "category":"Start", "location":"300 350", "step":"11", "text":"Action 1"},
|
||||
{"key":12, "category":"Exclusive", "location":"300 400"},
|
||||
{"key":13, "location":"225 450", "step":"13", "text":"Action 2"},
|
||||
{"key":14, "location":"325 475", "step":"14", "text":"Action 3"},
|
||||
{"key":15, "location":"225 500", "step":"15", "text":"Action 4"},
|
||||
{"key":16, "category":"Exclusive", "location":"300 550"},
|
||||
{"key":17, "location":"300 600", "step":"17", "text":"Action 6"},
|
||||
{"key":21, "location":"500 50", "step":"21", "text":"Act 1"},
|
||||
{"key":22, "location":"500 100", "step":"22", "text":"Act 2"},
|
||||
{"key":23, "location":"500 150", "step":"23", "text":"Act 3"},
|
||||
{"key":24, "location":"500 200", "step":"24", "text":"Act 4"},
|
||||
{"key":31, "location":"500 400", "step":"31", "text":"Act 1"},
|
||||
{"key":32, "location":"500 450", "step":"32", "text":"Act 2"},
|
||||
{"key":33, "location":"500 500", "step":"33", "text":"Act 3"},
|
||||
{"key":34, "location":"500 550", "step":"34", "text":"Act 4"}
|
||||
],
|
||||
"linkDataArray": [
|
||||
{"from":1, "to":2, "text":"condition 1"},
|
||||
{"from":2, "to":3},
|
||||
{"from":2, "to":4},
|
||||
{"from":3, "to":5, "text":"condition 2"},
|
||||
{"from":4, "to":6},
|
||||
{"from":5, "to":6},
|
||||
{"from":6, "to":7, "text":"condition 5"},
|
||||
{"from":11, "to":12, "text":"condition 1"},
|
||||
{"from":12, "to":13, "text":"condition 12"},
|
||||
{"from":12, "to":14, "text":"condition 13"},
|
||||
{"from":13, "to":15, "text":"condition 2"},
|
||||
{"from":14, "to":16, "text":"condition 14"},
|
||||
{"from":15, "to":16, "text":"condition 15"},
|
||||
{"from":16, "to":17, "text":"condition 5"},
|
||||
{"from":21, "to":22, "text":"c1"},
|
||||
{"from":22, "to":23, "text":"c2"},
|
||||
{"from":23, "to":24, "text":"c3"},
|
||||
{"from":21, "to":24, "text":"c14", "category":"Skip"},
|
||||
{"from":31, "to":32, "text":"c1"},
|
||||
{"from":32, "to":33, "text":"c2"},
|
||||
{"from":33, "to":34, "text":"c3"},
|
||||
{"from":33, "to":32, "text":"c14", "category":"Repeat"}
|
||||
]}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Grouping</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A diagram holding groups that incrementally grow the diagram as groups are expanded." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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", // Diagram refers to its DIV HTML element by id
|
||||
{
|
||||
layout: $(go.TreeLayout, // the layout for the entire diagram
|
||||
{
|
||||
angle: 90,
|
||||
arrangement: go.TreeLayout.ArrangementHorizontal,
|
||||
isRealtime: false
|
||||
})
|
||||
});
|
||||
|
||||
// define the node template for non-groups
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape, "Rectangle",
|
||||
{ stroke: null, strokeWidth: 0 },
|
||||
new go.Binding("fill", "key")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 7, font: "Bold 14px Sans-Serif" },
|
||||
//the text, color, and key are all bound to the same property in the node data
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ routing: go.Link.Orthogonal, corner: 10 },
|
||||
$(go.Shape, { strokeWidth: 2 }),
|
||||
$(go.Shape, { toArrow: "OpenTriangle" })
|
||||
);
|
||||
|
||||
// define the group template
|
||||
myDiagram.groupTemplate =
|
||||
$(go.Group, "Auto",
|
||||
{ // define the group's internal layout
|
||||
layout: $(go.TreeLayout,
|
||||
{ angle: 90, arrangement: go.TreeLayout.ArrangementHorizontal, isRealtime: false }),
|
||||
// the group begins unexpanded;
|
||||
// upon expansion, a Diagram Listener will generate contents for the group
|
||||
isSubGraphExpanded: false,
|
||||
// when a group is expanded, if it contains no parts, generate a subGraph inside of it
|
||||
subGraphExpandedChanged: function(group) {
|
||||
if (group.memberParts.count === 0) {
|
||||
randomGroup(group.data.key);
|
||||
}
|
||||
}
|
||||
},
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: null, stroke: "gray", strokeWidth: 2 }),
|
||||
$(go.Panel, "Vertical",
|
||||
{ defaultAlignment: go.Spot.Left, margin: 4 },
|
||||
$(go.Panel, "Horizontal",
|
||||
{ defaultAlignment: go.Spot.Top },
|
||||
// the SubGraphExpanderButton is a panel that functions as a button to expand or collapse the subGraph
|
||||
$("SubGraphExpanderButton"),
|
||||
$(go.TextBlock,
|
||||
{ font: "Bold 18px Sans-Serif", margin: 4 },
|
||||
new go.Binding("text", "key"))
|
||||
),
|
||||
// create a placeholder to represent the area where the contents of the group are
|
||||
$(go.Placeholder,
|
||||
{ padding: new go.Margin(0, 10) })
|
||||
) // end Vertical Panel
|
||||
); // end Group
|
||||
|
||||
// generate the initial model
|
||||
randomGroup();
|
||||
}
|
||||
|
||||
// Generate a random number of nodes, including groups.
|
||||
// If a group's key is given as a parameter, put these nodes inside it
|
||||
function randomGroup(group) {
|
||||
// all modification to the diagram is within this transaction
|
||||
myDiagram.startTransaction("addGroupContents");
|
||||
var addedKeys = []; // this will contain the keys of all nodes created
|
||||
var groupCount = 0; // the number of groups in the diagram, to determine the numbers in the keys of new groups
|
||||
myDiagram.nodes.each(function(node) {
|
||||
if (node instanceof go.Group) groupCount++;
|
||||
});
|
||||
// create a random number of groups
|
||||
// ensure there are at least 10 groups in the diagram
|
||||
var groups = Math.floor(Math.random() * 2);
|
||||
if (groupCount < 10) groups += 1;
|
||||
for (var i = 0; i < groups; i++) {
|
||||
var name = "group" + (i + groupCount);
|
||||
myDiagram.model.addNodeData({ key: name, isGroup: true, group: group });
|
||||
addedKeys.push(name);
|
||||
}
|
||||
var nodes = Math.floor(Math.random() * 3) + 2;
|
||||
// create a random number of non-group nodes
|
||||
for (var i = 0; i < nodes; i++) {
|
||||
var color = go.Brush.randomColor();
|
||||
// make sure the color, which will be the node's key, is unique in the diagram before adding the new node
|
||||
if (myDiagram.findPartForKey(color) === null) {
|
||||
myDiagram.model.addNodeData({ key: color, group: group });
|
||||
addedKeys.push(color);
|
||||
}
|
||||
}
|
||||
// add at least one link from each node to another
|
||||
// this could result in clusters of nodes unreachable from each other, but no lone nodes
|
||||
var arr = [];
|
||||
for (var x in addedKeys) arr.push(addedKeys[x]);
|
||||
arr.sort(function(x, y) { return Math.random(2) - 1; });
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var from = Math.floor(Math.random() * (arr.length - i)) + i;
|
||||
if (from !== i) {
|
||||
myDiagram.model.addLinkData({ from: arr[from], to: arr[i] });
|
||||
}
|
||||
}
|
||||
myDiagram.commitTransaction("addGroupContents");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="height:600px;width:100%;border:1px solid black"></div>
|
||||
<p>
|
||||
This sample demonstrates subgraphs that are created only as groups are expanded.
|
||||
</p>
|
||||
<p>
|
||||
The model is initially a random number of nodes, including some groups, in a tree layout.
|
||||
When a group is expanded, the <a>Group.subGraphExpandedChanged</a> event handler calls a function to generate a random number of nodes
|
||||
in a tree layout inside the group if it did not contain none any.
|
||||
Each non-group node added has a unique random color, and links are added by giving each node one link to another node.
|
||||
</p>
|
||||
<p>
|
||||
The addition of nodes and links is performed within a transaction to ensure that the diagram updates itself properly.
|
||||
The diagram's tree layout and the tree layouts within each group are performed again when a sub-graph is expanded or collapsed.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Buttons that show on Hover</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="When the mouse hovers over a node, show a set of Buttons that could perform various actions." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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 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", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
hoverDelay: 200, // controls how long to wait motionless (msec) before showing Adornment
|
||||
"undoManager.isEnabled": true // enable undo & redo
|
||||
});
|
||||
|
||||
// this is shown by the mouseHover event handler
|
||||
var nodeHoverAdornment =
|
||||
$(go.Adornment, "Spot",
|
||||
{
|
||||
background: "transparent",
|
||||
// hide the Adornment when the mouse leaves it
|
||||
mouseLeave: function(e, obj) {
|
||||
var ad = obj.part;
|
||||
ad.adornedPart.removeAdornment("mouseHover");
|
||||
}
|
||||
},
|
||||
$(go.Placeholder,
|
||||
{
|
||||
background: "transparent", // to allow this Placeholder to be "seen" by mouse events
|
||||
isActionable: true, // needed because this is in a temporary Layer
|
||||
click: function(e, obj) {
|
||||
var node = obj.part.adornedPart;
|
||||
node.diagram.select(node);
|
||||
}
|
||||
}),
|
||||
$("Button",
|
||||
{ alignment: go.Spot.Left, alignmentFocus: go.Spot.Right },
|
||||
{ click: function(e, obj) { alert("Hi!"); } },
|
||||
$(go.TextBlock, "Hi!")),
|
||||
$("Button",
|
||||
{ alignment: go.Spot.Right, alignmentFocus: go.Spot.Left },
|
||||
{ click: function(e, obj) { alert("Bye"); } },
|
||||
$(go.TextBlock, "Bye"))
|
||||
);
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto", // the Shape will go around the TextBlock
|
||||
$(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding("text", "key")),
|
||||
{ // show the Adornment when a mouseHover event occurs
|
||||
mouseHover: function(e, obj) {
|
||||
var node = obj.part;
|
||||
nodeHoverAdornment.adornedObject = node;
|
||||
node.addAdornment("mouseHover", nodeHoverAdornment);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: "Alpha", color: "lightblue" },
|
||||
{ key: "Beta", color: "orange" },
|
||||
{ key: "Gamma", color: "lightgreen" },
|
||||
{ key: "Delta", color: "pink" }
|
||||
],
|
||||
[
|
||||
{ from: "Alpha", to: "Beta" },
|
||||
{ from: "Alpha", to: "Gamma" },
|
||||
{ from: "Beta", to: "Beta" },
|
||||
{ from: "Gamma", to: "Delta" },
|
||||
{ from: "Delta", to: "Alpha" }
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
<p>
|
||||
This sample demonstrates buttons that appear when the user hovers over a node with the mouse.
|
||||
The advantage of using an <a>Adornment</a> is that it keeps the Node template simpler.
|
||||
That means there are less resources used to create nodes -- only that one adornment can be shown.
|
||||
</p>
|
||||
<p>
|
||||
However, using a template as the <a>Part.selectionAdornmentTemplate</a> would allow for more
|
||||
than one set of buttons to be shown simultaneously, one set for each selected node.
|
||||
</p>
|
||||
<p>
|
||||
This technique does not work on touch devices.
|
||||
</p>
|
||||
<p>
|
||||
If you want to show such an Adornment on mouseEnter and mouseLeave, rather than on mouseHover,
|
||||
the code is given in the documentation for the <a>GraphObject.mouseEnter</a> property.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,246 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Drag and Drop Example</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Use HTML5 drag-and-drop to implement dragging HTML elements onto a GoJS Diagram to create new nodes." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<style>
|
||||
.draggable {
|
||||
font: bold 16px sans-serif;
|
||||
width: 140px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
background: white;
|
||||
cursor: move;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.palettezone {
|
||||
width: 160px;
|
||||
height: 400px;
|
||||
background: lightblue;
|
||||
padding: 10px;
|
||||
padding-top: 1px;
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
<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 init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
// *********************************************************
|
||||
// First, set up the infrastructure to do HTML drag-and-drop
|
||||
// *********************************************************
|
||||
|
||||
var dragged = null; // A reference to the element currently being dragged
|
||||
|
||||
// highlight stationary nodes during an external drag-and-drop into a Diagram
|
||||
function highlight(node) { // may be null
|
||||
var oldskips = myDiagram.skipsUndoManager;
|
||||
myDiagram.skipsUndoManager = true;
|
||||
myDiagram.startTransaction("highlight");
|
||||
if (node !== null) {
|
||||
myDiagram.highlight(node);
|
||||
} else {
|
||||
myDiagram.clearHighlighteds();
|
||||
}
|
||||
myDiagram.commitTransaction("highlight");
|
||||
myDiagram.skipsUndoManager = oldskips;
|
||||
}
|
||||
|
||||
// This event should only fire on the drag targets.
|
||||
// Instead of finding every drag target,
|
||||
// we can add the event to the document and disregard
|
||||
// all elements that are not of class "draggable"
|
||||
document.addEventListener("dragstart", function(event) {
|
||||
if (event.target.className !== "draggable") return;
|
||||
// Some data must be set to allow drag
|
||||
event.dataTransfer.setData("text", event.target.textContent);
|
||||
|
||||
// store a reference to the dragged element and the offset of the mouse from the center of the element
|
||||
dragged = event.target;
|
||||
dragged.offsetX = event.offsetX - dragged.clientWidth / 2;
|
||||
dragged.offsetY = event.offsetY - dragged.clientHeight / 2;
|
||||
// Objects during drag will have a red border
|
||||
event.target.style.border = "2px solid red";
|
||||
}, false);
|
||||
|
||||
// This event resets styles after a drag has completed (successfully or not)
|
||||
document.addEventListener("dragend", function(event) {
|
||||
// reset the border of the dragged element
|
||||
dragged.style.border = "";
|
||||
highlight(null);
|
||||
}, false);
|
||||
|
||||
// Next, events intended for the drop target - the Diagram div
|
||||
|
||||
var div = document.getElementById("myDiagramDiv");
|
||||
div.addEventListener("dragenter", function(event) {
|
||||
// Here you could also set effects on the Diagram,
|
||||
// such as changing the background color to indicate an acceptable drop zone
|
||||
|
||||
// Requirement in some browsers, such as Internet Explorer
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
div.addEventListener("dragover", function(event) {
|
||||
// We call preventDefault to allow a drop
|
||||
// But on divs that already contain an element,
|
||||
// we want to disallow dropping
|
||||
|
||||
if (this === myDiagram.div) {
|
||||
var can = event.target;
|
||||
var pixelratio = window.PIXELRATIO;
|
||||
|
||||
// if the target is not the canvas, we may have trouble, so just quit:
|
||||
if (!(can instanceof HTMLCanvasElement)) return;
|
||||
|
||||
var bbox = can.getBoundingClientRect();
|
||||
var bbw = bbox.width;
|
||||
if (bbw === 0) bbw = 0.001;
|
||||
var bbh = bbox.height;
|
||||
if (bbh === 0) bbh = 0.001;
|
||||
var mx = event.clientX - bbox.left * ((can.width / pixelratio) / bbw);
|
||||
var my = event.clientY - bbox.top * ((can.height / pixelratio) / bbh);
|
||||
var point = myDiagram.transformViewToDoc(new go.Point(mx, my));
|
||||
var curnode = myDiagram.findPartAt(point, true);
|
||||
if (curnode instanceof go.Node) {
|
||||
highlight(curnode);
|
||||
} else {
|
||||
highlight(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.target.className === "dropzone") {
|
||||
// Disallow a drop by returning before a call to preventDefault:
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow a drop on everything else
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
div.addEventListener("dragleave", function(event) {
|
||||
// reset background of potential drop target
|
||||
if (event.target.className == "dropzone") {
|
||||
event.target.style.background = "";
|
||||
}
|
||||
highlight(null);
|
||||
}, false);
|
||||
|
||||
// handle the user option for removing dragged items from the Palette
|
||||
var remove = document.getElementById('remove');
|
||||
|
||||
div.addEventListener("drop", function(event) {
|
||||
// prevent default action
|
||||
// (open as link for some elements in some browsers)
|
||||
event.preventDefault();
|
||||
|
||||
// Dragging onto a Diagram
|
||||
if (this === myDiagram.div) {
|
||||
var can = event.target;
|
||||
var pixelratio = window.PIXELRATIO;
|
||||
|
||||
// if the target is not the canvas, we may have trouble, so just quit:
|
||||
if (!(can instanceof HTMLCanvasElement)) return;
|
||||
|
||||
var bbox = can.getBoundingClientRect();
|
||||
var bbw = bbox.width;
|
||||
if (bbw === 0) bbw = 0.001;
|
||||
var bbh = bbox.height;
|
||||
if (bbh === 0) bbh = 0.001;
|
||||
var mx = event.clientX - bbox.left * ((can.width / pixelratio) / bbw) - dragged.offsetX;
|
||||
var my = event.clientY - bbox.top * ((can.height / pixelratio) / bbh) - dragged.offsetY;
|
||||
var point = myDiagram.transformViewToDoc(new go.Point(mx, my));
|
||||
myDiagram.startTransaction('new node');
|
||||
myDiagram.model.addNodeData({
|
||||
location: point,
|
||||
text: event.dataTransfer.getData('text'),
|
||||
color: "lightyellow"
|
||||
});
|
||||
myDiagram.commitTransaction('new node');
|
||||
|
||||
// remove dragged element from its old location
|
||||
if (remove.checked) dragged.parentNode.removeChild(dragged);
|
||||
}
|
||||
|
||||
// If we were using drag data, we could get it here, ie:
|
||||
// var data = event.dataTransfer.getData('text');
|
||||
}, false);
|
||||
|
||||
// *********************************************************
|
||||
// Second, set up a GoJS Diagram
|
||||
// *********************************************************
|
||||
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram = $(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
"undoManager.isEnabled": true
|
||||
});
|
||||
window.PIXELRATIO = myDiagram.computePixelRatio(); // constant needed to determine mouse coordinates on the canvas
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding('location'),
|
||||
$(go.Shape, "Rectangle",
|
||||
{ fill: 'white' },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding("fill", "color"),
|
||||
// this binding changes the Shape.fill when Node.isHighlighted changes value
|
||||
new go.Binding("fill", "isHighlighted", function(h, shape) {
|
||||
if (h) return "red";
|
||||
var c = shape.part.data.color;
|
||||
return c ? c : "white";
|
||||
}).ofObject()), // binding source is Node.isHighlighted
|
||||
$(go.TextBlock,
|
||||
{ margin: 3, font: "bold 16px sans-serif", width: 140, textAlign: 'center' },
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding("text"))
|
||||
);
|
||||
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ text: "Alpha", color: "lightblue" },
|
||||
{ text: "Beta", color: "orange" },
|
||||
{ text: "Gamma", color: "lightgreen" },
|
||||
{ text: "Delta", color: "pink" }
|
||||
],
|
||||
[]);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
|
||||
<div id="sample">
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<div id="paletteZone" style="width: 160px; height: 400px; margin-right: 2px; background-color: lightblue; padding: 10px;">
|
||||
<div class="draggable" draggable="true">Water</div>
|
||||
<div class="draggable" draggable="true">Coffee</div>
|
||||
<div class="draggable" draggable="true">Tea</div>
|
||||
</div>
|
||||
<div id="myDiagramDiv" style="flex-grow: 1; height: 400px; border: solid 1px black"></div>
|
||||
</div>
|
||||
<input id="remove" type="checkbox" /><label for="remove">Remove HTML item after drag</label>
|
||||
<p>
|
||||
The "Palette" in this sample is not a Palette (or GoJS control) at all.
|
||||
It is a collection of HTML elements with draggable attributes using the
|
||||
<a href="https://developer.mozilla.org/en-US/docs/DragDrop/Drag_and_Drop">HTML Drag and Drop API</a>.
|
||||
</p>
|
||||
<p>
|
||||
This sample lets you drag these HTML elements onto the Diagram to create GoJS nodes.
|
||||
As the mouse passes over stationary nodes in the Diagram, they are highlighted.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML Interaction</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Show a GoJS Palette in a floating window and use an Inspector for changing the appearance of the selected node." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="../assets/css/jquery-ui.min.css" />
|
||||
<script src="../assets/js/jquery.min.js"></script>
|
||||
<script src="../assets/js/jquery-ui.min.js"></script>
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../extensions/Figures.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
|
||||
<link rel='stylesheet' href='../extensions/DataInspector.css' />
|
||||
<script src="../extensions/DataInspector.js"></script>
|
||||
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
// Note that we do not use $ here as an alias for go.GraphObject.make because we are using $ for jQuery
|
||||
var GO = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram =
|
||||
GO(go.Diagram, "myDiagramDiv",
|
||||
{ "undoManager.isEnabled": true });
|
||||
|
||||
// define several shared Brushes
|
||||
var fill1 = "rgb(105,210,231)"
|
||||
var brush1 = "rgb(65,180,181)";
|
||||
|
||||
var fill2 = "rgb(167,219,216)"
|
||||
var brush2 = "rgb(127,179,176)";
|
||||
|
||||
var fill3 = "rgb(224,228,204)"
|
||||
var brush3 = "rgb(184,188,164)";
|
||||
|
||||
var fill4 = "rgb(243,134,48)"
|
||||
var brush4 = "rgb(203,84,08)";
|
||||
|
||||
myDiagram.nodeTemplateMap.add("", // default category
|
||||
GO(go.Node, "Auto",
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
GO(go.Shape, "Ellipse",
|
||||
{ strokeWidth: 2, fill: fill1, name: "SHAPE" },
|
||||
new go.Binding("figure", "figure"),
|
||||
new go.Binding("fill", "fill"),
|
||||
new go.Binding("stroke", "stroke")
|
||||
),
|
||||
GO(go.TextBlock,
|
||||
{
|
||||
margin: 5,
|
||||
maxSize: new go.Size(200, NaN),
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
textAlign: "center",
|
||||
editable: true,
|
||||
font: "bold 9pt Helvetica, Arial, sans-serif",
|
||||
name: "TEXT"
|
||||
},
|
||||
new go.Binding("text", "text").makeTwoWay())));
|
||||
|
||||
// On selection changed, make sure infoDraggable will resize as necessary
|
||||
myDiagram.addDiagramListener("ChangedSelection", function(diagramEvent) {
|
||||
var idrag = document.getElementById("infoDraggable");
|
||||
idrag.style.width = "";
|
||||
idrag.style.height = "";
|
||||
});
|
||||
|
||||
|
||||
// initialize the Palette that is in a floating, draggable HTML container
|
||||
myPalette = new go.Palette("myPaletteDiv"); // must name or refer to the DIV HTML element
|
||||
myPalette.nodeTemplateMap = myDiagram.nodeTemplateMap;
|
||||
myPalette.model = new go.GraphLinksModel([
|
||||
{ text: "Lake", fill: fill1, stroke: brush1, figure: "Hexagon" },
|
||||
{ text: "Ocean", fill: fill2, stroke: brush2, figure: "Rectangle" },
|
||||
{ text: "Sand", fill: fill3, stroke: brush3, figure: "Diamond" },
|
||||
{ text: "Goldfish", fill: fill4, stroke: brush4, figure: "Octagon" }
|
||||
]);
|
||||
|
||||
myPalette.addDiagramListener("InitialLayoutCompleted", function(diagramEvent) {
|
||||
var pdrag = document.getElementById("paletteDraggable");
|
||||
var palette = diagramEvent.diagram;
|
||||
pdrag.style.width = palette.documentBounds.width + 28 + "px"; // account for padding/borders
|
||||
pdrag.style.height = palette.documentBounds.height + 38 + "px";
|
||||
});
|
||||
|
||||
|
||||
$(function() {
|
||||
$("#paletteDraggable").draggable({ handle: "#paletteDraggableHandle" }).resizable({
|
||||
// After resizing, perform another layout to fit everything in the palette's viewport
|
||||
stop: function() { myPalette.layoutDiagram(true); }
|
||||
});
|
||||
|
||||
$("#infoDraggable").draggable({ handle: "#infoDraggableHandle" });
|
||||
|
||||
|
||||
var inspector = new Inspector('myInfo', myDiagram,
|
||||
{
|
||||
properties: {
|
||||
// key would be automatically added for nodes, but we want to declare it read-only also:
|
||||
"key": { readOnly: true, show: Inspector.showIfPresent },
|
||||
// fill and stroke would be automatically added for nodes, but we want to declare it a color also:
|
||||
"fill": { show: Inspector.showIfPresent, type: 'color' },
|
||||
"stroke": { show: Inspector.showIfPresent, type: 'color' }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.draggable {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
border: 4px solid #BBB;
|
||||
border-radius: 4px;
|
||||
background-color: #F5F5F5;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.handle {
|
||||
background-color: lightblue;
|
||||
cursor: move;
|
||||
text-align: center;
|
||||
font: bold 12px sans-serif;
|
||||
}
|
||||
|
||||
#infoDraggable {
|
||||
font: 12px helvetica, sans-serif;
|
||||
min-width: 213px;
|
||||
}
|
||||
|
||||
#myInfo {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#myPaletteDiv {
|
||||
background-color: #F5F5F5;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
One simple way of making a div fill its space,
|
||||
with allowances for the title (top) and the resize handle (bottom)
|
||||
*/
|
||||
#paletteContainer {
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
|
||||
<div id="paletteDraggable" class="draggable" style="height: 300px;">
|
||||
<div id="paletteDraggableHandle" class="handle">Palette</div>
|
||||
<div id="paletteContainer">
|
||||
<div id="myPaletteDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="infoDraggable" class="draggable" style="display: inline-block; vertical-align: top; padding: 5px; top: 20px; left: 380px;">
|
||||
<div id="infoDraggableHandle" class="handle">Info</div>
|
||||
<div>
|
||||
<div id="myInfo"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: inline-block; vertical-align: top; width:400px">
|
||||
<div id="myDiagramDiv" style="background-color: whitesmoke; border: solid 1px black; height: 400px"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
This sample contains a draggable HTML element (using jQuery UI), which houses a GoJS Palette.
|
||||
</p>
|
||||
<p>
|
||||
A DIV to the right of the diagram houses the <a href="../extensions/DataInspector.html">GoJS Data inspector extension</a>,
|
||||
which displays some editable information about each Node.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>LightBox Style HTML Custom Context Menu</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Demonstrate context menus implemented in HTML covering the whole window." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
|
||||
<link rel='stylesheet' href='../extensions/LightBoxContextMenu.css' />
|
||||
<script src="../extensions/LightBoxContextMenu.js"></script>
|
||||
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
|
||||
<script id="code">
|
||||
|
||||
var myDiagram = null;
|
||||
|
||||
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
|
||||
|
||||
// Initialize the Diagram
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{ "undoManager.isEnabled": true });
|
||||
// define a simple Node template (but use the default Link template)
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ contextMenu: window.myHTMLLightBox }, // window.myHTMLLightBox is defined in extensions/LightBoxContextMenu.js
|
||||
$(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding("text", "key"))
|
||||
);
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: "Alpha", color: "lightblue" },
|
||||
{ key: "Beta", color: "orange" },
|
||||
{ key: "Gamma", color: "lightgreen" },
|
||||
{ key: "Delta", color: "pink" }
|
||||
],
|
||||
[
|
||||
{ from: "Alpha", to: "Beta" },
|
||||
{ from: "Alpha", to: "Gamma" },
|
||||
{ from: "Beta", to: "Beta" },
|
||||
{ from: "Gamma", to: "Delta" },
|
||||
{ from: "Delta", to: "Alpha" }
|
||||
]);
|
||||
|
||||
myDiagram.contextMenu = window.myHTMLLightBox; // window.myHTMLLightBox is defined in extensions/LightBoxContextMenu.js
|
||||
|
||||
} // end init
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div style="display: inline-block;">
|
||||
<div style="position: relative">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
</div>
|
||||
<div id="description">
|
||||
<p>
|
||||
This demonstrates the implementation of a custom HTML context menu using <a>HTMLInfo</a>.
|
||||
This sample is also a re-implementation of the built-in <a>ContextMenuTool.defaultTouchContextMenu</a>.
|
||||
</p>
|
||||
|
||||
<p>The implementation is contained in the files <a href="../extensions/LightBoxContextMenu.js">LightBoxContextMenu.js</a> and <a href="../extensions/LightBoxContextMenu.css">LightBoxContextMenu.css</a>. The JavaScript file exposes <code>window.myHTMLLightBox</code>, which is used in this file as the value of <code>myDiagram.contextMenu</code>.
|
||||
|
||||
<p>For a more regular HTML context menu implementation, see the <a href="customContextMenu.html">Custom Context Menu</a> sample.</p>
|
||||
<p>Right-click or tap-hold (mobile) on a Node to bring up a context menu.
|
||||
If you have a selection copied in the clipboard,
|
||||
you can bring up a context menu anywhere to paste.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Icons GoJS Sample</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Use SVG geometry path strings to create vector icons, rather than using images." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
|
||||
<script src="icons.js"></script> <!-- load SVG definitions for many icons in the "icons" variable -->
|
||||
<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
|
||||
|
||||
// a collection of colors
|
||||
var colors = {
|
||||
blue: "#2a6dc0",
|
||||
orange: "#ea2857",
|
||||
green: "#1cc1bc",
|
||||
gray: "#5b5b5b",
|
||||
white: "#F5F5F5"
|
||||
}
|
||||
|
||||
// The first Diagram showcases what the Nodes might look like "in action"
|
||||
myDiagram = $(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"undoManager.isEnabled": true,
|
||||
layout: $(go.TreeLayout)
|
||||
});
|
||||
|
||||
// "icons" is defined in icons.js
|
||||
|
||||
// A data binding conversion function. Given an icon name, return a Geometry.
|
||||
// This assumes that all icons want to be filled.
|
||||
// This caches the Geometry, because the Geometry may be shared by multiple Shapes.
|
||||
function geoFunc(geoname) {
|
||||
var geo = icons[geoname];
|
||||
if (geo === undefined) geo = icons["heart"]; // use this for an unknown icon name
|
||||
if (typeof geo === "string") {
|
||||
geo = icons[geoname] = go.Geometry.parse(geo, true); // fill each geometry
|
||||
}
|
||||
return geo;
|
||||
}
|
||||
|
||||
// Define a simple template consisting of the icon surrounded by a filled circle
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
$(go.Shape, "Circle",
|
||||
{ fill: "lightcoral", strokeWidth: 0, width: 65, height: 65 },
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.Shape,
|
||||
{ margin: 3, fill: colors["white"], strokeWidth: 0 },
|
||||
new go.Binding("geometry", "geo", geoFunc)),
|
||||
// Each node has a tooltip that reveals the name of its icon
|
||||
{
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
{ "Border.stroke": colors["gray"], "Border.strokeWidth": 2 },
|
||||
$(go.TextBlock, { margin: 8, stroke: colors["gray"], font: "bold 16px sans-serif" },
|
||||
new go.Binding("text", "geo")))
|
||||
}
|
||||
);
|
||||
|
||||
// Define a Link template that routes orthogonally, with no arrowhead
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ routing: go.Link.Orthogonal, corner: 5 },
|
||||
$(go.Shape, { strokeWidth: 3.5, stroke: colors["gray"] })); // the link shape
|
||||
|
||||
// Create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 1, geo: "file", color: colors["blue"] },
|
||||
{ key: 2, geo: "alarm", color: colors["orange"] },
|
||||
{ key: 3, geo: "lab", color: colors["blue"] },
|
||||
{ key: 4, geo: "earth", color: colors["blue"] },
|
||||
{ key: 5, geo: "heart", color: colors["green"] },
|
||||
{ key: 6, geo: "arrow-up-right", color: colors["blue"] },
|
||||
{ key: 7, geo: "html5", color: colors["orange"] },
|
||||
{ key: 8, geo: "twitter", color: colors["orange"] }
|
||||
],
|
||||
[
|
||||
{ from: 1, to: 2 },
|
||||
{ from: 1, to: 3 },
|
||||
{ from: 3, to: 4 },
|
||||
{ from: 4, to: 5 },
|
||||
{ from: 4, to: 6 },
|
||||
{ from: 3, to: 7 },
|
||||
{ from: 3, to: 8 }
|
||||
]);
|
||||
|
||||
|
||||
// The second Diagram showcases every icon in icons.js
|
||||
myDiagram2 = $(go.Diagram, "myDiagramDiv2",
|
||||
{ // share node templates between both Diagrams
|
||||
nodeTemplate: myDiagram.nodeTemplate,
|
||||
// simple grid layout
|
||||
layout: $(go.GridLayout)
|
||||
});
|
||||
|
||||
// Convert the icons collection into an Array of JavaScript objects
|
||||
var nodeArray = [];
|
||||
for (var k in icons) {
|
||||
nodeArray.push({ geo: k, color: colors["blue"] });
|
||||
}
|
||||
myDiagram2.model.nodeDataArray = nodeArray;
|
||||
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:450px; height:300px"></div>
|
||||
|
||||
<p>This sample shows several "icons" that were originally SVG paths, used as Shapes in GoJS.</p>
|
||||
<p>Above some icons are shown in a Tree-like Diagram, below a larger selection is shown.</p>
|
||||
<p>You can easily add your own shapes to GoJS by writing your own geometry strings, or by copying SVG path strings, as is done in this sample. The icons for this sample are defined in <a href="icons.js">icons.js</a>.</p>
|
||||
<p><a href="../intro/geometry.html">Read more about GoJS path syntax here.</a></p>
|
||||
|
||||
<div id="myDiagramDiv2" style="border: solid 1px black; width:700px; height:500px"></div>
|
||||
|
||||
<p>The icons in this sample are from a selection of free icons at <a href="https://icomoon.io" target = "blank">icomoon.io</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,160 @@
|
||||
// a selection of free icons from https://icomoon.io/
|
||||
// the following paths are all pure SVG path strings that GoJS parses as Geometry strings.
|
||||
|
||||
var icons = {
|
||||
"home":
|
||||
"M32 18.451l-16-12.42-16 12.42v-5.064l16-12.42 16 12.42zM28 18v12h-8v-8h-8v8h-8v-12l12-9z",
|
||||
|
||||
"droplet":
|
||||
"M27.998 19.797c-0-0.022-0.001-0.044-0.001-0.066-0.001-0.045-0.002-0.089-0.004-0.134-0.313-9.864-11.993-19.597-11.993-19.597s-11.68 9.733-11.993 19.598c-0.001 0.045-0.003 0.089-0.004 0.134-0 0.022-0.001 0.044-0.001 0.066-0.001 0.067-0.002 0.135-0.002 0.203 0 0.074 0.001 0.148 0.002 0.221 0 0.006 0 0.012 0 0.018 0.127 6.517 5.45 11.76 11.997 11.76s11.87-5.244 11.997-11.761c0-0.006 0-0.012 0-0.018 0.001-0.074 0.002-0.147 0.002-0.221 0-0.068-0.001-0.136-0.002-0.203zM23.998 20.148l-0 0.013c-0.041 2.103-0.892 4.073-2.395 5.548-1.504 1.477-3.495 2.291-5.604 2.291-0.389 0-0.775-0.028-1.154-0.082 4.346-2.589 7.257-7.335 7.257-12.76 0-0.608-0.037-1.207-0.108-1.796 1.259 2.311 1.939 4.462 2 6.363l0 0.005c0.001 0.030 0.002 0.059 0.002 0.089l0.001 0.045c0.001 0.046 0.002 0.091 0.002 0.137 0 0.049-0.001 0.099-0.002 0.148z",
|
||||
|
||||
"camera":
|
||||
"M9.5 19c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6.5-2.91-6.5-6.5-6.5-6.5 2.91-6.5 6.5zM30 8h-7c-0.5-2-1-4-3-4h-8c-2 0-2.5 2-3 4h-7c-1.1 0-2 0.9-2 2v18c0 1.1 0.9 2 2 2h28c1.1 0 2-0.9 2-2v-18c0-1.1-0.9-2-2-2zM16 27.875c-4.902 0-8.875-3.973-8.875-8.875s3.973-8.875 8.875-8.875c4.902 0 8.875 3.973 8.875 8.875s-3.973 8.875-8.875 8.875zM30 14h-4v-2h4v2z",
|
||||
|
||||
"pacman":
|
||||
"M30.148 5.588c-2.934-3.42-7.288-5.588-12.148-5.588-8.837 0-16 7.163-16 16s7.163 16 16 16c4.86 0 9.213-2.167 12.148-5.588l-10.148-10.412 10.148-10.412zM22 3.769c1.232 0 2.231 0.999 2.231 2.231s-0.999 2.231-2.231 2.231-2.231-0.999-2.231-2.231c0-1.232 0.999-2.231 2.231-2.231z",
|
||||
|
||||
"spades":
|
||||
"M25.549 10.88c-6.049-4.496-8.133-8.094-9.549-10.88v0c-0 0-0-0-0-0v0c-1.415 2.785-3.5 6.384-9.549 10.88-10.314 7.665-0.606 18.365 7.93 12.476-0.556 3.654-2.454 6.318-4.381 7.465v1.179h12.001v-1.179c-1.928-1.147-3.825-3.811-4.382-7.465 8.535 5.889 18.244-4.811 7.93-12.476z",
|
||||
|
||||
"clubs":
|
||||
"M24.588 12.274c-1.845 0-3.503 0.769-4.683 2.022-0.5 0.531-1.368 1.16-2.306 1.713 0.441-1.683 1.834-3.803 2.801-4.733 1.239-1.193 2-2.87 2-4.734 0-3.59-2.859-6.503-6.4-6.541-3.541 0.038-6.4 2.951-6.4 6.541 0 1.865 0.761 3.542 2 4.734 0.967 0.93 2.36 3.050 2.801 4.733-0.939-0.553-1.806-1.182-2.306-1.713-1.18-1.253-2.838-2.022-4.683-2.022-3.575 0-6.471 2.927-6.471 6.541s2.897 6.542 6.471 6.542c1.845 0 3.503-0.792 4.683-2.045 0.525-0.558 1.451-1.254 2.447-1.832-0.094 4.615-2.298 8.005-4.541 9.341v1.179h12v-1.179c-2.244-1.335-4.448-4.726-4.541-9.341 0.995 0.578 1.922 1.274 2.447 1.832 1.18 1.253 2.838 2.045 4.683 2.045 3.575 0 6.471-2.928 6.471-6.542s-2.897-6.541-6.471-6.541z",
|
||||
|
||||
"diamonds":
|
||||
"M16 0l-10 16 10 16 10-16z",
|
||||
|
||||
"connection":
|
||||
"M20 18c3.308 0 6.308 1.346 8.481 3.519l-2.827 2.827c-1.449-1.449-3.449-2.346-5.654-2.346s-4.206 0.897-5.654 2.346l-2.827-2.827c2.173-2.173 5.173-3.519 8.481-3.519zM5.858 15.858c3.777-3.777 8.8-5.858 14.142-5.858s10.365 2.080 14.142 5.858l-2.828 2.828c-3.022-3.022-7.040-4.686-11.314-4.686s-8.292 1.664-11.314 4.686l-2.828-2.828zM30.899 4.201c3.334 1.41 6.329 3.429 8.899 6v0l-2.828 2.828c-4.533-4.533-10.56-7.029-16.971-7.029s-12.438 2.496-16.971 7.029l-2.828-2.828c2.571-2.571 5.565-4.589 8.899-6 3.453-1.461 7.12-2.201 10.899-2.201s7.446 0.741 10.899 2.201zM18 28c0-1.105 0.895-2 2-2s2 0.895 2 2c0 1.105-0.895 2-2 2s-2-0.895-2-2z",
|
||||
|
||||
"feed":
|
||||
"M12 16c0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.209-1.791 4-4 4s-4-1.791-4-4zM20.761 7.204c3.12 1.692 5.239 4.997 5.239 8.796s-2.119 7.104-5.239 8.796c1.377-2.191 2.239-5.321 2.239-8.796s-0.862-6.605-2.239-8.796zM9 16c0 3.475 0.862 6.605 2.239 8.796-3.12-1.692-5.239-4.997-5.239-8.796s2.119-7.104 5.239-8.796c-1.377 2.191-2.239 5.321-2.239 8.796zM3 16c0 5.372 1.7 10.193 4.395 13.491-4.447-2.842-7.395-7.822-7.395-13.491s2.948-10.649 7.395-13.491c-2.695 3.298-4.395 8.119-4.395 13.491zM24.605 2.509c4.447 2.842 7.395 7.822 7.395 13.491s-2.948 10.649-7.395 13.491c2.695-3.298 4.395-8.119 4.395-13.491s-1.7-10.193-4.395-13.491z",
|
||||
|
||||
"file":
|
||||
"M27.879 5.879l-3.757-3.757c-1.167-1.167-3.471-2.121-5.121-2.121h-14c-1.65 0-3 1.35-3 3v26c0 1.65 1.35 3 3 3h22c1.65 0 3-1.35 3-3v-18c0-1.65-0.955-3.955-2.121-5.121zM20 4.236c0.069 0.025 0.139 0.053 0.211 0.082 0.564 0.234 0.956 0.505 1.082 0.631l3.757 3.757c0.126 0.126 0.397 0.517 0.631 1.082 0.030 0.072 0.057 0.143 0.082 0.211h-5.764v-5.764zM26 28h-20v-24h12v8h8v16zM8 16h16v2h-16zM8 20h16v2h-16zM8 24h16v2h-16z",
|
||||
|
||||
"history":
|
||||
"M18 2c7.732 0 14 6.268 14 14s-6.268 14-14 14v-3c2.938 0 5.701-1.144 7.778-3.222s3.222-4.84 3.222-7.778c0-2.938-1.144-5.701-3.222-7.778s-4.84-3.222-7.778-3.222c-2.938 0-5.701 1.144-7.778 3.222-1.598 1.598-2.643 3.601-3.041 5.778h5.819l-7 8-7-8h5.143c0.971-6.784 6.804-12 13.857-12zM24 14v4h-8v-10h4v6z",
|
||||
|
||||
"alarm":
|
||||
"M16 4c-7.732 0-14 6.268-14 14s6.268 14 14 14 14-6.268 14-14-6.268-14-14-14zM16 29.25c-6.213 0-11.25-5.037-11.25-11.25s5.037-11.25 11.25-11.25c6.213 0 11.25 5.037 11.25 11.25s-5.037 11.25-11.25 11.25zM29.212 8.974c0.501-0.877 0.788-1.892 0.788-2.974 0-3.314-2.686-6-6-6-1.932 0-3.65 0.913-4.747 2.331 4.121 0.851 7.663 3.287 9.96 6.643zM12.748 2.331c-1.097-1.418-2.816-2.331-4.748-2.331-3.314 0-6 2.686-6 6 0 1.082 0.287 2.098 0.788 2.974 2.297-3.356 5.838-5.792 9.96-6.643zM14 10h2v10h-2v-10zM16 18h6v2h-6v-2z",
|
||||
|
||||
"box-add":
|
||||
"M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM16 26l-10-8h6v-6h8v6h6l-10 8zM4.828 6l2-2h18.343l2 2h-22.343z",
|
||||
|
||||
"box-remove":
|
||||
"M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM20 20v6h-8v-6h-6l10-8 10 8h-6zM4.828 6l2-2h18.343l2 2h-22.343z",
|
||||
|
||||
"user":
|
||||
"M8 10c0-4.418 3.582-8 8-8s8 3.582 8 8c0 4.418-3.582 8-8 8s-8-3.582-8-8zM24 20h-16c-4.418 0-8 3.582-8 8v2h32v-2c0-4.418-3.582-8-8-8z",
|
||||
|
||||
"zoomin":
|
||||
"M31.008 27.231l-7.58-6.447c-0.784-0.705-1.622-1.029-2.299-0.998 1.789-2.096 2.87-4.815 2.87-7.787 0-6.627-5.373-12-12-12s-12 5.373-12 12c0 6.627 5.373 12 12 12 2.972 0 5.691-1.081 7.787-2.87-0.031 0.677 0.293 1.515 0.998 2.299l6.447 7.58c1.104 1.226 2.907 1.33 4.007 0.23s0.997-2.903-0.23-4.007zM12 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8zM14 6h-4v4h-4v4h4v4h4v-4h4v-4h-4z",
|
||||
|
||||
"zoomout":
|
||||
"M31.008 27.231l-7.58-6.447c-0.784-0.705-1.622-1.029-2.299-0.998 1.789-2.096 2.87-4.815 2.87-7.787 0-6.627-5.373-12-12-12s-12 5.373-12 12c0 6.627 5.373 12 12 12 2.972 0 5.691-1.081 7.787-2.87-0.031 0.677 0.293 1.515 0.998 2.299l6.447 7.58c1.104 1.226 2.907 1.33 4.007 0.23s0.997-2.903-0.23-4.007zM12 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8zM6 10h12v4h-12z",
|
||||
|
||||
"cog":
|
||||
"M29.181 19.070c-1.679-2.908-0.669-6.634 2.255-8.328l-3.145-5.447c-0.898 0.527-1.943 0.829-3.058 0.829-3.361 0-6.085-2.742-6.085-6.125h-6.289c0.008 1.044-0.252 2.103-0.811 3.070-1.679 2.908-5.411 3.897-8.339 2.211l-3.144 5.447c0.905 0.515 1.689 1.268 2.246 2.234 1.676 2.903 0.672 6.623-2.241 8.319l3.145 5.447c0.895-0.522 1.935-0.82 3.044-0.82 3.35 0 6.067 2.725 6.084 6.092h6.289c-0.003-1.034 0.259-2.080 0.811-3.038 1.676-2.903 5.399-3.894 8.325-2.219l3.145-5.447c-0.899-0.515-1.678-1.266-2.232-2.226zM16 22.479c-3.578 0-6.479-2.901-6.479-6.479s2.901-6.479 6.479-6.479c3.578 0 6.479 2.901 6.479 6.479s-2.901 6.479-6.479 6.479z",
|
||||
|
||||
"cogs":
|
||||
"M11.366 22.564l1.291-1.807-1.414-1.414-1.807 1.291c-0.335-0.187-0.694-0.337-1.071-0.444l-0.365-2.19h-2l-0.365 2.19c-0.377 0.107-0.736 0.256-1.071 0.444l-1.807-1.291-1.414 1.414 1.291 1.807c-0.187 0.335-0.337 0.694-0.443 1.071l-2.19 0.365v2l2.19 0.365c0.107 0.377 0.256 0.736 0.444 1.071l-1.291 1.807 1.414 1.414 1.807-1.291c0.335 0.187 0.694 0.337 1.071 0.444l0.365 2.19h2l0.365-2.19c0.377-0.107 0.736-0.256 1.071-0.444l1.807 1.291 1.414-1.414-1.291-1.807c0.187-0.335 0.337-0.694 0.444-1.071l2.19-0.365v-2l-2.19-0.365c-0.107-0.377-0.256-0.736-0.444-1.071zM7 27c-1.105 0-2-0.895-2-2s0.895-2 2-2 2 0.895 2 2-0.895 2-2 2zM32 12v-2l-2.106-0.383c-0.039-0.251-0.088-0.499-0.148-0.743l1.799-1.159-0.765-1.848-2.092 0.452c-0.132-0.216-0.273-0.426-0.422-0.629l1.219-1.761-1.414-1.414-1.761 1.219c-0.203-0.149-0.413-0.29-0.629-0.422l0.452-2.092-1.848-0.765-1.159 1.799c-0.244-0.059-0.492-0.109-0.743-0.148l-0.383-2.106h-2l-0.383 2.106c-0.251 0.039-0.499 0.088-0.743 0.148l-1.159-1.799-1.848 0.765 0.452 2.092c-0.216 0.132-0.426 0.273-0.629 0.422l-1.761-1.219-1.414 1.414 1.219 1.761c-0.149 0.203-0.29 0.413-0.422 0.629l-2.092-0.452-0.765 1.848 1.799 1.159c-0.059 0.244-0.109 0.492-0.148 0.743l-2.106 0.383v2l2.106 0.383c0.039 0.251 0.088 0.499 0.148 0.743l-1.799 1.159 0.765 1.848 2.092-0.452c0.132 0.216 0.273 0.426 0.422 0.629l-1.219 1.761 1.414 1.414 1.761-1.219c0.203 0.149 0.413 0.29 0.629 0.422l-0.452 2.092 1.848 0.765 1.159-1.799c0.244 0.059 0.492 0.109 0.743 0.148l0.383 2.106h2l0.383-2.106c0.251-0.039 0.499-0.088 0.743-0.148l1.159 1.799 1.848-0.765-0.452-2.092c0.216-0.132 0.426-0.273 0.629-0.422l1.761 1.219 1.414-1.414-1.219-1.761c0.149-0.203 0.29-0.413 0.422-0.629l2.092 0.452 0.765-1.848-1.799-1.159c0.059-0.244 0.109-0.492 0.148-0.743l2.106-0.383zM21 15.35c-2.402 0-4.35-1.948-4.35-4.35s1.948-4.35 4.35-4.35 4.35 1.948 4.35 4.35c0 2.402-1.948 4.35-4.35 4.35z",
|
||||
|
||||
"stats":
|
||||
"M4 28h28v4h-32v-32h4zM9 26c-1.657 0-3-1.343-3-3s1.343-3 3-3c0.088 0 0.176 0.005 0.262 0.012l3.225-5.375c-0.307-0.471-0.487-1.033-0.487-1.638 0-1.657 1.343-3 3-3s3 1.343 3 3c0 0.604-0.179 1.167-0.487 1.638l3.225 5.375c0.086-0.007 0.174-0.012 0.262-0.012 0.067 0 0.133 0.003 0.198 0.007l5.324-9.316c-0.329-0.482-0.522-1.064-0.522-1.691 0-1.657 1.343-3 3-3s3 1.343 3 3c0 1.657-1.343 3-3 3-0.067 0-0.133-0.003-0.198-0.007l-5.324 9.316c0.329 0.481 0.522 1.064 0.522 1.691 0 1.657-1.343 3-3 3s-3-1.343-3-3c0-0.604 0.179-1.167 0.487-1.638l-3.225-5.375c-0.086 0.007-0.174 0.012-0.262 0.012s-0.176-0.005-0.262-0.012l-3.225 5.375c0.307 0.471 0.487 1.033 0.487 1.637 0 1.657-1.343 3-3 3z",
|
||||
|
||||
"bars":
|
||||
"M0 26h32v4h-32zM4 18h4v6h-4zM10 10h4v14h-4zM16 16h4v8h-4zM22 4h4v20h-4z",
|
||||
|
||||
"bars2":
|
||||
"M9 12h-6c-0.55 0-1 0.45-1 1v18c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1v-18c0-0.55-0.45-1-1-1zM9 30h-6v-8h6v8zM19 8h-6c-0.55 0-1 0.45-1 1v22c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1v-22c0-0.55-0.45-1-1-1zM19 30h-6v-10h6v10zM29 4h-6c-0.55 0-1 0.45-1 1v26c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1v-26c0-0.55-0.45-1-1-1zM29 30h-6v-12h6v12z",
|
||||
|
||||
"lab":
|
||||
"M29.884 25.14l-9.884-16.47v-6.671h1c0.55 0 1-0.45 1-1s-0.45-1-1-1h-10c-0.55 0-1 0.45-1 1s0.45 1 1 1h1v6.671l-9.884 16.47c-2.264 3.773-0.516 6.86 3.884 6.86h20c4.4 0 6.148-3.087 3.884-6.86zM7.532 20l6.468-10.779v-7.221h4v7.221l6.468 10.779h-16.935z",
|
||||
|
||||
"remove":
|
||||
"M6 32h20l2-22h-24zM20 4v-4h-8v4h-10v6l2-2h24l2 2v-6h-10zM18 4h-4v-2h4v2z",
|
||||
|
||||
"switch":
|
||||
"M20 4.581v4.249c1.131 0.494 2.172 1.2 3.071 2.099 1.889 1.889 2.929 4.4 2.929 7.071s-1.040 5.182-2.929 7.071c-1.889 1.889-4.4 2.929-7.071 2.929s-5.182-1.040-7.071-2.929c-1.889-1.889-2.929-4.4-2.929-7.071s1.040-5.182 2.929-7.071c0.899-0.899 1.94-1.606 3.071-2.099v-4.249c-5.783 1.721-10 7.077-10 13.419 0 7.732 6.268 14 14 14s14-6.268 14-14c0-6.342-4.217-11.698-10-13.419zM14 0h4v16h-4z",
|
||||
|
||||
"tree":
|
||||
"M30.5 24h-0.5v-6.5c0-1.93-1.57-3.5-3.5-3.5h-8.5v-4h0.5c0.825 0 1.5-0.675 1.5-1.5v-5c0-0.825-0.675-1.5-1.5-1.5h-5c-0.825 0-1.5 0.675-1.5 1.5v5c0 0.825 0.675 1.5 1.5 1.5h0.5v4h-8.5c-1.93 0-3.5 1.57-3.5 3.5v6.5h-0.5c-0.825 0-1.5 0.675-1.5 1.5v5c0 0.825 0.675 1.5 1.5 1.5h5c0.825 0 1.5-0.675 1.5-1.5v-5c0-0.825-0.675-1.5-1.5-1.5h-0.5v-6h8v6h-0.5c-0.825 0-1.5 0.675-1.5 1.5v5c0 0.825 0.675 1.5 1.5 1.5h5c0.825 0 1.5-0.675 1.5-1.5v-5c0-0.825-0.675-1.5-1.5-1.5h-0.5v-6h8v6h-0.5c-0.825 0-1.5 0.675-1.5 1.5v5c0 0.825 0.675 1.5 1.5 1.5h5c0.825 0 1.5-0.675 1.5-1.5v-5c0-0.825-0.675-1.5-1.5-1.5zM6 30h-4v-4h4v4zM18 30h-4v-4h4v4zM14 8v-4h4v4h-4zM30 30h-4v-4h4v4z",
|
||||
|
||||
"cloud":
|
||||
"M32 20.548c0-2.565-1.771-4.716-4.156-5.296-0.101-4.022-3.389-7.252-7.433-7.252-2.369 0-4.477 1.109-5.839 2.835-0.764-0.987-1.959-1.624-3.303-1.624-2.307 0-4.176 1.871-4.176 4.179 0 0.201 0.015 0.399 0.043 0.592-0.351-0.063-0.711-0.098-1.080-0.098-3.344-0-6.054 2.712-6.054 6.058s2.71 6.058 6.054 6.058l20.508-0c3.004-0.006 5.438-2.444 5.438-5.451z",
|
||||
|
||||
"download":
|
||||
"M23 14l-8 8-8-8h5v-12h6v12zM15 22h-15v8h30v-8h-15zM28 26h-4v-2h4v2z",
|
||||
|
||||
"earth":
|
||||
"M27.314 4.686c3.022 3.022 4.686 7.040 4.686 11.314s-1.664 8.292-4.686 11.314c-3.022 3.022-7.040 4.686-11.314 4.686s-8.292-1.664-11.314-4.686c-3.022-3.022-4.686-7.040-4.686-11.314s1.664-8.292 4.686-11.314c3.022-3.022 7.040-4.686 11.314-4.686s8.292 1.664 11.314 4.686zM25.899 25.9c1.971-1.971 3.281-4.425 3.821-7.096-0.421 0.62-0.824 0.85-1.073-0.538-0.257-2.262-2.335-0.817-3.641-1.621-1.375 0.927-4.466-1.802-3.941 1.276 0.81 1.388 4.375-1.858 2.598 1.079-1.134 2.050-4.145 6.592-3.753 8.946 0.049 3.43-3.504 0.715-4.729-0.422-0.824-2.279-0.281-6.262-2.434-7.378-2.338-0.102-4.344-0.314-5.25-2.927-0.545-1.87 0.58-4.653 2.584-5.083 2.933-1.843 3.98 2.158 6.731 2.232 0.854-0.894 3.182-1.178 3.375-2.18-1.805-0.318 2.29-1.517-0.173-2.199-1.358 0.16-2.234 1.409-1.512 2.467-2.632 0.614-2.717-3.809-5.247-2.414-0.064 2.206-4.132 0.715-1.407 0.268 0.936-0.409-1.527-1.594-0.196-1.379 0.654-0.036 2.854-0.807 2.259-1.325 1.225-0.761 2.255 1.822 3.454-0.059 0.866-1.446-0.363-1.713-1.448-0.98-0.612-0.685 1.080-2.165 2.573-2.804 0.497-0.213 0.973-0.329 1.336-0.296 0.752 0.868 2.142 1.019 2.215-0.104-1.862-0.892-3.915-1.363-6.040-1.363-3.051 0-5.952 0.969-8.353 2.762 0.645 0.296 1.012 0.664 0.39 1.134-0.483 1.439-2.443 3.371-4.163 3.098-0.893 1.54-1.482 3.238-1.733 5.017 1.441 0.477 1.773 1.42 1.464 1.736-0.734 0.64-1.185 1.548-1.418 2.541 0.469 2.87 1.818 5.515 3.915 7.612 2.644 2.644 6.16 4.1 9.899 4.1s7.255-1.456 9.899-4.1z",
|
||||
|
||||
"heart":
|
||||
"M32 11.192c0 2.699-1.163 5.126-3.015 6.808h0.015l-10 10c-1 1-2 2-3 2s-2-1-3-2l-9.985-10c-1.852-1.682-3.015-4.109-3.015-6.808 0-5.077 4.116-9.192 9.192-9.192 2.699 0 5.126 1.163 6.808 3.015 1.682-1.852 4.109-3.015 6.808-3.015 5.077 0 9.192 4.116 9.192 9.192z",
|
||||
|
||||
"smiley":
|
||||
"M16 32c8.837 0 16-7.163 16-16s-7.163-16-16-16-16 7.163-16 16 7.163 16 16 16zM16 3c7.18 0 13 5.82 13 13s-5.82 13-13 13-13-5.82-13-13 5.82-13 13-13zM8 10c0-1.105 0.895-2 2-2s2 0.895 2 2c0 1.105-0.895 2-2 2s-2-0.895-2-2zM20 10c0-1.105 0.895-2 2-2s2 0.895 2 2c0 1.105-0.895 2-2 2s-2-0.895-2-2zM22.003 19.602l2.573 1.544c-1.749 2.908-4.935 4.855-8.576 4.855s-6.827-1.946-8.576-4.855l2.573-1.544c1.224 2.036 3.454 3.398 6.003 3.398s4.779-1.362 6.003-3.398z",
|
||||
|
||||
"close":
|
||||
"M31.708 25.708c-0-0-0-0-0-0l-9.708-9.708 9.708-9.708c0-0 0-0 0-0 0.105-0.105 0.18-0.227 0.229-0.357 0.133-0.356 0.057-0.771-0.229-1.057l-4.586-4.586c-0.286-0.286-0.702-0.361-1.057-0.229-0.13 0.048-0.252 0.124-0.357 0.228 0 0-0 0-0 0l-9.708 9.708-9.708-9.708c-0-0-0-0-0-0-0.105-0.104-0.227-0.18-0.357-0.228-0.356-0.133-0.771-0.057-1.057 0.229l-4.586 4.586c-0.286 0.286-0.361 0.702-0.229 1.057 0.049 0.13 0.124 0.252 0.229 0.357 0 0 0 0 0 0l9.708 9.708-9.708 9.708c-0 0-0 0-0 0-0.104 0.105-0.18 0.227-0.229 0.357-0.133 0.355-0.057 0.771 0.229 1.057l4.586 4.586c0.286 0.286 0.702 0.361 1.057 0.229 0.13-0.049 0.252-0.124 0.357-0.229 0-0 0-0 0-0l9.708-9.708 9.708 9.708c0 0 0 0 0 0 0.105 0.105 0.227 0.18 0.357 0.229 0.356 0.133 0.771 0.057 1.057-0.229l4.586-4.586c0.286-0.286 0.362-0.702 0.229-1.057-0.049-0.13-0.124-0.252-0.229-0.357z",
|
||||
|
||||
"checkmark":
|
||||
"M27 4l-15 15-7-7-5 5 12 12 20-20z",
|
||||
|
||||
"play":
|
||||
"M6 4l20 12-20 12z",
|
||||
|
||||
"pause":
|
||||
"M4 4h10v24h-10zM18 4h10v24h-10z",
|
||||
|
||||
"stop":
|
||||
"M4 4h24v24h-24z",
|
||||
|
||||
"shuffle":
|
||||
"M32 8l-8-8v6c-4.087 0-7.211 0.975-9.552 2.982-0.164 0.141-0.321 0.284-0.474 0.43 0.86 1.193 1.522 2.422 2.118 3.597 1.51-1.825 3.689-3.008 7.907-3.008v12c-6.764 0-8.285-3.043-10.211-6.894-1.072-2.144-2.181-4.361-4.237-6.124-2.341-2.006-5.465-2.982-9.552-2.982v4c6.764 0 8.285 3.043 10.211 6.894 1.072 2.144 2.181 4.361 4.237 6.124 2.341 2.006 5.465 2.982 9.552 2.982v6l8-8-8-8 8-8zM0 22v4c4.087 0 7.211-0.975 9.552-2.982 0.164-0.141 0.321-0.284 0.474-0.43-0.86-1.193-1.522-2.422-2.118-3.597-1.51 1.825-3.689 3.009-7.907 3.009z",
|
||||
|
||||
"arrow-up-right":
|
||||
"M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13zM12 8c-1.105 0-2 0.895-2 2s0.895 2 2 2h5.172l-8.586 8.586c-0.781 0.781-0.781 2.047 0 2.829 0.39 0.39 0.902 0.586 1.414 0.586s1.024-0.195 1.414-0.586l8.586-8.586v5.172c0 1.105 0.895 2 2 2s2-0.895 2-2v-12h-12z",
|
||||
|
||||
"googleplus":
|
||||
"M0.025 27.177c-0.008-0.079-0.014-0.158-0.018-0.238 0.004 0.080 0.011 0.159 0.018 0.238zM7.372 17.661c2.875 0.086 4.804-2.897 4.308-6.662s-3.231-6.787-6.106-6.873c-2.876-0.085-4.804 2.796-4.308 6.562 0.496 3.765 3.23 6.887 6.106 6.973zM32 8v-2.666c0-2.934-2.399-5.334-5.333-5.334h-21.333c-2.884 0-5.25 2.32-5.33 5.185 1.824-1.606 4.354-2.947 6.965-2.947 2.791 0 11.164 0 11.164 0l-2.498 2.113h-3.54c2.348 0.9 3.599 3.629 3.599 6.429 0 2.351-1.307 4.374-3.153 5.812-1.801 1.403-2.143 1.991-2.143 3.184 0 1.018 1.93 2.75 2.938 3.462 2.949 2.079 3.904 4.010 3.904 7.233 0 0.513-0.064 1.026-0.19 1.53h9.617c2.934 0 5.333-2.398 5.333-5.334v-16.666h-6v6h-2v-6h-6v-2h6v-6h2v6h6zM5.809 23.936c0.675 0 1.294-0.018 1.936-0.018-0.848-0.823-1.52-1.831-1.52-3.074 0-0.738 0.236-1.448 0.567-2.079-0.337 0.024-0.681 0.031-1.035 0.031-2.324 0-4.297-0.752-5.756-1.995v2.101l0 6.304c1.67-0.793 3.653-1.269 5.809-1.269zM0.107 27.727c-0.035-0.171-0.061-0.344-0.079-0.52 0.018 0.176 0.045 0.349 0.079 0.52zM14.233 29.776c-0.471-1.838-2.139-2.749-4.465-4.361-0.846-0.273-1.778-0.434-2.778-0.444-2.801-0.030-5.41 1.092-6.882 2.762 0.498 2.428 2.657 4.267 5.226 4.267h8.951c0.057-0.348 0.084-0.707 0.084-1.076 0-0.392-0.048-0.775-0.137-1.148z",
|
||||
|
||||
"facebook":
|
||||
"M17.996 32h-5.996v-16h-4v-5.514l4-0.002-0.007-3.248c0-4.498 1.22-7.236 6.519-7.236h4.412v5.515h-2.757c-2.064 0-2.163 0.771-2.163 2.209l-0.008 2.76h4.959l-0.584 5.514-4.37 0.002-0.004 16z",
|
||||
|
||||
"twitter":
|
||||
"M26.667 0h-21.333c-2.934 0-5.334 2.4-5.334 5.334v21.332c0 2.936 2.4 5.334 5.334 5.334h21.333c2.934 0 5.333-2.398 5.333-5.334v-21.332c0-2.934-2.399-5.334-5.333-5.334zM26.189 10.682c0.010 0.229 0.015 0.46 0.015 0.692 0 7.069-5.288 15.221-14.958 15.221-2.969 0-5.732-0.886-8.059-2.404 0.411 0.050 0.83 0.075 1.254 0.075 2.463 0 4.73-0.855 6.529-2.29-2.3-0.043-4.242-1.59-4.911-3.715 0.321 0.063 0.65 0.096 0.989 0.096 0.479 0 0.944-0.066 1.385-0.188-2.405-0.492-4.217-2.654-4.217-5.245 0-0.023 0-0.045 0-0.067 0.709 0.401 1.519 0.641 2.381 0.669-1.411-0.959-2.339-2.597-2.339-4.453 0-0.98 0.259-1.899 0.712-2.689 2.593 3.237 6.467 5.366 10.836 5.589-0.090-0.392-0.136-0.8-0.136-1.219 0-2.954 2.354-5.349 5.257-5.349 1.512 0 2.879 0.65 3.838 1.689 1.198-0.24 2.323-0.685 3.338-1.298-0.393 1.249-1.226 2.298-2.311 2.96 1.063-0.129 2.077-0.417 3.019-0.842-0.705 1.073-1.596 2.015-2.623 2.769z",
|
||||
|
||||
"github":
|
||||
"M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM25.502 25.502c-1.235 1.235-2.672 2.204-4.272 2.881-0.406 0.172-0.819 0.323-1.238 0.453v-2.398c0-1.26-0.432-2.188-1.297-2.781 0.542-0.052 1.039-0.125 1.492-0.219s0.932-0.229 1.438-0.406 0.958-0.388 1.359-0.633 0.786-0.563 1.156-0.953 0.68-0.833 0.93-1.328 0.448-1.089 0.594-1.781 0.219-1.456 0.219-2.289c0-1.615-0.526-2.99-1.578-4.125 0.479-1.25 0.427-2.609-0.156-4.078l-0.391-0.047c-0.271-0.031-0.758 0.083-1.461 0.344s-1.492 0.688-2.367 1.281c-1.24-0.344-2.526-0.516-3.859-0.516-1.344 0-2.625 0.172-3.844 0.516-0.552-0.375-1.075-0.685-1.57-0.93s-0.891-0.411-1.188-0.5-0.573-0.143-0.828-0.164-0.419-0.026-0.492-0.016-0.125 0.021-0.156 0.031c-0.583 1.479-0.635 2.839-0.156 4.078-1.052 1.135-1.578 2.51-1.578 4.125 0 0.833 0.073 1.596 0.219 2.289s0.344 1.286 0.594 1.781 0.56 0.938 0.93 1.328 0.755 0.708 1.156 0.953 0.854 0.456 1.359 0.633 0.984 0.313 1.438 0.406 0.95 0.167 1.492 0.219c-0.854 0.583-1.281 1.51-1.281 2.781v2.445c-0.472-0.14-0.937-0.306-1.394-0.5-1.6-0.677-3.037-1.646-4.272-2.881s-2.204-2.672-2.881-4.272c-0.7-1.655-1.055-3.414-1.055-5.23s0.355-3.575 1.055-5.23c0.677-1.6 1.646-3.037 2.881-4.272s2.672-2.204 4.272-2.881c1.655-0.7 3.415-1.055 5.23-1.055s3.575 0.355 5.23 1.055c1.6 0.677 3.037 1.646 4.272 2.881s2.204 2.672 2.881 4.272c0.7 1.655 1.055 3.415 1.055 5.23s-0.355 3.575-1.055 5.23c-0.677 1.6-1.646 3.037-2.881 4.272z",
|
||||
|
||||
"tumblr":
|
||||
"M26.668 0h-21.334c-2.934 0-5.334 2.4-5.334 5.334v21.332c0 2.936 2.4 5.334 5.334 5.334h21.334c2.933 0 5.332-2.398 5.332-5.334v-21.332c-0-2.933-2.399-5.334-5.332-5.334zM22.866 25.771c-0.942 0.443-1.798 0.756-2.563 0.936-0.765 0.178-1.593 0.267-2.481 0.267-1.010 0-1.605-0.127-2.381-0.381-0.775-0.256-1.438-0.621-1.984-1.090-0.549-0.473-0.928-0.975-1.14-1.506s-0.317-1.303-0.317-2.313v-7.744h-3v-3.127c0.867-0.281 1.873-0.685 2.49-1.211 0.62-0.527 1.116-1.158 1.49-1.896 0.375-0.736 0.633-1.676 0.774-2.815h3.141v5.108h5.105v3.941h-5.106v5.662c0 1.281-0.017 2.020 0.119 2.383 0.135 0.361 0.473 0.736 0.841 0.953 0.489 0.293 1.047 0.439 1.676 0.439 1.118 0 2.231-0.363 3.336-1.090v3.482z",
|
||||
|
||||
"apple":
|
||||
"M24.734 17.003c-0.040-4.053 3.305-5.996 3.454-6.093-1.88-2.751-4.808-3.127-5.851-3.171-2.492-0.252-4.862 1.467-6.127 1.467-1.261 0-3.213-1.43-5.28-1.392-2.716 0.040-5.221 1.579-6.619 4.012-2.822 4.897-0.723 12.151 2.028 16.123 1.344 1.944 2.947 4.127 5.051 4.049 2.026-0.081 2.793-1.311 5.242-1.311s3.138 1.311 5.283 1.271c2.18-0.041 3.562-1.981 4.897-3.931 1.543-2.255 2.179-4.439 2.216-4.551-0.048-0.022-4.252-1.632-4.294-6.473zM20.705 5.11c1.117-1.355 1.871-3.235 1.665-5.11-1.609 0.066-3.559 1.072-4.713 2.423-1.036 1.199-1.942 3.113-1.699 4.951 1.796 0.14 3.629-0.913 4.747-2.264z",
|
||||
|
||||
"android":
|
||||
"M27 10c-1.1 0-2 0.9-2 2v8c0 1.1 0.9 2 2 2s2-0.9 2-2v-8c0-1.1-0.9-2-2-2zM3 10c-1.1 0-2 0.9-2 2v8c0 1.1 0.9 2 2 2s2-0.9 2-2v-8c0-1.1-0.9-2-2-2zM6 23c0 1.657 1.343 3 3 3h1v4c0 1.1 0.9 2 2 2s2-0.9 2-2v-4h2v4c0 1.1 0.9 2 2 2s2-0.9 2-2v-4h1c1.657 0 3-1.343 3-3v-11h-18v11zM18.706 2.797l1.266-2.431c0.064-0.122 0.016-0.274-0.106-0.337s-0.274-0.016-0.337 0.106l-1.285 2.468c-1.006-0.389-2.1-0.603-3.244-0.603s-2.237 0.214-3.244 0.603l-1.285-2.468c-0.063-0.122-0.215-0.17-0.337-0.106s-0.17 0.215-0.106 0.337l1.266 2.432c-2.832 1.282-4.883 3.987-5.238 7.203h17.889c-0.355-3.216-2.406-5.921-5.238-7.203zM11 8.45c-0.801 0-1.45-0.649-1.45-1.45s0.649-1.45 1.45-1.45 1.45 0.649 1.45 1.45c-0 0.801-0.649 1.45-1.45 1.45zM19 8.45c-0.801 0-1.45-0.649-1.45-1.45s0.649-1.45 1.45-1.45 1.45 0.649 1.45 1.45c0 0.801-0.649 1.45-1.45 1.45z",
|
||||
|
||||
"windows8":
|
||||
"M0.011 16l-0.011-9.752 12-1.63v11.382zM14 4.328l15.996-2.328v14h-15.996zM30 18l-0.004 14-15.996-2.25v-11.75zM12 29.495l-11.99-1.644-0.001-9.851h11.991z",
|
||||
|
||||
"html5":
|
||||
"M1.892 0l2.567 28.801 11.524 3.199 11.554-3.204 2.572-28.796h-28.216zM24.52 9.42h-13.517l0.322 3.617h12.874l-0.97 10.844-7.245 2.008-7.237-2.008-0.495-5.547h3.547l0.252 2.82 3.933 1.060 0.009-0.002 3.935-1.062 0.408-4.58h-12.242l-0.953-10.681h17.694l-0.316 3.532z",
|
||||
|
||||
"chrome":
|
||||
"M8.071 13.954l-4.579-7.931c2.932-3.671 7.445-6.023 12.508-6.023 5.857 0 10.978 3.148 13.767 7.844h-13.055c-0.235-0.020-0.472-0.031-0.711-0.031-3.809 0-7.018 2.614-7.929 6.142zM21.728 10.156h9.171c0.711 1.81 1.101 3.781 1.101 5.844 0 8.776-7.066 15.9-15.818 15.998l6.544-11.334c0.921-1.324 1.462-2.932 1.462-4.664 0-2.287-0.943-4.357-2.459-5.844zM10.188 16c0-3.205 2.607-5.813 5.813-5.813s5.813 2.607 5.813 5.813c0 3.205-2.608 5.813-5.813 5.813s-5.813-2.608-5.813-5.813zM18.193 23.889l-4.581 7.934c-7.704-1.153-13.613-7.797-13.613-15.822 0-2.851 0.746-5.526 2.053-7.845l6.532 11.314c1.308 2.785 4.14 4.718 7.415 4.718 0.759 0 1.495-0.104 2.193-0.299z",
|
||||
|
||||
"firefox":
|
||||
"M31.954 10.442l-0.371 2.377c0 0-0.53-4.402-1.179-6.047-0.995-2.521-1.438-2.501-1.441-2.498 0.667 1.694 0.546 2.604 0.546 2.604s-1.181-3.219-4.303-4.243c-3.459-1.134-5.33-0.824-5.547-0.765-0.033-0-0.064-0-0.095-0 0.026 0.002 0.050 0.005 0.076 0.007-0.001 0.001-0.003 0.001-0.003 0.002 0.014 0.017 3.822 0.666 4.497 1.594 0 0-1.617 0-3.227 0.464-0.073 0.021 5.923 0.749 7.148 6.74 0 0-0.657-1.371-1.47-1.604 0.535 1.626 0.397 4.712-0.112 6.245-0.066 0.197-0.133-0.853-1.135-1.305 0.321 2.301-0.019 5.952-1.616 6.957-0.124 0.078 1.001-3.603 0.226-2.18-4.46 6.838-9.731 3.155-12.101 1.535 1.215 0.264 3.52-0.041 4.541-0.8 0.001-0.001 0.002-0.002 0.004-0.003 1.108-0.758 1.765-1.311 2.354-1.18s0.982-0.46 0.524-0.985c-0.459-0.526-1.572-1.249-3.079-0.855-1.063 0.278-2.379 1.454-4.389 0.264-1.543-0.914-1.688-1.673-1.702-2.199 0.038-0.186 0.086-0.361 0.143-0.52 0.178-0.496 0.716-0.646 1.015-0.764 0.508 0.087 0.946 0.246 1.405 0.481 0.006-0.153 0.008-0.356-0.001-0.586 0.044-0.088 0.017-0.352-0.054-0.674-0.041-0.322-0.107-0.655-0.211-0.959 0-0 0.001-0 0.001-0 0.002-0.001 0.003-0.001 0.005-0.002s0.005-0.004 0.007-0.006c0-0.001 0.001-0.001 0.001-0.002 0.003-0.004 0.005-0.008 0.006-0.015 0.032-0.144 0.376-0.423 0.804-0.722 0.383-0.268 0.834-0.553 1.19-0.774 0.314-0.195 0.554-0.34 0.605-0.378 0.019-0.015 0.042-0.032 0.068-0.051 0.005-0.004 0.009-0.007 0.014-0.011 0.003-0.002 0.006-0.005 0.009-0.007 0.169-0.135 0.421-0.389 0.474-0.924 0-0.001 0-0.002 0-0.004 0.002-0.016 0.003-0.032 0.004-0.048 0.001-0.011 0.002-0.023 0.002-0.034 0-0.009 0.001-0.018 0.001-0.027 0.001-0.021 0.002-0.043 0.002-0.065 0-0.001 0-0.002 0-0.004 0.001-0.052-0-0.106-0.003-0.163-0.002-0.032-0.004-0.060-0.009-0.086-0-0.001-0.001-0.003-0.001-0.004-0.001-0.003-0.001-0.005-0.002-0.008-0.001-0.005-0.002-0.009-0.004-0.013-0-0.001-0-0.001-0.001-0.001-0.002-0.005-0.004-0.010-0.005-0.014-0-0-0-0-0-0.001-0.055-0.128-0.26-0.177-1.108-0.191-0.001-0-0.002-0-0.002-0v0c-0.346-0.006-0.798-0.006-1.391-0.004-1.039 0.004-1.613-1.016-1.797-1.41 0.251-1.389 0.977-2.379 2.17-3.051 0.023-0.013 0.018-0.023-0.009-0.031 0.233-0.141-2.82-0.004-4.225 1.782-1.247-0.31-2.333-0.289-3.269-0.069-0.18-0.005-0.404-0.027-0.67-0.083-0.623-0.564-1.514-1.606-1.562-2.85 0 0-0.003 0.002-0.008 0.006-0.001-0.012-0.002-0.024-0.002-0.036 0 0-1.897 1.458-1.613 5.434-0.001 0.064-0.002 0.125-0.004 0.184-0.514 0.696-0.768 1.282-0.787 1.411-0.455 0.926-0.917 2.32-1.292 4.437 0 0 0.263-0.833 0.79-1.777-0.388 1.188-0.693 3.036-0.514 5.808 0 0 0.047-0.615 0.215-1.5 0.131 1.719 0.704 3.841 2.152 6.337 2.78 4.791 7.052 7.211 11.775 7.582 0.839 0.069 1.689 0.071 2.544 0.006 0.079-0.006 0.157-0.011 0.236-0.018 0.968-0.068 1.942-0.214 2.914-0.449 13.287-3.212 11.842-19.256 11.842-19.256z",
|
||||
|
||||
"IE":
|
||||
"M22.944 19.651h7.377c0.057-0.512 0.080-1.034 0.080-1.569 0-2.507-0.673-4.858-1.848-6.883 1.215-3.228 1.172-5.968-0.455-7.606-1.547-1.54-5.697-1.29-10.388 0.787-0.347-0.026-0.697-0.040-1.051-0.040-6.439 0-11.841 4.431-13.335 10.402 2.020-2.586 4.145-4.461 6.984-5.826-0.258 0.242-1.764 1.739-2.018 1.993-7.486 7.484-9.847 17.26-7.306 19.8 1.931 1.93 5.43 1.604 9.449-0.364 1.869 0.952 3.984 1.488 6.226 1.488 6.035 0 11.15-3.885 13.003-9.295h-7.433c-1.023 1.887-3.023 3.171-5.319 3.171s-4.296-1.284-5.319-3.171c-0.455-0.852-0.716-1.83-0.716-2.864v-0.023h12.071zM10.884 16.025c0.171-3.035 2.694-5.456 5.774-5.456s5.604 2.421 5.774 5.456h-11.548zM28.030 5.119c1.048 1.059 1.021 3.007 0.125 5.438-1.535-2.341-3.766-4.186-6.4-5.239 2.816-1.207 5.106-1.367 6.274-0.199zM2.921 30.227c-1.337-1.337-0.934-4.144 0.788-7.526 1.072 3.008 3.161 5.534 5.854 7.161-2.982 1.354-5.423 1.584-6.643 0.365z",
|
||||
|
||||
"opera":
|
||||
"M15.939 0c-8.741 0-15.023 6.34-15.023 15.851 0 8.463 6.109 16.149 15.024 16.149 9.002 0 15.144-7.684 15.144-16.149 0-9.586-6.483-15.851-15.146-15.851zM21.511 15.579c-0.002 5.292-0.284 12.828-5.571 12.828v0.001c-5.212 0-5.42-7.54-5.42-12.822 0-6.199 0.58-12.143 5.42-12.143s5.571 6.018 5.571 12.136z",
|
||||
|
||||
"safari":
|
||||
"M16 2c-0.752 0-1.491 0.056-2.213 0.163-0.009-0.024-0.020-0.048-0.031-0.072 0.401-0.313 0.611-0.745 0.52-1.173-0.117-0.549-0.7-0.918-1.451-0.918-0.162 0-0.327 0.018-0.49 0.052-0.943 0.2-1.567 0.894-1.421 1.58 0.089 0.417 0.447 0.729 0.943 0.856-0.001 0.030-0.001 0.061 0.001 0.092-6.269 1.798-10.857 7.573-10.857 14.42 0 8.284 6.716 15 15 15s15-6.716 15-15c0-8.284-6.716-15-15-15zM11.403 1.528c-0.085-0.398 0.39-0.85 1.036-0.987 0.129-0.027 0.259-0.041 0.386-0.041 0.501 0 0.897 0.215 0.962 0.522 0.050 0.235-0.096 0.488-0.356 0.684-0.22-0.155-0.501-0.223-0.784-0.162-0.283 0.060-0.511 0.235-0.649 0.464-0.318-0.077-0.547-0.253-0.595-0.48zM24.043 25.043c-1.374 1.374-3.038 2.352-4.853 2.88l-1.325-2.149-0.336 2.499c-0.503 0.067-1.013 0.102-1.529 0.102-3.038 0-5.895-1.183-8.043-3.332-1.374-1.374-2.352-3.038-2.88-4.853l2.149-1.325-2.499-0.336c-0.067-0.503-0.102-1.013-0.102-1.529 0-3.038 1.183-5.895 3.332-8.043 1.374-1.374 3.038-2.352 4.853-2.88l1.325 2.149 0.336-2.499c0.503-0.067 1.013-0.102 1.529-0.102 3.038 0 5.895 1.183 8.043 3.332 1.374 1.374 2.352 3.038 2.88 4.853l-2.149 1.325 2.499 0.336c0.067 0.503 0.102 1.013 0.102 1.529 0 3.038-1.183 5.895-3.332 8.043zM23.778 9.222l-6.156 4.678c-0.458-0.239-0.965-0.377-1.482-0.398l-1.596-3.349-0.096 3.708c-0.415 0.204-0.784 0.489-1.090 0.842l-1.972-0.699 1.44 1.517c-0.198 0.422-0.308 0.876-0.326 1.338l-3.349 1.596 3.711 0.096c0.012 0.024 0.024 0.048 0.037 0.071l-4.678 6.156 6.156-4.678c0.458 0.239 0.965 0.377 1.482 0.398l1.596 3.349 0.096-3.708c0.415-0.204 0.785-0.489 1.090-0.842l1.972 0.699-1.44-1.517c0.198-0.422 0.308-0.876 0.326-1.338l3.349-1.596-3.71-0.096c-0.012-0.024-0.024-0.048-0.037-0.071l4.678-6.156zM16.003 13.892v0zM15.376 14.066c0.21-0.045 0.42-0.066 0.627-0.066 0.13 0 0.258 0.008 0.384 0.025l0.003 0.001-0-0.001c0.273 0.036 0.537 0.109 0.785 0.216l-2.236 1.699-1.699 2.236c-0.074-0.175-0.133-0.359-0.174-0.552-0.345-1.621 0.69-3.214 2.311-3.558zM17.566 19.559v0 0c-0.283 0.173-0.599 0.303-0.942 0.376-0.21 0.045-0.42 0.066-0.627 0.066-0.13 0-0.258-0.009-0.385-0.025l-0.002-0c-0.273-0.036-0.537-0.109-0.785-0.215l2.236-1.699 1.699-2.236c0.074 0.175 0.133 0.359 0.174 0.552 0.272 1.278-0.314 2.538-1.368 3.183z"
|
||||
};
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" version="1.1" width="18px" height="18px">
|
||||
<g id="surface1">
|
||||
<path style=" " d="M 2.5 2 C 1.675781 2 1 2.675781 1 3.5 L 1 12.5 C 1 13.324219 1.675781 14 2.5 14 L 13.5 14 C 14.324219 14 15 13.324219 15 12.5 L 15 5.5 C 15 4.675781 14.324219 4 13.5 4 L 6.796875 4 L 6.144531 2.789063 C 5.882813 2.300781 5.375 2 4.824219 2 Z M 2.5 3 L 4.824219 3 C 5.007813 3 5.175781 3.101563 5.265625 3.261719 L 5.664063 4 L 2 4 L 2 3.5 C 2 3.21875 2.21875 3 2.5 3 Z M 2 5 L 13.5 5 C 13.78125 5 14 5.21875 14 5.5 L 14 12.5 C 14 12.78125 13.78125 13 13.5 13 L 2.5 13 C 2.21875 13 2 12.78125 2 12.5 Z "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 738 B |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" version="1.1" width="18px" height="18px">
|
||||
<g id="surface1">
|
||||
<path style=" " d="M 4.5 2 C 3.675781 2 3 2.675781 3 3.5 L 3 12.5 C 3 13.324219 3.675781 14 4.5 14 L 11.5 14 C 12.324219 14 13 13.324219 13 12.5 L 13 5.292969 L 9.707031 2 Z M 4.5 3 L 9 3 L 9 6 L 12 6 L 12 12.5 C 12 12.78125 11.78125 13 11.5 13 L 4.5 13 C 4.21875 13 4 12.78125 4 12.5 L 4 3.5 C 4 3.21875 4.21875 3 4.5 3 Z M 10 3.707031 L 11.292969 5 L 10 5 Z M 6 8 L 6 9 L 10 9 L 10 8 Z M 6 10 L 6 11 L 9 11 L 9 10 Z "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 636 B |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#000000" d="M8 0c-4.418 0-8 1.119-8 2.5v1.5l6 6v5c0 0.552 0.895 1 2 1s2-0.448 2-1v-5l6-6v-1.5c0-1.381-3.582-2.5-8-2.5zM1.475 2.169c0.374-0.213 0.9-0.416 1.52-0.586 1.374-0.376 3.152-0.583 5.005-0.583s3.631 0.207 5.005 0.583c0.62 0.17 1.146 0.372 1.52 0.586 0.247 0.141 0.38 0.26 0.442 0.331-0.062 0.071-0.195 0.19-0.442 0.331-0.374 0.213-0.9 0.416-1.52 0.586-1.374 0.376-3.152 0.583-5.005 0.583s-3.631-0.207-5.005-0.583c-0.62-0.17-1.146-0.372-1.52-0.586-0.247-0.141-0.38-0.26-0.442-0.331 0.062-0.071 0.195-0.19 0.442-0.331z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 864 B |
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#000000" d="M5 2h-2c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h2c0.55 0 1-0.45 1-1v-2c0-0.55-0.45-1-1-1z"></path>
|
||||
<path fill="#000000" d="M11 6h2c0.55 0 1-0.45 1-1v-2c0-0.55-0.45-1-1-1h-2c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1zM11 3h2v2h-2v-2z"></path>
|
||||
<path fill="#000000" d="M5 10h-2c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h2c0.55 0 1-0.45 1-1v-2c0-0.55-0.45-1-1-1zM5 13h-2v-2h2v2z"></path>
|
||||
<path fill="#000000" d="M13 10h-2c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h2c0.55 0 1-0.45 1-1v-2c0-0.55-0.45-1-1-1z"></path>
|
||||
<path fill="#000000" d="M14 8h-1c-1.336 0-2.591-0.52-3.536-1.464s-1.464-2.2-1.464-3.536v-1c0-1.1-0.9-2-2-2h-4c-1.1 0-2 0.9-2 2v4c0 1.1 0.9 2 2 2h1c1.336 0 2.591 0.52 3.536 1.464s1.464 2.2 1.464 3.536v1c0 1.1 0.9 2 2 2h4c1.1 0 2-0.9 2-2v-4c0-1.1-0.9-2-2-2zM15 14c0 0.265-0.105 0.515-0.295 0.705s-0.44 0.295-0.705 0.295h-4c-0.265 0-0.515-0.105-0.705-0.295s-0.295-0.44-0.295-0.705v-1c0-3.314-2.686-6-6-6h-1c-0.265 0-0.515-0.105-0.705-0.295s-0.295-0.441-0.295-0.705v-4c0-0.265 0.105-0.515 0.295-0.705s0.44-0.295 0.705-0.295h4c0.265 0 0.515 0.105 0.705 0.295s0.295 0.44 0.295 0.705v1c0 3.314 2.686 6 6 6h1c0.265 0 0.515 0.105 0.705 0.295s0.295 0.44 0.295 0.705v4z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 37 KiB |