UNPKG

gojs

Version:

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

472 lines (454 loc) 23.5 kB
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Control Instruments: Gauges and Meters</title> <meta name="description" content="Gauges and Meters that allow the user to control the values that those instruments show" /> <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", { "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: function(e, obj) { obj._dragging = true; obj._original = obj.part.data.value; }, actionMove: function(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(function(m) { m.set(obj.part.data, "value", val); }, null); // null means skipsUndoManager }, actionUp: function(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(function(m) { m.set(obj.part.data, "value", obj._original); }, null); // null means skipsUndoManager // now set the data.value for real e.diagram.model.commit(function(m) { m.set(obj.part.data, "value", val); }, "dragged slider"); }, actionCancel: function(e, obj) { obj._dragging = false; e.diagram.model.commit(function(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", function(e) { return 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", function(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", function(v) { return v.toString(); }).makeTwoWay(function(s) { return 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", function(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", function(v) { return v.toString(); }).makeTwoWay(function(s) { return 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", function(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", function(v) { return v.toString(); }).makeTwoWay(function(s) { return 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.7 70.7 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", function(v) { return v.toString(); }).makeTwoWay(function(s) { return 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", function(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.7 70.7 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", function(v) { return v.toString(); }).makeTwoWay(function(s) { return parseFloat(s); }), new go.Binding("stroke", "color")), $(go.Shape, { fill: "red", strokeWidth: 0 }, new go.Binding("geometry", "value", function(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(function() { myDiagram.commit(function() { myDiagram.links.each(function(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)) { myDiagram.model.set(l.fromNode.data, "value", prev-1); myDiagram.model.set(l.toNode.data, "value", now+1); } }); }) loop(); }, 500); } </script> </head> <body onload="init()"> <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> </body> </html>