create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
825 lines (777 loc) • 35.1 kB
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" />
<meta itemprop="description" content="Gauges and Meters that allow the user to control the values that those instruments show" />
<meta property="og:description" content="Gauges and Meters that allow the user to control the values that those instruments show" />
<meta name="twitter:description" content="Gauges and Meters that allow the user to control the values that those instruments show" />
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="../assets/css/style.css">
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
<meta itemprop="name" content="Meter and Gauge Controls Drawn in Nodes Connected by Flows" />
<meta property="og:title" content="Meter and Gauge Controls Drawn in Nodes Connected by Flows" />
<meta name="twitter:title" content="Meter and Gauge Controls Drawn in Nodes Connected by Flows" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/controlgauges.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/controlgauges.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/controlgauges.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/controlGauges.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/controlGauges.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Meter and Gauge Controls Drawn in Nodes Connected by Flows | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<div id="allSampleContent" class="p-4 w-full">
<script id="code">
function init() {
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(obj, alwaysVisible) {
obj.isActionable = true;
obj.actionDown = (e, obj) => {
obj._dragging = true;
obj._original = obj.part.data.value;
};
obj.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
};
obj.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');
};
obj.actionCancel = (e, obj) => {
obj._dragging = false;
e.diagram.model.commit(m => {
m.set(obj.part.data, 'value', obj._original);
}, null); // null means skipsUndoManager
};
}
function applySliderBindings(object, alwaysVisible) {
if (alwaysVisible) object.bindObject('visible', 'isEnabled', null, null, 'SCALE');
object.bindObject('cursor', 'isEnabled', e => e ? 'pointer' : '', null, null, 'SCALE');
return object;
}
function applyCommonScaleBindings(object) {
object
.bind('graduatedMin', 'min')
.bind('graduatedMax', 'max')
.bind('graduatedTickUnit', 'unit')
.bind('isEnabled', 'editable');
}
function commonSlider(vert) {
return new 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
})
.apply(sliderActions, false);
}
function nodeStyle(node) {
node.locationSpot = go.Spot.Center;
node.fromSpot = go.Spot.BottomRightSides;
node.toSpot = go.Spot.TopLeftSides;
node.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify);
}
myDiagram.nodeTemplateMap.add('Horizontal',
new go.Node('Auto')
.apply(nodeStyle)
.add(
new go.Shape({ fill: 'lightgray', stroke: 'gray' }),
new go.Panel('Table', { margin: 1, stretch: go.Stretch.Fill })
.add(
// header information
new go.TextBlock({ row: 0, font: 'bold 10pt sans-serif' })
.bind('text'),
new go.Panel('Spot', { row: 1 })
.add(
new go.Panel('Graduated', {
name: 'SCALE',
margin: new go.Margin(0, 6),
graduatedTickUnit: 10,
isEnabled: false
})
.apply(applyCommonScaleBindings)
.add(
new go.Shape({ geometryString: 'M0 0 H200', height: 0, name: 'PATH' }),
new go.Shape({
geometryString: 'M0 0 V16',
alignmentFocus: go.Spot.Center,
stroke: 'gray'
}),
new go.Shape({
geometryString: 'M0 0 V20',
alignmentFocus: go.Spot.Center,
interval: 5,
strokeWidth: 1.5
})
),
new go.Panel('Spot', {
alignment: go.Spot.Left,
alignmentFocus: go.Spot.Left,
alignmentFocusName: 'BAR'
})
// the indicator (a bar)
.add(
new go.Shape({ name: 'BAR', fill: 'red', strokeWidth: 0, height: 8 })
.bind('fill', 'color')
.bind('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
new go.TextBlock('0', { row: 2, alignment: go.Spot.Left })
.bind('text', 'min'),
new go.TextBlock('100', { row: 2, alignment: go.Spot.Right })
.bind('text', 'max'),
new go.TextBlock({
row: 2,
background: 'white',
font: 'bold 10pt sans-serif',
isMultiline: false,
editable: true
})
.bindTwoWay('text', 'value', v => v.toString(), s => parseFloat(s))
)
)
);
myDiagram.nodeTemplateMap.add('Vertical',
new go.Node('Auto')
.apply(nodeStyle)
.add(
new go.Shape({ fill: 'lightgray', stroke: 'gray' }),
new go.Panel('Table', { margin: 1, stretch: go.Stretch.Fill })
// header information
.add(
new go.TextBlock({ row: 0, font: 'bold 10pt sans-serif' })
.bind('text'),
new go.Panel('Spot', { row: 1 })
.add(
new go.Panel('Graduated', {
name: 'SCALE',
margin: new go.Margin(6, 0),
graduatedTickUnit: 10,
isEnabled: false
})
.apply(applyCommonScaleBindings)
.add(
// NOTE: path goes upward!
new go.Shape({ geometryString: 'M0 0 V-200', width: 0, name: 'PATH' }),
new go.Shape({
geometryString: 'M0 0 V16',
alignmentFocus: go.Spot.Center,
stroke: 'gray'
}),
new go.Shape({
geometryString: 'M0 0 V20',
alignmentFocus: go.Spot.Center,
interval: 5,
strokeWidth: 1.5
})
),
new go.Panel('Spot', {
alignment: go.Spot.Bottom,
alignmentFocus: go.Spot.Bottom,
alignmentFocusName: 'BAR'
})
// the indicator (a bar)
.add(
new go.Shape({ name: 'BAR', fill: 'red', strokeWidth: 0, height: 8 })
.bind('fill', 'color')
.bind('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
new go.TextBlock('0', { row: 2, alignment: go.Spot.Left })
.bind('text', 'min'),
new go.TextBlock('100', { row: 2, alignment: go.Spot.Right })
.bind('text', 'max'),
new go.TextBlock({
row: 2,
background: 'white',
font: 'bold 10pt sans-serif',
isMultiline: false,
editable: true
})
.bindTwoWay('text', 'value', v => v.toString(), s => parseFloat(s))
)
)
);
function needleMeterGeometry(v, shp) {
var scale = shp.part.findObject('SCALE');
var pt = scale.graduatedPointForValue(v);
var geo = new go.Geometry(go.GeometryType.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;
}
myDiagram.nodeTemplateMap.add('NeedleMeter',
new go.Node('Auto')
.apply(nodeStyle)
.add(
new go.Shape({ fill: 'darkslategray' }),
new go.Panel('Spot')
.add(
new go.Panel('Position')
.add(
new go.Panel('Graduated', { name: 'SCALE', margin: 10 })
.apply(applyCommonScaleBindings)
.add(
new go.Shape({
name: 'PATH',
geometryString: 'M0 0 A120 120 0 0 1 200 0',
stroke: 'white'
}),
new go.Shape({ geometryString: 'M0 0 V10', stroke: 'white' }),
new go.TextBlock({
segmentOffset: new go.Point(0, 12),
segmentOrientation: go.Orientation.Along,
stroke: 'white'
})
),
new go.Shape({
stroke: 'red',
strokeWidth: 2,
isGeometryPositioned: true
})
.apply(sliderActions, true)
.bind('geometry', 'value', needleMeterGeometry)
),
new go.TextBlock({
alignment: new go.Spot(0.5, 0.5, 0, 20),
stroke: 'white',
font: 'bold 10pt sans-serif'
})
.bind('text')
.bind('stroke', 'color'),
new 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
})
.bindTwoWay('text', 'value', v => v.toString(), s => parseFloat(s))
.bind('stroke', 'color')
)
)
);
myDiagram.nodeTemplateMap.add('CircularMeter',
new go.Node('Table')
.apply(nodeStyle)
.add(
new go.Panel('Auto', { row: 0 })
.add(
new go.Shape('Circle', {
stroke: 'orange',
strokeWidth: 5,
spot1: go.Spot.TopLeft,
spot2: go.Spot.BottomRight
})
.bind('stroke', 'color'),
new go.Panel('Spot')
.add(
new go.Panel('Graduated', {
name: 'SCALE',
margin: 14,
graduatedTickUnit: 2.5, // tick marks at each multiple of 2.5
stretch: go.Stretch.None // needed to avoid unnecessary re-measuring!!!
})
.apply(applyCommonScaleBindings)
.add(
// the main path of the graduated panel, an arc starting at 135 degrees and sweeping for 270 degrees
new 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
new go.Shape({ geometryString: 'M0 0 V10', stroke: 'white', strokeWidth: 1 }),
new go.Shape({
geometryString: 'M0 0 V12',
stroke: 'white',
strokeWidth: 2,
interval: 2
}),
new go.Shape({
geometryString: 'M0 0 V15',
stroke: 'white',
strokeWidth: 3,
interval: 4
}),
new 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)
})
),
new go.TextBlock({
alignment: new go.Spot(0.5, 0.9),
stroke: 'white',
font: 'bold italic 14pt sans-serif',
editable: true
})
.bindTwoWay('text', 'value', v => v.toString(), s => parseFloat(s))
.bind('stroke', 'color'),
new go.Shape({
fill: 'red',
strokeWidth: 0,
geometryString: 'F1 M-6 0 L0 -6 100 0 0 6z x M-100 0'
})
.apply(sliderActions, true)
.bind('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);
}),
new go.Shape('Circle', { width: 2, height: 2, fill: '#444' })
)
),
new go.TextBlock({ row: 1, font: 'bold 11pt sans-serif' })
.bind('text')
)
);
function barMeterGeometry(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.SegmentType.Arc,
a0,
sweep,
0,
0,
radius,
radius
)
)
.add(
new go.PathSegment(go.SegmentType.Line, pv.x - radius, pv.y - radius)
)
.add(
new go.PathSegment(
go.SegmentType.Arc,
av,
-sweep,
0,
0,
radius - layerThickness,
radius - layerThickness
).close()
)
);
}
myDiagram.nodeTemplateMap.add('BarMeter',
new go.Node('Table', { scale: 0.8 })
.apply(nodeStyle)
.add(
new go.Panel('Auto', { row: 0 })
.add(
new go.Shape('Circle', {
stroke: 'orange',
strokeWidth: 5,
spot1: go.Spot.TopLeft,
spot2: go.Spot.BottomRight
})
.bind('stroke', 'color'),
new go.Panel('Spot')
.add(
new go.Panel('Graduated', {
name: 'SCALE',
margin: 14,
graduatedTickUnit: 2.5, // tick marks at each multiple of 2.5
stretch: go.Stretch.None // needed to avoid unnecessary re-measuring!!!
})
.apply(applyCommonScaleBindings)
.add(
// the main path of the graduated panel, an arc starting at 135 degrees and sweeping for 270 degrees
new 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
new go.Shape({ geometryString: 'M0 0 V10', stroke: 'white', strokeWidth: 1 }),
new go.Shape({
geometryString: 'M0 0 V12',
stroke: 'white',
strokeWidth: 2,
interval: 2
}),
new go.Shape({
geometryString: 'M0 0 V15',
stroke: 'white',
strokeWidth: 3,
interval: 4
}),
new 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)
})
),
new go.TextBlock({
alignment: go.Spot.Center,
stroke: 'white',
font: 'bold italic 14pt sans-serif',
editable: true
})
.bindTwoWay('text', 'value', v => v.toString(), s => parseFloat(s))
.bind('stroke', 'color'),
new go.Shape({ fill: 'red', strokeWidth: 0 })
.apply(sliderActions, true)
.bind('geometry', 'value', barMeterGeometry),
new go.Shape('Circle', { width: 2, height: 2, fill: '#444' })
)
),
new go.TextBlock({ row: 1, font: 'bold 11pt sans-serif' })
.bind('text')
)
);
myDiagram.linkTemplate =
new go.Link({
routing: go.Routing.AvoidsNodes,
corner: 12
})
.add(
new go.Shape({ isPanelMain: true, stroke: 'gray', strokeWidth: 9 }),
new go.Shape({ isPanelMain: true, stroke: 'lightgray', strokeWidth: 5 }),
new 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.</p>
<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>
<div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto">
<hr/>
<h3 class="text-xl">GoJS Features in this sample</h3>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Geometry Path Strings</h4>
<p>
The <b>GoJS</b> <a href="../api/symbols/Geometry.html" target="api">Geometry</a> class controls the "shape" of a <a href="../api/symbols/Shape.html" target="api">Shape</a>,
whereas the <a href="../api/symbols/Shape.html#fill" target="api">Shape.fill</a> and <a href="../api/symbols/Shape.html#stroke" target="api">Shape.stroke</a> and other shape properties control the colors and appearance of the shape.
For common shape figures, there are predefined geometries that can be used by setting <a href="../api/symbols/Shape.html#figure" target="api">Shape.figure</a>.
However one can also define custom geometries.
</p>
<p>
One can construct any Geometry by allocating and initializing a <a href="../api/symbols/Geometry.html" target="api">Geometry</a> of at least one <a href="../api/symbols/PathFigure.html" target="api">PathFigure</a> holding some <a href="../api/symbols/PathSegment.html" target="api">PathSegment</a>s.
But you may find that using the string representation of a Geometry is easier to write and save in a database.
Use the static method <a href="../api/symbols/Geometry.html#parse" target="api">Geometry.parse</a> or the <a href="../api/symbols/Shape.html#geometryString" target="api">Shape.geometryString</a> property to transform a geometry path string into a <a href="../api/symbols/Geometry.html" target="api">Geometry</a> object.
</p>
<p>
More information can be found in the <a href="../intro/geometry.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#geometries">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Tools</h4>
<p>
<a href="../api/symbols/Tool.html" target="api">Tool</a>s handle all input events, such as mouse and keyboard interactions, in a Diagram.
There are many kinds of predefined Tool classes that implement all of the common operations that users do.
</p>
<p>
For flexibility and simplicity, all input events are canonicalized as <a href="../api/symbols/InputEvent.html" target="api">InputEvent</a>s and
redirected by the diagram to go to the <a href="../api/symbols/Diagram.html#currentTool" target="api">Diagram.currentTool</a>.
By default the Diagram.currentTool is an instance of <a href="../api/symbols/ToolManager.html" target="api">ToolManager</a> held as the <a href="../api/symbols/Diagram.html#toolManager" target="api">Diagram.toolManager</a>.
The ToolManager implements support for all mode-less tools.
The ToolManager is responsible for finding another tool that is ready to run and then making it the new current tool.
This causes the new tool to process all of the input events (mouse, keyboard, and touch) until the tool decides that it is finished,
at which time the diagram's current tool reverts back to the <a href="../api/symbols/Diagram.html#defaultTool" target="api">Diagram.defaultTool</a>, which is normally the ToolManager, again.
</p>
<p>
More information can be found in the <a href="../intro/tools.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#tools">Related samples</a>
</p>
<hr>
</div>
</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>