UNPKG

gojs

Version:

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

523 lines (501 loc) 26.8 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="Gauges and Meters that allow the user to control the values that those instruments show"/> <link rel="stylesheet" href="../assets/css/style.css"/> <!-- Copyright 1998-2023 by Northwoods Software Corporation. --> <title>Control Instruments: Gauges and Meters</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", { "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: (e, obj) => { obj._dragging = true; obj._original = obj.part.data.value; }, actionMove: (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(m => { m.set(obj.part.data, "value", val); }, null); // null means skipsUndoManager }, actionUp: (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(m => { m.set(obj.part.data, "value", obj._original); }, null); // null means skipsUndoManager // now set the data.value for real e.diagram.model.commit(m => { m.set(obj.part.data, "value", val); }, "dragged slider"); }, actionCancel: (e, obj) => { obj._dragging = false; e.diagram.model.commit(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", e => 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", (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", v => v.toString()).makeTwoWay(s => 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", (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", v => v.toString()).makeTwoWay(s => 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", (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", v => v.toString()).makeTwoWay(s => 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.7107 70.7107 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", v => v.toString()).makeTwoWay(s => 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", (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.7107 70.7107 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", v => v.toString()).makeTwoWay(s => parseFloat(s)), new go.Binding("stroke", "color")), $(go.Shape, { fill: "red", strokeWidth: 0 }, new go.Binding("geometry", "value", (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(() => { myDiagram.commit(diag => { diag.links.each(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)) { diag.model.set(l.fromNode.data, "value", prev-1); diag.model.set(l.toNode.data, "value", now+1); } }); }) loop(); }, 500); } window.addEventListener('DOMContentLoaded', init); </script> <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> </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>