gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
489 lines (450 loc) • 20.9 kB
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Grafcet Diagrams</title>
<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">
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
<script id="code">
function 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>