UNPKG

gojs

Version:

Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams

489 lines (450 loc) 20.9 kB
<!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>