UNPKG

gojs

Version:

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

366 lines (340 loc) 15.9 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="Belts &amp; gears: chains, pulleys, tensioners, conveyor belts, rollers."/> <link rel="stylesheet" href="../assets/css/style.css"/> <!-- Copyright 1998-2023 by Northwoods Software Corporation. --> <title>Belts</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() { if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this // 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", { "InitialLayoutCompleted": e => { updateBelts(); // changes the bounds of the "Belt"s e.diagram.alignDocument(go.Spot.Center, go.Spot.Center); }, "animationManager.isInitial": false, "undoManager.isEnabled": true, allowCopy: false, allowDelete: false, "draggingTool.moveParts": function(parts, offset, check) { // method override must be function, not => 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: (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 = next.to.compareWithLineSegmentPoint(guide.from, follow.to) < 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 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(node => { if (node.category !== "Belt") return; animation.add(node.findObject("BELT"), "strokeDashOffset", 36, 0) }); // Run indefinitely animation.runCount = Infinity; animation.start(); beltAnimation = animation; } window.addEventListener('DOMContentLoaded', init); </script> <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> </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>