UNPKG

gojs

Version:

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

531 lines (491 loc) 24 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/> <meta name="description" content="A Grafcet diagram editor, showing buttons for creating new nodes and links related to the selected node."/> <link rel="stylesheet" href="../assets/css/style.css"/> <!-- Copyright 1998-2023 by Northwoods Software Corporation. --> <title>Grafcet Diagrams</title> </head> <body> <!-- This top nav is not part of the sample code --> <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary"> <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2"> <div class="md:pl-4"> <a class="text-white hover:text-white no-underline hover:no-underline font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../"> <h1 class="my-0 p-1 ">GoJS</h1> </a> </div> <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation"> <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6"> <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path> <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button> <div id="topnavList" class="hidden sm:block items-center w-auto mt-0 text-white p-0 z-20"> <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0"> <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li> </ul> </div> </div> <hr class="border-b border-gray-600 opacity-50 my-0 py-0" /> </nav> <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto"> <div id="navSide" class="flex flex-col w-full md:w-48 text-gray-700 bg-white flex-shrink-0"></div> <!-- * * * * * * * * * * * * * --> <!-- Start of GoJS sample code --> <script src="../release/go.js"></script> <div id="allSampleContent" class="p-4 w-full"> <script id="code"> function init() { // Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make // For details, see https://gojs.net/latest/intro/buildingObjects.html const $ = go.GraphObject.make; myDiagram = new 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", e => { const button = document.getElementById("saveModel"); if (button) button.disabled = !myDiagram.isModified; const idx = document.title.indexOf("*"); if (myDiagram.isModified) { if (idx < 0) document.title += "*"; } else { if (idx >= 0) document.title = document.title.slice(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. const 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) { const node = obj.part.adornedPart; const model = myDiagram.model; model.startTransaction("add Step"); const loc = node.location.copy(); loc.y += 50; const nodedata = { location: go.Point.stringify(loc) }; model.addNodeData(nodedata); const nodekey = model.getKeyForNodeData(nodedata); const linkdata = { from: model.getKeyForNodeData(node.data), to: nodekey, text: "c" }; model.addLinkData(linkdata); const newnode = myDiagram.findNodeForData(nodedata); myDiagram.select(newnode); model.commitTransaction("add Step"); } function canAddStep(adorn) { const 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) { const model = myDiagram.model; model.startTransaction("add " + type); const loc = node.location.copy(); loc.y += 50; const nodedata = { category: type, location: go.Point.stringify(loc) }; model.addNodeData(nodedata); const nodekey = model.getKeyForNodeData(nodedata); const linkdata = { from: model.getKeyForNodeData(node.data), to: nodekey }; model.addLinkData(linkdata); const newnode = myDiagram.findNodeForData(nodedata); myDiagram.select(newnode); model.commitTransaction("add " + type); } function canAddSplit(adorn) { const 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) { const 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) { const 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()) ) )); const 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", t => 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", t => 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", t => 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", t => 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", t => 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", t => 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. class CustomLinkingTool extends go.LinkingTool { // user-drawn linking is normally disabled, // but needs to be turned on when using this tool doStart() { this.diagram.allowLink = true; super.doStart(); } doStop() { super.doStop(); this.diagram.allowLink = false; } } // end CustomLinkingTool // This custom Link class is smart about computing the link point and direction // at "Parallel" and "Exclusive" nodes. class BarLink extends go.Link { getLinkPoint(node, port, spot, from, ortho, othernode, otherport) { const r = port.getDocumentBounds(); const op = otherport.getDocumentBounds(); const below = op.centerY > r.centerY; const y = below ? r.bottom : r.top; if (node.category === "Parallel" || node.category === "Exclusive") { if (op.right < r.left) return new go.Point(r.left, y); if (op.left > r.right) return new go.Point(r.right, y); return new go.Point((Math.max(r.left, op.left) + Math.min(r.right, op.right)) / 2, y); } else { return new go.Point(r.centerX, y); } } getLinkDirection(node, port, linkpoint, spot, from, ortho, othernode, otherport) { const p = port.getDocumentPoint(go.Spot.Center); const op = otherport.getDocumentPoint(go.Spot.Center); const 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); } window.addEventListener('DOMContentLoaded', init); </script> <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> </div> <!-- * * * * * * * * * * * * * --> <!-- End of GoJS sample code --> </div> </body> <!-- This script is part of the gojs.net website, and is not needed to run the sample --> <script src="../assets/js/goSamples.js"></script> </html>