create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
1,503 lines • 79.8 kB
HTML
<!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="Interactive circuit schematic implemented by GoJS in JavaScript for HTML." />
<meta itemprop="description" content="Interactive circuit schematic implemented by GoJS in JavaScript for HTML." />
<meta property="og:description" content="Interactive circuit schematic implemented by GoJS in JavaScript for HTML." />
<meta name="twitter:description" content="Interactive circuit schematic implemented by GoJS in JavaScript for HTML." />
<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="Interactive Diagram for Building Circuit Schematics" />
<meta property="og:title" content="Interactive Diagram for Building Circuit Schematics" />
<meta name="twitter:title" content="Interactive Diagram for Building Circuit Schematics" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/circuit.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/circuit.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/circuit.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/circuitDesigner.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/circuitDesigner.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Interactive Diagram for Building Circuit Schematics | 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 src="../extensions/OrthogonalLinkReshapingTool.js"></script>
<script src="../extensions/PolylineLinkingTool.js"></script>
<script id="code">
// Size of the grid snapping cells
const CELLSIZE = 10;
function init() {
myDiagram = new go.Diagram(
'myDiagramDiv',
{
'undoManager.isEnabled': true,
// Lets links be reshaped and moved around
linkReshapingTool: new OrthogonalLinkReshapingTool(),
// enables the grid snapping tool for dragging nodes, default is 10x10
'draggingTool.isGridSnapEnabled': true,
'draggingTool.gridSnapCellSize': new go.Size(CELLSIZE, CELLSIZE),
// allow Ctrl-G to call groupSelection()
'commandHandler.archetypeGroupData': { text: 'Group', color: '#029ffa10', isGroup: true },
'toolManager.hoverDelay': 3000,
click: (e) => {
e.diagram.commit((d) => d.clearHighlighteds(), 'no highlighteds');
},
ExternalObjectsDropped: e => {
// handle drops from the Palette
if (markingPorts) {
var newnode = e.diagram.selection.first();
markNode(newnode);
}
},
initialAutoScale: go.AutoScale.Uniform
}
);
// Enables the polyline linking tool
myDiagram.toolManager.linkingTool = new PolylineLinkingTool();
myDiagram.toolManager.linkingTool.temporaryLink.routing = go.Routing.Orthogonal;
// Checks if node is still in the diagram, adds it to the set
function stillExists(nodeKey, theSet) {
if (myDiagram.findNodeForKey(nodeKey)) {
theSet.add(nodeKey);
}
}
// Listens for changes to the link data array to update port markings if marks unused ports is on
myDiagram.addModelChangedListener(e => {
if (!markingPorts) return; // Don't run if not marking unused ports
if (myDiagram.skipsUndoManager) return; // We mark ports in skipped transactions - don't run for those either
if (e.isTransactionFinished) {
const nodesToUpdate = new Set();
const tx = e.object;
if (tx instanceof go.Transaction) {
tx.changes.each(c => {
// If a relink occurs, add involved nodes to the set
if (c.modelChange === 'linkFromKey' || c.modelChange === 'linkToKey') {
stillExists(c.oldValue, nodesToUpdate);
stillExists(c.newValue, nodesToUpdate);
stillExists(c.object.from, nodesToUpdate);
stillExists(c.object.to, nodesToUpdate);
}
// Handles relinks between the same two nodes or between a node and itself
if (c.modelChange === 'linkFromPortId' || c.modelChange === 'linkToPortId') {
stillExists(c.object.from, nodesToUpdate);
stillExists(c.object.to, nodesToUpdate);
}
if (c.modelChange === 'linkDataArray') {
// If a link is added or removed, add its from and to node keys to the set
if (c.oldValue) {
stillExists(c.oldValue['from'], nodesToUpdate);
stillExists(c.oldValue['to'], nodesToUpdate);
}
if (c.newValue) {
stillExists(c.newValue['from'], nodesToUpdate);
stillExists(c.newValue['to'], nodesToUpdate);
}
}
});
}
for (x of nodesToUpdate) {
const n = myDiagram.findNodeForKey(x);
markNode(n);
}
}
});
myPalette = new go.Palette(
'myPaletteDiv',
{
'animationManager.isEnabled': false,
'toolManager.hoverDelay': 600,
layout: new go.GridLayout({ wrappingColumn: 2 })
}
);
// Don't show context menus in the palette
myPalette.toolManager.contextMenuTool.isEnabled = false;
// A helper function for defining tooltips for buttons
function makeToolTip(str) {
return go.GraphObject.build('ToolTip').add(new go.TextBlock(str));
}
// Returns a simple context menu button with a given function and text
function createContextMenuButton(text, func) {
return go.GraphObject.build('ContextMenuButton', {
// Passes the node instead of the adornment
click: (e, button) => func(e, button.part.adornedObject),
'ButtonBorder.fill': 'white',
"_buttonFillOver": '#1E90FF20',
})
.add(new go.TextBlock(text))
}
// Context menus for different nodes
const ICContextMenu = go.GraphObject.build('ContextMenu')
.add(
createContextMenuButton('Rotate Node', flipHV),
createContextMenuButton('Swap Pins', swapPins)
);
const switchContextMenu = go.GraphObject.build('ContextMenu')
.add(
createContextMenuButton('Rotate Node', flipHV),
createContextMenuButton('Open Switch', openSwitch)
);
const nodeContextMenu = go.GraphObject.build('ContextMenu')
.add(
createContextMenuButton('Rotate Node', flipCircle)
)
const groupContextMenu = go.GraphObject.build('ContextMenu')
.add(
createContextMenuButton('Change Group Color', changeColor),
createContextMenuButton('Ungroup', (e, obj) => e.diagram.commandHandler.ungroupSelection())
);
// Template for integrated circuit pins, serve as the ports for each IC
const pinTemplate = new go.Panel('Auto', {
// Allows user selection and link drawing
fromLinkable: true,
toLinkable: true,
click: (e, pin) => highlightLinksFromPin(e, pin),
mouseEnter: (e, port) => lightPort(e, port),
mouseLeave: (e, port) => unlightPort(e, port),
cursor: 'pointer'
})
.bind('portId', 'key', (v) => String(v))
.bind('fromSpot', 'side', (v) => (v === 'left' ? go.Spot.Left : go.Spot.Right))
.bind('toSpot', 'side', (v) => (v === 'left' ? go.Spot.Left : go.Spot.Right))
.add(
new go.Shape('Rectangle', {
name: 'SHAPE',
width: 25,
height: 20,
stroke: 'black',
strokeWidth: 1,
fill: 'white',
margin: new go.Margin(4.5, 0)
}),
new go.TextBlock({
font: '12px sans-serif'
})
.bind('angle', '', (dataObj, textBlock) => {
return textBlock.part.data.orientation === 'horizontal' ? 90 : 0;
})
.bind('text', 'key')
);
// Template for the whole IC, stores pins in vertical panels on sides of main shape
function createIntegratedCircuitNodeTemplate() {
return new go.Node('Auto', {
locationSpot: new go.Spot(0.5, 0.5, 0, 5),
toolTip: makeToolTip('Integrated Circuit'),
cursor: 'pointer',
contextMenu: ICContextMenu
})
.bind('angle', 'orientation', (v) => (v === 'horizontal' ? -90 : 0))
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.add(
new go.Panel('Horizontal').add(
new go.Panel('Vertical', {
itemTemplate: pinTemplate
}).bind('itemArray', 'pinArrays', (v) => v[0]),
new go.Panel('Spot').add(
new go.Shape({
stroke: 'black',
fill: 'white',
width: 120,
click: (e, node) => highlightLinksFromIC(e, node),
doubleClick: flipHV
})
// Sets height of the rectangle part based on the number of nodes on one side
.bind('geometryString', 'pinArrays',
(v) =>
'F M 0 0 H 8 A 1 4 0 0 0 12 0 H 20 V' + // added F at beginning to fill shape
String(v[0].length * 30 + 16) + // height + margin per pin, plus fixed end height
'H 0 V 0'
),
new go.Shape('Circle', {
stroke: 'black',
fill: 'transparent',
width: 8,
alignment: new go.Spot(0, 0, 12, 10)
}),
new go.TextBlock({
alignment: go.Spot.Center,
editable: true
})
.bindTwoWay('text', 'name')
.bind('angle', 'orientation', (v) => (v === 'horizontal' ? 90 : 0))
),
new go.Panel('Vertical', {
itemTemplate: pinTemplate
}).bind('itemArray', 'pinArrays', (v) => v[1])
)
);
}
// Creates separate template for palette that is scaled down
const integratedCircuitNodeTemplate = createIntegratedCircuitNodeTemplate();
const paletteICNodeTemplate = createIntegratedCircuitNodeTemplate();
paletteICNodeTemplate.scale = 0.4;
paletteICNodeTemplate.locationSpot = new go.Spot(0.2, 0.2);
const commonPortProperties = {
fill: 'transparent',
stroke: 'transparent',
desiredSize: new go.Size(10, 10),
cursor: 'pointer',
fromLinkable: true,
toLinkable: true,
mouseEnter: (e, port) => lightPort(e, port),
mouseLeave: (e, port) => unlightPort(e, port)
}
// Creates circle ports
function createCirclePort(align, linkSpot, id) {
return new go.Shape('Circle', commonPortProperties)
.set({
fromSpot: linkSpot,
toSpot: linkSpot,
alignment: align,
portId: id
})
}
// Item array template for ports
const circlePortTemplate = new go.Panel()
.bind('alignment', 'align')
.add(
new go.Shape('Circle', commonPortProperties)
.bind('fromSpot', 'linkSpot') // Required
.bind('toSpot', 'linkSpot')
.bind('portId', 'id') // Required
.bind('toLinkableSelfNode', 'linksSelf')
.bind('fromLinkableSelfNode', 'linksSelf')
.bind('stroke')
.bind('fill')
);
// Template for creating parts with a text block and a geometry string
// Has partType property that automatically sets geometry, size, and ports
// Bindings for additional optional parameters like text, tool tips, stroke, etc.
const partNodeTemplate = new go.Node('Auto', {
locationObjectName: 'MAIN',
locationSpot: go.Spot.Center,
toolTip: makeToolTip('Part'),
click: (e, node) => highlightLinks(e, node),
doubleClick: flipCircle,
contextMenu: nodeContextMenu,
cursor: 'pointer',
background: 'transparent'
})
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.bind('toolTip', 'toolTip', (str) => makeToolTip(str))
.bind('locationSpot', 'partType', (type) => !!partLocSpots[type]
? partLocSpots[type] : go.Spot.Center) // Adjusts spot on grid if needed
.add(
new go.Panel('Vertical')
.bind('angle', 'orientation', (v) => getAngle(v))
.add(
new go.TextBlock({
textAlign: 'center',
editable: true,
margin: 4
})
.bindTwoWay('text')
.bind('visible', 'text', (text) => text ? true : false)
.bind('angle', 'orientation', (v) => getTextAngle(v)),
new go.Panel('Spot', {
itemTemplate: circlePortTemplate
})
.bind('itemArray', 'partType', (type) => partPorts[type]) // Required
.add(
new go.Panel('Spot').add(
new go.Shape({
name: 'MAIN',
stroke: 'black',
fill: 'transparent',
strokeWidth: 2
})
.bind('geometryString', 'partType', (type) => partGeometries[type])
.bind('desiredSize', 'partType', (type) => !!partSizes[type] ? partSizes[type] : null)
.bind('strokeWidth')
.bind('fill'),
new go.Shape({
visible: false,
strokeWidth: 1
})
.bind('geometryString', 'partType', (type) => !!partGeometries2[type]
? partGeometries2[type]: '')
.bind('alignment', 'partType', (type) => !!secondGeometryAlignments[type]
? secondGeometryAlignments[type] : go.Spot.Center)
.bind('visible', 'partType', (type) => !!partGeometries2[type])
)
)
)
);
// Data for part geometry strings
const partGeometries = {
resistor: 'M 0 3 L 3 3 L 4 1 L 6 5 L 8 1 L 10 5 L 12 1 L 14 5 L 15 3 L 18 3',
potentiometer: 'M 0 3 L 3 3 L 4 1 L 6 5 L 8 1 L 10 5 L 12 1 L 14 5 L 15 3 L 18 3 M 7 8 L 9 6 L 11 8 M 9 12 V 6',
capacitor: 'M 0 2 L 2 2 M 2 0 L 2 4 M 4 2 L 6 2 M 4 0 L 4 4',
capacitorPolarized: 'M 0 2 L 2 2 M 2 0 L 2 4 M 4 2 L 6 2 M 5 0 A 1 2 0 0 0 5 4',
diodeStandard: 'F M 0 2 L 2 2 M 2 0 L 5 2 L 2 4 M 5 2 L 7 2 M 2 0 L 2 4 M 5 0 L 5 4',
diodeZener: 'F M 0 2 L 2 2 M 2 0 L 5 2 L 2 4 M 5 2 L 7 2 M 2 0 L 2 4 z M 5 2 A 1 10 0 0 0 6 2 M 5 2 A 1 10 0 0 0 4 2',
diodeSchottky: 'F M 0 2 L 2 2 M 2 0 L 5 2 L 2 4 M 5 2 L 7 2 M 2 0 L 2 4 z M 5 2 L 5 3 L 6 4 M 5 2 V 1 L 4 0',
ground: 'M 4 0 L 4 4 M 0 4 H 8 M 1.5 6 H 7 M 2.7 8 H 5.6',
pChannelMOSFET: 'X M 6 6 L 6 30 H 0 M 9 6 V 12 M 9 15 V 21 M 9 24 V 30 M 9 9 H 24 V 0 M 9 18 H 24 M 9 27 H 24 V 36 X F M 24 18 L 18 15 V 21 L 24 18',
nChannelMOSFET: 'X M 6 6 L 6 30 H 0 M 9 6 V 12 M 9 15 V 21 M 9 24 V 30 M 9 9 H 24 V 0 M 9 18 H 24 M 9 27 H 24 V 36 X F M 9 18 L 15 15 V 21 L 9 18',
bjtNPN: 'M 18 21 L 18 9 M 28.5 9 L 7.5 9 M 12 9 L 6 0 L 0 0 M 24 9 L 30 0 L 36 0 M 30 0 L 30 0 L 25.5 1.5 L 30 4.5 L 30 0 M 12 9',
bjtPNP: 'M 12 14 L 12 6 M 19 6 L 5 6 M 8 6 L 4 0 L 0 0 M 16 6 L 20 0 L 24 0 M 20 0 L 20 0 L 20 0 M 8 6 L 8 3 L 5 5 L 8 6',
speaker: 'F M 6 18 H 18 V 48 H 6 V 18 M 6 18 M 18 18 L 30 0 V 66 L 18 48 M 6 17.5 H 0 M 6 18.5 H 0 M 6 47.5 H 0 M 6 48.5 H 0',
pushSwitch: 'F M 0 7.5 H 40 M 16 7.5 V 0 H 24 V 7.5 M 0 8.5 H 40'
};
// Spots for grid alignment
const partLocSpots = {
pushSwitch: new go.Spot(0.5, 1, 0, 8),
potentiometer: new go.Spot(0.5, 0.5, 0, -7.65),
pChannelMOSFET: new go.Spot(1, 0.5, -11, 2),
nChannelMOSFET: new go.Spot(1, 0.5, -11, 2),
bjtNPN: new go.Spot(0.5, 0.5, 0, -10.5),
bjtPNP: new go.Spot(0.5, 0.5, 0, -10.5),
speaker: new go.Spot(0.5, 0.5, 0, 5)
}
// Optional second shape geometries and alignments
const partGeometries2 = {
bjtNPN: 'M 21 3 A 1 1 0 0 0 21 33 A 1 1 0 0 0 21 3',
bjtPNP: 'M 21 3 A 1 1 0 0 0 21 33 A 1 1 0 0 0 21 3'
}
const secondGeometryAlignments = {
bjtNPN: new go.Spot(0.5, 0.5, 0, -10.5),
bjtPNP: new go.Spot(0.5, 0.5, 0, -10.5)
}
// Data for desiredSize property of each part
const partSizes = {
resistor: new go.Size(36, 8),
potentiometer: new go.Size(36, 24),
capacitor: new go.Size(24, 16),
capacitorPolarized: new go.Size(24, 16),
diodeStandard: new go.Size(24, 16),
diodeZener: new go.Size(24, 16),
diodeSchottky: new go.Size(24, 16),
ground: new go.Size(20, 20),
pChannelMOSFET: new go.Size(24, 36),
nChannelMOSFET: new go.Size(24, 36),
bjtNPN: new go.Size(36, 21),
bjtPNP: new go.Size(36, 21),
speaker: new go.Size(30, 66),
pushSwitch: new go.Size(40, 6.5)
};
// Data for item arrays of ports on each part
const partPorts = {
resistor: [
{ align: new go.Spot(0, 0.5, 3, 0), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in' },
{ align: new go.Spot(1, 0.5, -3, 0), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'out' }
],
capacitor: [
{ align: new go.Spot(0, 0.5, 3, 0), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in' },
{ align: new go.Spot(1, 0.5, -3, 0), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'out' }
],
capacitorPolarized: [
{ align: new go.Spot(0, 0.5, 3, 0), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in' },
{ align: new go.Spot(1, 0.5, -3, 0), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'out' }
],
diodeStandard: [
{ align: new go.Spot(0, 0.5, 3, 0), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in' },
{ align: new go.Spot(1, 0.5, -3, 0), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'out' }
],
diodeZener: [
{ align: new go.Spot(0, 0.5, 3, 0), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in' },
{ align: new go.Spot(1, 0.5, -3, 0), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'out' }
],
diodeSchottky: [
{ align: new go.Spot(0, 0.5, 3, 0), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in' },
{ align: new go.Spot(1, 0.5, -3, 0), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'out' }
],
ground: [
{ align: new go.Spot(0.5, 0, 0, 3), linkSpot: new go.Spot(0.5, 0, 0, 5), id: '' }
],
pChannelMOSFET: [
{ align: new go.Spot(0, 0.8, 0, 0.5), linkSpot: new go.Spot(0, 0.5, 7, 0), id: 'gate' },
{ align: new go.Spot(1, 0, -1, 0), linkSpot: new go.Spot(0.5, 0, 0, 7), id: 'drain' },
{ align: new go.Spot(1, 1, -1, 0), linkSpot: new go.Spot(0.5, 1, 0, -7), id: 'source' }
],
nChannelMOSFET: [
{ align: new go.Spot(0, 0.8, 0, 0.5), linkSpot: new go.Spot(0, 0.5, 7, 0), id: 'gate' },
{ align: new go.Spot(1, 0, -1, 0), linkSpot: new go.Spot(0.5, 0, 0, 7), id: 'drain' },
{ align: new go.Spot(1, 1, -1, 0), linkSpot: new go.Spot(0.5, 1, 0, -7), id: 'source' }
],
speaker: [
{ align: new go.Spot(0, 0.5, 3, -15), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in' },
{ align: new go.Spot(0, 0.5, 3, 15), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'out' }
],
potentiometer: [
{ align: new go.Spot(0, 0.5, 3, -7.65), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'in', linksSelf: true },
{ align: new go.Spot(1, 0.5, -3, -7.65), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'out', linksSelf: true },
{ align: new go.Spot(.5, 1, 0, -3), linkSpot: new go.Spot(0.5, 1, 0, -5), id: 'wiper', linksSelf: true }
],
bjtNPN: [
{ align: new go.Spot(0.5, 1, 0, -3), linkSpot: new go.Spot(0.5, 1, 0, -5), id: 'base' },
{ align: new go.Spot(0, 0.5, 3, -3.25), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'collector' },
{ align: new go.Spot(1, 0.5, -3, -3.25), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'emitter' }
],
bjtPNP: [
{ align: new go.Spot(0.5, 1, 0, -3), linkSpot: new go.Spot(0.5, 1, 0, -5), id: 'base' },
{ align: new go.Spot(0, 0.5, 3, -3.25), linkSpot: new go.Spot(0, 0.5, 5, 0), id: 'collector' },
{ align: new go.Spot(1, 0.5, -3, -3.25), linkSpot: new go.Spot(1, 0.5, -5, 0), id: 'emitter' }
],
pushSwitch: [
{ align: new go.Spot(0, 1, 5, 8), linkSpot: new go.Spot(0, 0.5, 1, 0), stroke: 'black', fill: 'white', id: 'in' },
{ align: new go.Spot(1, 1, -5, 8), linkSpot: new go.Spot(1, 0.5, -1, 0), stroke: 'black', fill: 'white', id: 'out' }
]
};
// Node for adding wire junctions
const junctionNodeTemplate = new go.Node('Auto', {
locationSpot: go.Spot.Center,
toolTip: makeToolTip('Junction'),
click: (e, node) => highlightLinks(e, node),
cursor: 'pointer'
})
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.add(
new go.Panel('Spot').add(
// Transparent outer circle for easier selection and port location
new go.Shape('Circle', {
width: 40,
fill: 'transparent',
stroke: null
}),
// Visible inner circle
new go.Shape('Circle', {
fill: 'black',
width: 10,
alignment: go.Spot.Center
}).bind('fill', 'isHollow', (v) => (v ? 'white' : 'black')),
// Ports on all sides for easier routing
createCirclePort(new go.Spot(0.5, 0, 0, 10), new go.Spot(0.5, 0, 0, 11), 'top'),
createCirclePort(new go.Spot(0.5, 1, 0, -10), new go.Spot(0.5, 1, 0, -11), 'bottom'),
createCirclePort(new go.Spot(0, 0.5, 10, 0), new go.Spot(0, 0.5, 11, 0), 'left'),
createCirclePort(new go.Spot(1, 0.5, -10, 0), new go.Spot(1, 0.5, -11, 0), 'right')
)
);
// Template for a simple switch, see Logic Circuit sample
const switchNodeTemplate = new go.Node('Spot', {
locationObjectName: 'MAIN',
locationSpot: go.Spot.Center,
toolTip: makeToolTip('Switch'),
click: (e, node) => highlightLinks(e, node),
doubleClick: openSwitch,
contextMenu: switchContextMenu,
cursor: 'pointer'
})
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.bind('angle', 'orientation', (v) => (v === 'horizontal' ? 0 : 90))
.add(
// Expands clickable region for opening switch
new go.Shape({
fill: 'transparent',
stroke: null,
width: 40,
height: 40
}),
// Transparent shape in place of closed switch to anchor on grid when opened
new go.Shape({
name: 'MAIN',
desiredSize: new go.Size(40, 40),
stroke: null,
fill: 'transparent',
width: 40,
height: 2,
position: new go.Point(40, 0)
}),
new go.Panel('Horizontal', {
// This prevents the ports from moving when the shape rotates
minSize: new go.Size(42, 42)
}).add(
new go.Panel('Spot', {
isClipping: true
}).add(
new go.Shape({ fill: 'blue', strokeWidth: 0 }),
new go.Panel({
alignment: go.Spot.Left,
alignmentFocus: go.Spot.Center,
angle: 359.99 // Rotate counter clock wise
})
.add(
new go.Shape({ width: 1, height: 1 }),
new go.Shape({
desiredSize: new go.Size(40, 40),
strokeWidth: 0,
fill: 'black',
width: 40,
height: 2,
position: new go.Point(40, 0)
})
)
.bind('angle', 'isOpen', (isOpen) => (!isOpen ? 359.99 : 359.99 - 30))
)
),
// Ports
createCirclePort(new go.Spot(1, 0.5), new go.Spot(1, 0.5, -1, 0), 'out')
.set({ fill: 'white', stroke: 'black' }),
createCirclePort(new go.Spot(0, 0.5), new go.Spot(0, 0.5, 1, 0), 'in')
.set({ fill: 'white', stroke: 'black' })
);
// Node for simple user created labels
const labelNodeTemplate = new go.Node('Auto', {
locationSpot: go.Spot.Center,
toolTip: makeToolTip('Label'),
click: (e, node) => highlightLinks(e, node),
cursor: 'pointer'
})
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.add(
new go.TextBlock('Label', {
editable: true,
textAlign: 'center',
verticalAlignment: go.Spot.Center,
alignment: go.Spot.Center,
minSize: new go.Size(40, 30),
}).bindTwoWay('text')
);
// Node for a voltage divider chip
const voltDivNodeTemplate = new go.Node('Spot', {
locationObjectName: 'MAIN',
locationSpot: go.Spot.Center,
toolTip: makeToolTip('Voltage Divider'),
click: (e, node) => highlightLinks(e, node),
contextMenu: nodeContextMenu,
cursor: 'pointer'
})
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.bind('angle', 'orientation', (v) => getAngle(v))
.add(
new go.Shape('Rectangle', {
fill: 'transparent',
stroke: 'black',
desiredSize: new go.Size(40, 30),
doubleClick: flipCircle
}),
new go.TextBlock({
alignment: go.Spot.Center,
editable: true
})
.bindTwoWay('text', 'name')
.bind('angle', 'orientation', (v) => getTextAngle(v)),
// Anchors node on grid
new go.Shape('Rectangle', {
name: 'MAIN',
fill: null,
stroke: null,
desiredSize: new go.Size(46, 2),
alignment: go.Spot.Center
}),
// Ports, rectangles are visible, transparent circles are the actual ports
new go.Shape('Rectangle', {
fill: 'black',
stroke: null,
desiredSize: new go.Size(6, 2),
alignment: new go.Spot(0, 0.5, -3, 0)
}),
createCirclePort(new go.Spot(0, 0.5, -3, 0), new go.Spot(0, 0.5, 5, 0), 'in'),
new go.Shape('Rectangle', {
fill: 'black',
stroke: null,
desiredSize: new go.Size(6, 2),
alignment: new go.Spot(1, 0.5, 3, 0)
}),
createCirclePort(new go.Spot(1, 0.5, 3, 0), new go.Spot(1, 0.5, -5, 0), 'out'),
new go.Shape('Rectangle', {
fill: 'black',
stroke: null,
desiredSize: new go.Size(2, 6),
alignment: new go.Spot(0.5, 1, 0, 3)
}),
createCirclePort(new go.Spot(0.5, 1, 0, 3), new go.Spot(0.5, 1, 0, -5), 'ground')
);
// Adds node templates to a template map for the diagram and palette
const nodeTemplateMap = new go.Map();
nodeTemplateMap.add('IC', integratedCircuitNodeTemplate);
nodeTemplateMap.add('junction', junctionNodeTemplate);
nodeTemplateMap.add('switch', switchNodeTemplate);
nodeTemplateMap.add('label', labelNodeTemplate);
nodeTemplateMap.add('voltDiv', voltDivNodeTemplate);
// General part template
nodeTemplateMap.add('part', partNodeTemplate);
myDiagram.nodeTemplateMap = nodeTemplateMap;
myPalette.nodeTemplateMap = nodeTemplateMap.copy();
// Changes to the scaled down/centered versions
myPalette.nodeTemplateMap.delete('IC');
myPalette.nodeTemplateMap.add('IC', paletteICNodeTemplate);
// Link template for drawing wires
myDiagram.linkTemplate = new go.Link({
reshapable: true,
resegmentable: true,
routing: go.Routing.Orthogonal,
adjusting: go.LinkAdjusting.End,
corner: 3,
relinkableFrom: true,
relinkableTo: true,
selectionAdorned: false,
cursor: 'pointer'
})
.bindTwoWay('points')
.add(
new go.Shape({ name: 'SHAPE', strokeWidth: 2, stroke: 'black' })
// Color for link highlighting
.bindObject('stroke', 'isHighlighted', (h) => (h ? '#42b6f5AA' : 'black'))
.bindObject('strokeWidth', 'isHighlighted', (h) => (h ? 4 : 2))
);
// Template for created groups
myDiagram.groupTemplate = new go.Group('Auto', {
ungroupable: true,
contextMenu: groupContextMenu,
dragComputation: groupDragComputation
})
.add(
new go.Panel('Vertical').add(
new go.TextBlock({
font: '14px sans-serif',
editable: true,
margin: 4
}).bindTwoWay('text'),
new go.Panel('Auto').add(
new go.Shape('Rectangle', {
selectable: false, // Allows nodes and links in a group to still be selected
fill: '#029ffa10',
stroke: 'black',
strokeWidth: 0.5,
strokeDashArray: [10, 5],
doubleClick: changeColor
}).bind('fill', 'color'),
new go.Placeholder({ padding: 5 })
)
)
);
// Customizes selection adornment
myDiagram.nodeSelectionAdornmentTemplate = new go.Adornment('Spot')
.add(
new go.Panel('Auto').add(
new go.Shape('RoundedRectangle', {
fill: null,
stroke: '#42b6f5AA',
strokeWidth: 3,
strokeDashArray: [6, 8]
}),
new go.Placeholder({ padding: 3 }),
)
)
myDiagram.groupSelectionAdornmentTemplate = myDiagram.nodeSelectionAdornmentTemplate;
myPalette.nodeSelectionAdornmentTemplate = myDiagram.groupSelectionAdornmentTemplate;
// Starting elements in the palette
myPalette.model.nodeDataArray = [
{ key: 'label', category: 'label', text: 'Label' },
{ key: 'junction', category: 'junction' },
{ key: 'openPoint', category: 'junction', isHollow: true },
{ key: 'ground', category: 'part', partType: 'ground', toolTip: 'Ground', text: null, orientation: 'horizontal' },
{ key: 'switch', category: 'switch', isOpen: false, orientation: 'horizontal' },
{ key: 'pushSwitch', category: 'part', partType: 'pushSwitch', toolTip: 'Push Switch', text: null, strokeWidth: 1, orientation: 'horizontal' },
{ key: 'resistor', category: 'part', partType: 'resistor', toolTip: 'Resistor', text: '4.7 k', orientation: 'horizontal'},
{ key: 'potentiometer', category: 'part', partType: 'potentiometer', toolTip: 'Potentiometer', text: '1M', orientation: 'horizontal' },
{ key: 'capacitor', category: 'part', partType: 'capacitor', toolTip: 'Capacitor', text: '47 μF', orientation: 'horizontal' },
{ key: 'polarizedCapacitor', category: 'part', partType: 'capacitorPolarized', toolTip: 'Polarized Capacitor', text: '100 μF', orientation: 'horizontal' },
{ key: 'standardDiode', category: 'part', partType: 'diodeStandard', toolTip: 'Standard Diode', text: '1N4001', fill: 'black', orientation: 'horizontal' },
{ key: 'zenerDiode', category: 'part', partType: 'diodeZener', toolTip: 'Zener Diode', text: '1N4001', fill: 'black', orientation: 'horizontal' },
{ key: 'schottkyDiode', category: 'part', partType: 'diodeSchottky', toolTip: 'Schottky Diode', text: '1N4001', fill: 'black', orientation: 'horizontal' },
{ key: 'P-ChannelMOSFET', category: 'part', partType: 'pChannelMOSFET', toolTip: 'P-Channel MOSFET', text: null, fill: 'black', orientation: 'horizontal' },
{ key: 'N-ChannelMOSFET', category: 'part', partType: 'nChannelMOSFET', toolTip: 'N-Channel MOSFET', text: null, fill: 'black', orientation: 'horizontal' },
{ key: 'voltageDivider', category: 'voltDiv', name: '7805', orientation: 'horizontal' },
{ key: 'bjtNPN', category: 'part', partType: 'bjtNPN', toolTip: 'NPN Bipolar Junction Transistor', text: '2N3904', orientation: 'horizontal' },
{ key: 'bjtPNP', category: 'part', partType: 'bjtPNP', toolTip: 'PNP Bipolar Junction Transistor', text: '2N3906', orientation: 'horizontal' },
{ key: 'speaker', category: 'part', partType: 'speaker', toolTip: 'Speaker', text: null, strokeWidth: 1, orientation: 'horizontal'}
];
// Add primary IC's to palette
addICtoPalette(8, '555');
addICtoPalette(28, 'SN76477');
// Starting diagram
myDiagram.model = new go.GraphLinksModel({
linkFromPortIdProperty: 'fromPort',
linkToPortIdProperty: 'toPort'
});
load();
} // end init
// Keeps track of number of ICs in the panel
var numICinPalette = 0;
function addICtoPalette(num, ICname) {
leftPinArr = [];
rightPinArr = [];
for (let i = 0; i < num / 2; i++) {
leftPinArr.push({ key: i + 1, side: 'left' });
rightPinArr.push({ key: num - i, side: 'right' });
}
numICinPalette++;
myPalette.model.addNodeData({
key: 'IC' + String(numICinPalette),
category: 'IC',
name: ICname,
pinArrays: [leftPinArr, rightPinArr],
orientation: 'vertical'
});
}
// Handles button input and adding IC's
function addICHandler() {
let num = Number(document.getElementById('pinNumInput').value);
let name = document.getElementById('ICNameInput').value;
if (Number.isFinite(num) && num > 3 && num < 99 && num % 2 === 0) {
addICtoPalette(num, name);
document.getElementById('pinNumInput').value = '';
document.getElementById('ICNameInput').value = '';
} else {
alert('Enter an even number of pins between 4 and 98');
}
}
// Swaps the pins on an IC from one side to the other
function swapPins(e, obj) {
if (e.diagram instanceof go.Palette) return;
e.diagram.commit(() => {
while (obj.part && obj.part !== obj) obj = obj.part;
const newArrays = [obj.data.pinArrays[1], obj.data.pinArrays[0]];
myDiagram.model.set(obj.data, 'pinArrays', newArrays)
})
}
// Flips node orientation back and forth between horizontal and vertical on double click
function flipHV(e, obj) {
if (e.diagram instanceof go.Palette) return;
e.diagram.commit(() => {
while (obj.part && obj.part !== obj) obj = obj.part; // Moves up the tree until it finds the node
const flip = obj.data.orientation === 'horizontal' ? 'vertical' : 'horizontal';
myDiagram.model.set(obj.data, 'orientation', flip);
})
}
// Rotates node 90 degrees on double click
const orientationArray = ['horizontal', 'vertical', 'horizontalFlipped', 'verticalFlipped'];
function flipCircle(e, obj) {
if (e.diagram instanceof go.Palette) return;
e.diagram.commit(() => {
while (obj.part && obj.part !== obj) obj = obj.part;
let index = orientationArray.indexOf(obj.data.orientation);
let next = orientationArray[(index+1) % 4];
myDiagram.model.set(obj.data, 'orientation', next);
});
}
// Returns the angle for a given string orientation
const angleMap = {
'horizontal': 0,
'vertical': 90,
'horizontalFlipped': 180,
'verticalFlipped': 270
};
function getAngle(orientation) {
return angleMap[orientation];
}
// Gets angles to keep text blocks upright
const textAngleMap = {
'horizontal': 0,
'vertical': -90,
'horizontalFlipped': 180,
'verticalFlipped': 90
};
function getTextAngle(orientation) {
return textAngleMap[orientation];
}
// Opens switch
function openSwitch(e, obj) {
if (e.diagram instanceof go.Palette) return;
e.diagram.commit(() => {
while (obj.part && obj.part !== obj) obj = obj.part;
const isOpen = !obj.data.isOpen;
myDiagram.model.setDataProperty(obj.data, 'isOpen', isOpen);
})
}
// Finds links connected to the selected node and highlights them
function highlightLinks(e, node, pid) {
myDiagram.commit(() => {
myDiagram.clearHighlighteds();
if (pid) { // Case for selection of a single IC pin
node.findLinksConnected(pid).each((l) => (l.isHighlighted = true));
} else {
node.findLinksConnected().each((l) => (l.isHighlighted = true));
}
}, null)
}
// Finds links out of a single pin
function highlightLinksFromPin(e, pin) {
const pid = pin.portId; // Saves portId
while (pin.part && pin.part !== pin) pin = pin.part; // Finds parent node
highlightLinks(e, pin, pid);
}
// Finds links out of the whole IC
function highlightLinksFromIC(e, node) {
while (node.part && node.part !== node) node = node.part; // Finds parent node
highlightLinks(e, node);
}
// Global flag for checking if unused ports are to be highlighted
var markingPorts = false;
// Loops through nodes in the diagram and highlights ports that have no links connected to them
function unusedPorts() {
myDiagram.nodes.each((node) => {
if (node.category !== 'junction') { // Ignores junctions
markNode(node);
}
});
document.getElementById('markPortsButton').style.display = 'none';
document.getElementById('unmarkPortsButton').style.display = '';
markingPorts = true;
}
function markNode(node) {
if (node.category === 'junction') return;
node.ports.each((port) => markPort(node, port));
}
function markPort(node, port) {
// The second argument to commit() is null to skip the undo manager
myDiagram.commit(() => {
const isIC = node.category === 'IC';
const isSwitch = node.category === 'switch' || node.data.partType === 'pushSwitch';
const shape = isIC ? port.findObject('SHAPE') : port;
if (node.findLinksConnected(port.portId).count === 0 && node.category !== 'junction') {
shape.stroke = 'red';
} else {
shape.stroke = isIC || isSwitch ? 'black' : 'transparent';
}
shape.fill = isIC || isSwitch ? 'white' : 'transparent'; // For un-lighting
}, null)
}
// Does the opposite of above
function unmarkPorts() {
myDiagram.nodes.each((node) => {
if (node.category !== 'junction') {
unmarkNode(node);
}
});
document.getElementById('markPortsButton').style.display = '';
document.getElementById('unmarkPortsButton').style.display = 'none';
markingPorts = false;
}
function unmarkNode(node) {
node.ports.each((port) => unmarkPort(node, port));
}
function unmarkPort(node, port) {
myDiagram.commit(() => {
const shape = node.category === 'IC' ? port.findObject('SHAPE') : port;
const isICorSwitch = node.category === 'IC' ||
node.category === 'switch' || node.data.partType === 'pushSwitch';
if (isICorSwitch) {
shape.stroke = 'black';
shape.fill = 'white'; // for un-lighting
} else {
shape.stroke = 'transparent';
shape.fill = 'transparent';
}
}, null)
}
// Highlights ports when hovered over
function lightPort(e, port) {
if (port.diagram instanceof go.Palette) return; // Doesn't light palette ports
e.diagram.commit(() => {
if (port instanceof go.Panel) { // Finds port through panel if needed pin
port.findObject('SHAPE').stroke = '#1E90FF';
port.findObject('SHAPE').fill = '#1E90FF20';
} else {
port.stroke = '#1E90FF';
port.fill = '#1E90FF20';
}
}, null)
}
function unlightPort(e, port) {
if (port.diagram instanceof go.Palette) return;
let node = port;
while (node.part && node.part !== node) node = node.part;
if (markingPorts) {
markPort(node, port);
} else {
unmarkPort(node, port);
}
}
// Changes color of groups
const colorArray = ['#029ffa10', '#02fa7610', '#f2fa0210', '#fa1f0210', '#ffffff10'];
function changeColor(e, node) {
while (node.part && node.part !== node) node = node.part;
let index = colorArray.indexOf(node.data.color);
let color = colorArray[(index+1) % colorArray.length];
e.diagram.commit(() => {myDiagram.model.set(node.data, 'color', color)});
}
// Accounts for groups not locking to the grid when created
function groupDragComputation(part, pt, gridpt) {
const loc = part.location;
const bounds = part.naturalBounds;
const offx = loc.x % CELLSIZE;
const offy = loc.y % CELLSIZE;
return new go.Point(gridpt.x + offx, gridpt.y + offy);
}
// 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);
}
document.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div class="sampleWrapper">
<div style="width: 100%; height: fit-content; display: flex; flex: 2">
<div
id="myPaletteDiv"
style="border: solid 1px black; width: 300px; height: 700px"></div>
<div
id="myDiagramDiv"
style="border: solid 1px black; width: 1400px; height: 700px"></div>
</div>
<div
id="menuDiv"
style="display: flex; justify-content: space-between; margin: 10px 0px">
<div style="display: flex; flex-direction: column; gap: 4px; border: solid 1px black; padding: .5rem; border-radius: .25rem;">
<div style="display: flex; gap: 8px; margin-top: 4px">
<label for="pinNumInput" style="text-wrap: nowrap">Number of Pins: </label>
<input
id="pinNumInput"
type="number"
min="4"
max="98"
step="2"
style="text-align: center; width: 100%" />
</div>
<div style="display: flex; gap: 8px; margin-bottom: 4px">
<label for="ICNameInput" style="text-wrap: nowrap">Model Name: </label>
<input id="ICNameInput" style="text-align: center; width: 100%" />
</div>
<button onclick="addICHandler()" style="align-self: self-end;">Add Integrated Circuit</button>
</div>
<div style="display: flex">
<button onclick="myDiagram.commandHandler.groupSelection()" style="height: fit-content;">
Group Selected
</button>
<button onclick="myDiagram.commandHandler.ungroupSelection()" style="height: fit-content;">
Ungroup Selected
</button>
<button id="markPortsButton" onclick="unusedPorts()" style="height: fit-content;">Mark Unused Ports</button>
<button id="unmarkPortsButton" onclick="unmarkPorts()" style="display: none; height: fit-content;">Unmark Unused Ports</button>
</div>
</div>
<div id="description">
<p>
This GoJS diagram shows an interactive circuit schematic designer. The sample shown
is an application circuit of a steam train sound with a whistle using the
Texas Instruments SN76477 sound chip (see
<a href="https://en.wikipedia.org/wiki/Texas_Instruments_SN76477">Wikipedia</a>
for more information). The chip was used primarily for arcade game sound
effects, and it is shown here used with a 555 Timer and a speaker to
create a steam train sound effect for circuit production.
</p>
<p>
This sample allows users to construct descriptive circuit schematics using integrated
circuits with between 4 and 98 pins and a variety of standard circuit components
of various types. Users can add customized integrated circuits with the menu
below the diagram, group components using <a>Group</a> (double click to change colors),
and mark ports that are unused. Users can also edit the text on the components to change
their names or values. Developing new parts uses a reusable <a>Diagram.nodeTemplate</a>
with text and one, or optionally two, geometries that takes data from a partType binding
in the model data and constructs a part node. It has several optional bindings for
further customization as well.
</p>
<p>
A <a>Palette</a> to the left of the diagram allows the user to drag and drop new
circuit component nodes. Click and drag between the ports on each node to draw new
links, and double click the nodes to rotate them (or right click them to use the
<a>GraphObject.contextMenu</a>). The switch can also be opened by right clicking it.
Selecting any of the nodes or individual pins on the integrated circuits
will highlight links going into and out of that node's ports with the
<a>Node.findLinksConnected</a> method.
</p>
<p>
The wire paths are created manually by either dragging between ports or clicking along
the desired path with the
<a href="../extensions/PolylineLinkingTool.js">PolylineLinkingTool.js</a> extension.
They can be reshaped and adjusted with <a>LinkReshapingTool</a> and the
<a href="../extensions/OrthogonalLinkReshapingTool.js">OrthogonalLinkReshapingTool.js</a>
extension. The paths are saved in the JSON model by saving the <a>Link.points</a>
along each wire. Hover over any component to see a description with
<a>GraphObject.toolTip</a>.
</p>
</div>
<div id="JSONsection">
<button onclick="save()">Save</button>
<button onclick="load()">Load</button>
Diagram model saved in JSON format:
</div>
<textarea id="mySavedModel" style="width: 100%; height: 200px">
{ "class": "GraphLinksModel",
"linkFromPortIdProperty": "fromPort",
"linkToPortIdProperty": "toPort",
"nodeDataArray": [
{"key":-1,"category":"IC","name":"555","pinArrays":[[{"key":1,"side":"left"},{"key":2,"side":"left"},{"key":3,"side":"left"},{"key":4,"side":"left"}],[{"key":8,"side":"right"},{"key":7,"side":"right"},{"key":6,"side":"right"},{"key":5,"side":"right"}]],"orientation":"vertical","loc":"-294.5 -110","group":-77},
{"key":-2,"category":"IC","name":"SN76477","pinArrays":[[{"key":1,"side":"left"},{"key":2,"side":"left"},{"key":3,"side":"left"},{"key":4,"side":"left"},{"key":5,"side":"left"},{"key":6,"side":"left"},{"key":7,"side":"left"},{"key":8,"side":"left"},{"key":9,"side":"left"},{"key":10,"side":"left"},{"key":11,"side":"left"},{"key":12,"side":"left"},{"key":13,"side":"left"},{"key":14,"side":"left"}],[{"key":28,"side":"right"},{"key":27,"side":"right"},{"key":26,"side":"right"},{"key":25,"side":"right"},{"key":24,"side":"right"},{"key":23,"side":"right"},{"key":22,"side":"right"},{"key":21,"side":"right"},{"key":20,"side":"right"},{"key":19,"side":"right"},{"key":18,"side":"right"},{"key":17,"side":"right"},{"key":16,"side":"right"},{"key":15,"side":"right"}]],"orientation":"horizontal","loc":"90 -120"},
{"key":-3,"category":"part","partType":"resistor","toolTip":"Resistor","text":"100 k","orientation":"vertical","loc":"-514.5 -240","group":-77},
{"key":-4,"category":"junction","loc":"-514.5 -200","group":-77},
{"key":-5,"category":"part","partType":"resistor","toolTip":"Resistor","text":"100 k","orientation":"vertical","loc":"-514.5 -160","group":-77},
{"key":-6,"category":"junction","loc":"-514.5 -100","group":-77},
{"key":-7,"category":"part","partType":"capacitorPolarized","toolTip":"Polarized Capacitor","text":"220 pF","orientation":"vertical","loc":"-514.5 -50","group":-77},
{"key":-8,"category":"junction","loc":"-514.5 0","group":-77},
{"key":-9,"category":"junction","loc":"-424.5 -30","group":-77},
{"key":-10,"category":"part","partType":"pushSwitch","toolTip":"Push Switch","text":null,"strokeWidth":1,"orientation":"horizontal","loc":"-234.5 -290","group":-77},
{"key":-11,"category":"junction","loc":"-414.5 -270","group":-77},
{"key":-12,"category":"junction","loc":"-184.5 -250","group":-77},
{"key":-13,"category":"junction","loc":"-110 -250"},
{"key":-14,"category":"junction","loc":"-80 -290"},
{"key":-15,"category":"part","partType":"resistor","toolTip":"Resistor","text":"5 k","orientation":"verticalFlipped","loc":"-80 -320"},
{"key":-16,"category":"junction","loc":"-50 -360"},
{"key":-17,"category":"junction","loc":"-20 -360"},
{"key":-18,"category":"part","partType":"capacitorPolarized","toolTip":"Polarized Capacitor","text":".1 \u03bcF","orientation":"verticalFlipped","loc":"100 -320"},
{"key":-19,"category":"junction","loc":"100 -360"},
{"key":-20,"category":"junction","loc":"150 -360"},
{"key":-21,"category":"part","partType":"potentiometer","toolTip":"Potentiometer","text":"1M","orientation":"verticalFlipped","loc":"150 -320"},
{"key":-22,"category":"part","partType":"resistor","toolTip":"Resistor","text":"100 k","orientation":"vertical","loc":"200 -320"},
{"key":-23,"category":"junction","loc":"160 -250"},
{"key":-24,"category":"part","partType":"resistor","toolTip":"Resistor","text":"47 k","orientation":"vertical","loc":"270 -320"},
{"key":-25,"category":"junction","loc":"270 -360"},
{"key":-26,"category":"part","partType":"capacitorPolarized","toolTip":"Polarized Capacitor","text":".1 \u03bcF","orientation":"verticalFlipped","loc":"370 -320"},
{"key":-27,"category":"junction","loc":"370 -360"},
{"key":-28,"category":"junction","loc":"310 -250"},
{"key":-29,"category":"label","text":"5 V","loc":"330 -260"},
{"key":-30,"category":"junction","loc":"570 -320"},
{"key":-31,"category":"part","partType":"resistor","toolTip":"Resistor","text":"77 k","orientation":"horizontal","loc":"510 -320"},
{"key":-32,"category":"part","partType":"resistor","toolTip":"Resistor","text":"68 k","orientation":"horizontal","loc":"510 -280"},
{"key":-33,"category":"junction","loc":"450 -280"},
{"key":-35,"category":"junction","loc":"450 -250"},
{"key":-36,"category":"voltDiv","name":"7805","orientation":"verticalFlipped","loc":"450 -150"},
{"key":-37,"category":"junction","loc":"570 -150"},
{"key":-38,"category":"junction","loc":"450 0"},
{"key":-39,"category":"label","text":"9 V","loc":"430 -10"},
{"key":-40,"category":"part","partType":"bjtNPN","toolTip":"NPN Bipolar Junction Transistor","text":"2N3904","orientation":"vertical","loc":"450 60"},
{"key":-41,"category":"junction","loc":"390 60"},
{"key":-42,"category":"part","partType":"diodeStandard","toolTip":"Standard Diode","text":"1N4148","fill":"black","orientation":"vertical","loc":"390 120"},
{"key":-43,"category":"part","partType":"diodeStandard","toolTip":"Standard Diode","text":"1N4148","fill":"black","orientation":"vertical","loc":"390 200"},
{"key":-44,"category":"part","partType":"resistor","toolTip":"Resistor","text":"1.5\nOhm","orientation":"vertical","loc":"510 120"},
{"key":-45,"category":"part","partType":"resistor","toolTip":"Resistor","text":"1.5\nOhm","orientation":"vertical","loc":"510 200"},
{"key":-46,"category":"junction","loc":"510 160"},
{"key":-47,"category":"part","partType":"bjtPNP","toolTip":"PNP Bipolar Junction Transistor","text":"2N3906","orientation":"vertical","loc":"450 260"},
{"key":-48,"category":"junction","loc":"390 260"},
{"key":-49,"category":"junction","loc":"450 340"},
{"key":-50,"category":"part","partType":"resistor","toolTip":"Resistor","text":"10 k","orientation":"vertical","loc":"330 290"},
{"key":-51,"category":"junction","loc":"330 340"},
{"key":-52,"category":"part","partType":"resistor","toolTip":"Resistor","text":"330 k","orientation":"vertical","loc":"10 290"},
{"key":-53,"category":"part","partType":"resistor","toolTip":"Resistor","text":"47 k","orientation":"verticalFlipped","loc":"-20 290"},
{"key":-54,"category":"junction","loc":"10 340"},
{"key":-55,"category":"junction","loc":"-20 340"},
{"key":-56,"category":"junction","loc":"-110 20"},
{"key":-57,"category":"junction","loc":"-80 20"},
{"key":-58,"category":"junction","loc":"-140 20"},
{"key":-59,"category":"part","partType":"capacitorPolarized","toolTip":"Polarized Capacitor","text":"100 \u03bcF","orientation":"vertical","loc":"90 290"},
{"key":-60,"category":"junction","loc":"90 340"},
{"key":-61,"category":"junction","loc":"230 340"},
{"key":-62,"category":"part","partType":"resistor","toolTip":"Resistor","text":"100 k","orientation":"vertical","loc":"260 290"},
{"key":-63,"category":"part","partType":"resistor","toolTip":"Resistor","text":"100 k","orientation":"verticalFlipped","loc":"230 290"},
{"key":-64,"category":"junction","loc":"260 340"},
{"key":-65,"category":"part","partType":"resistor","toolTip":"Resistor","text":"47 k","orientation":"vertical","loc":"320 90"},
{"key":-66,"category":"junction","loc":"170 340"},
{"key":-67,"category":"junction","isHollow":true,"loc":"680 0.13522086576601566","group":-76},
{"key":-68,"category":"junction","isHollow":true,"loc":"680 340.1352208657661","group":-76},
{"key":-69,"category":"label","text":"Plus\n9 V\nInput","loc":"710 0.13522086576601566","group":-76},
{"key":-70,"category":"label","text":"Minus","loc":"710 340.1352208657661","group":-76},
{"key":-71,"category":"part","partType":"speaker","toolTip":"Speaker","text":null,"strokeWidth":1,"orientation":"horizontal","loc":"690 180.135220865766","group":-76},
{"key":-72,"category":"junction","loc":"570 340"},
{"key":-73,"category":"junction","loc":"640 340"},
{"key":-74,"category":"label","text":"Speaker","loc":"700 120.13522086576603","group":-76},
{"key":-75,"category":"label","text":"Whistle","loc":"-284.5 -310","group":-77},
{"text":"555 Timer Section","color":"#029ffa10","isGroup":true,"key":-77},
{"text":"Sound/Input","color":"#02fa7610","isGroup":true,"key":-76}
],
"linkDataArray": [
{"from":-4,"to":-3,"fromPort":"top","toPort":"out","points":[-514.5,-205,-514.5,-215,-514.5,-139.25,-514.5,-139.25,-514.5,-213.5,-514.5,-223.5]},
{"from":-4,"to":-5,"fromPort":"bottom","toPort":"in","points":[-514.5,-195,-514.5,-185,-514.5,-75.75,-514.5,-75.75,-514.5,-186.5,-514.5,-176.5]},
{"from":-5,"to":-6,"fromPort":"out","toPort":"top","points":[-514.5,-143.5,-514.5,-133.5,-514.5,-104.25,-514.5,-104.25,-514.5,-115,-514.5,-105]},
{"from":-6,"to":-7,"fromPort":"bottom","toPort":"in","points":[-514.5,-95,-514.5,-85,-514.5,-77.75,-514.5,-77.75,-514.5,-70.5,-514.5,-60.5]},
{"from":-7,"to":-8,"fromPort":"out","toPort":"top","points":[-514.5,-39.5,-514.5,-29.5,-514.5,-27.25,-514.5,-27.25,-514.5,-15,-514.5,-5]},
{"from":-8,"to":-1,"fromPort":"right","toPort":"1","points":[-509.5,0,-499.5,0,-453.80859375,0,-453.80859375,-160,-391,-160,-381,-160]},
{"from":-9,"to":-6,"fromPort":"left","toPort":"right","points":[-429.5,-30,-439.5,-30,-437.5546875,-30,-437.5546875,-100,-499.5,-100,-509.5,-100]},
{"from":-9,"to":-1,"fromPort":"top","toPort":"2","points":[-424.5,-35,-424.5,-45,-424.5,-130,-391,-130,-391,-130,-381,-130]},
{"from":-11,"to":-1,"fromPort":"bottom","toPort":"4","points":[-414.5,-265,-414.5,-255,-414.5,-70,-391,-70,-391,-70,-381,-70]},
{"from":-1,"to":-10,"fromPort":"3","toPort":"in","points":[-381,-100,-391,-100,-391,-226.6484375,-264.5,-226.6484375,-264.5,-290,-254.5,-290]},
{"from":-1,"to":-12,"fromPort":"8","toPort":"bottom","points":[-208,-160,-198,-160,-184.5,-160,-184.5,-235,-184.5,-235,-184.5,-245]},
{"from":-11,"to":-12,"fromPort":"right","toPort":"left","points":[-409.5,-270,-399.5,-270,-299.5,-270,-299.5,-250,-199.5,-250,-189.5,-250]},
{"from":-2,"to":-13,"fromPort":"28","toPort":"bottom","points":[-110,-206.5,-110,-216.5,-110,-225.75,-110,-225.75,-110,-235,-110,-245]},
{"from":-10,"to":-14,"fromPort":"out","toPort":"left","points":[-214.5,-290,-204.5,-290,-142.5,-290,-142.5,-290,-95,-290,-85,-290]},
{"from":-14,"to":-2,"fromPort":"bottom","toPort":"27","points":[-80,-285,-80,-275,-80,-240.75,-80,-240.75,-80,-216.5,-80,-206.5]},
{"from":-2,"to":-16,"fromPort":"26","toPort":"bottom","points":[-50,-206.5,-50,-216.5,-50,-275.75,-50,-275.75,-50,-345,-50,-355]},
{"from":-2,"to":-17,"fromPort":"25","toPort":"bottom","points":[-20,-206.5,-20,-216.5,-20,-275.75,-20,-275.75,-20,-345,-20,-355]},
{"from":-14,"to":-15,"fromPort":"top","toPort":"in","points":[-80,-295,-80,-305,-80,-305,-80,-293.5,-80,-293.5,-80,-303.5]},
{"from":-16,"to":-17,"fromPort":"right","toPort":"left","points":[-45,-360,-35,-360,-35,-360,-35,-360,-35,-360,-25,-360]},
{"from":-2,"to":-18,"fromPort":"21","toPort":"in","points":[100,-206.5,100,-216.5,100,-258,100,-258,100,-299.5,100,-309.5]},
{"from":-19,"to":-18,"fromPort":"bottom","toPort":"out","points":[100,-355,100,-345,100,-345,100,-340.5,100,-340.5,100,-330.5]},
{"from":-15,"to":-16,"fromPort":"out","toPort":"left","points":[-80,-336.5,-80,-346.5,-80,-360,-72.5,-360,-65,-360,-55,-360]},
{"from":-17,"to":-19,"fromPort":"right","toPort":"left","points":[-15,-360,-5,-360,40,-360,40,-360,85,-360,95,-360]},
{"from":-21,"to":-20,"fromPort":"out","toPort":"bottom","points":[150,-336.5,150,-346.5,150,-346.5,150,-345,150,-345,150,-355]},
{"from":-19,"to":-20,"fromPort":"right","toPort":"left","points":[105,-360,115,-360,125,-360,125,-360,135,-360,145,-360]},
{"from":-21,"to":-21,"fromPort":"in","toPort":"wiper","points":[150,-303.5,150,-293.5,150,-290.5,178.15,-290.5,178.15,-320,168.15,-320]},
{"from":-22,"to":-21,"fromPort":"in","toPort":"wiper","points":[200,-336.5,200,-346.5,178.1875,-346.5,178.1875,-320,178.15,-320,168.15,-320]},
{"from":-22,"to":-2,"fromPort":"out","toPort":"20","points":[200,-303.5,200,-293.5,200,-276.515625,130,-276.515625,130,-216.5,130,-206.5]},
{"from":-2,"to":-23,"fromPort":"19","toPort":"bottom","points":[160,-206.5,160,-216.5,160,-225.75,160,-225.75,160,-235,160,-245]},
{"from":-13,"to":-23,"fromPort":"right","toPort":"left","points":[-105,-250,-95,-250,25,-250,25,-250,145,-250,155,-250]},
{"from":-11,"to":-3,"fromPort":"left","toPort":"in","points":[-419.5,-270,-429.5,-270,-514.5,-270,-514.5,-266.5,-514.5,-266.5,-514.5,-256.5]},
{"from":-2,"to":-24,"fromPort":"18","toPort":"out","points":[190,-206.5,190,-216.5,190,-267.45703125,270,-267.45703125,270,-293.5,270,-303.5]},
{"from":-24,"to":-25,"fromPort":"in","toPort":"bottom","points":[270,-336.5,270,-346.5,270,-346.5,270,-345,270,-345,270,-355]},
{"from":-20,"to":-25,"fromPort":"right","toPort":"left","points":[155,-360,165,-360,210,-360,210,-360,255,-360,265,-360]},
{"from":-25,"to":-27,"fromPort":"right","toPort":"left","points":[275,-360,285,-360,330,-360,330,-360,355,-360,365,-360]},
{"from":-26,"to":-27,"fromPort":"out","toPort":"bottom","points":[370,-330.5,370,-340.5,370,-342.75,370,-342.75,370,-345,370,-355]},
{"from":-23,"to":-28,"fromPort":"right","toPort":"left","points":[165,-250,175,-250,240,-250,240,-250,295,-250,305,-250]},
{"from":-31,"to":-30,"fromPort":"out","toPort":"left","points":[526.5,-320,536.5,-320,545.75,-320,545.75,-320,555,-320,565,-320]},
{"from":-33,"to":-32,"fromPort":"right","toPort":"in","points":[455,-280,465,-280,474.25,-280,474.25,-280,483.5,-280,493.5,-280]},
{"from":-2,"to":-33,"fromPort":"16","toPort":"left","points":[250,-206.5,250,-216.5,250,-234.421875,404.3203125,-234.421875,404.3203125,-280,435,-280,445,-280]},
{"from":-2,"to":-26,"fromPort":"17","toPort":"in","points":[220,-206.5,220,-216.5,220,-222.6640625,370,-222.6640625,370,-299.5,370,-309.5]},
{"from":-28,"to":-35,"fromPort":"right","toPort":"left","points":[315,-250,325,-250,405,-250,405,-250,435,-250,445,-250]},
{"from":-32,"to":-35,"fromPort":"out","toPort":"right","points":[526.5,-280,536.5,-280,536.5,-250,500.75,-250,465,-250,455,-250]},
{"from":-30,"to":-37,"fromPort":"bottom","toPort":"top","points":[570,-315,570,-305,570,-240,570,-240,570,-165,570,-155]},
{"from":-36,"to":-37,"fromPort":"ground","toPort":"left","points":[469,-150,479,-150,517,-150,517,-150,555,-150,565,-150]},
{"from":-35,"to":-36,"fromPort":"bottom","toPort":"out","points":[450,-245,450,-235,450,-209.5,450,-209.5,450,-184,450,-174]},
{"from":-31,"to":-33,"fromPort":"in","toPort":"top","points":[493.5,-320,483.5,-320,450,-320,450,-307.5,450,-295,450,-285]},
{"from":-36,"to":-38,"fromPort":"in","toPort":"top","points":[450,-126,450,-116,450,-65.5,450,-65.5,450,-15,450,-5]},
{"from":-2,"to":-38,"fromPort":"14","toPort":"left","points":[280,-33.5,280,-23.5,280,0,435,0,435,0,445,0]},
{"from":-38,"to":-40,"fromPort":"bottom","toPort":"collector","points":[450,5,450,15,450,24.25,450,24.25,450,33.5,450,43.5]},
{"from":-41,"to":-40,"fromPort":"right","toPort":"base","points":[395,60,405,60,417.75,60,417.75,60,420.5,60,430.5,60]},
{"from":-2,"to":-41,"fromPort":"13","toPort":"top","points":[250,-33.5,250,-23.5,250,20.625,390,20.625,390,45,390,55]},
{"from":-41,"to":-42,"fromPort":"bottom","toPort":"in","points":[390,65,390,75,390,87.25,390,87.25,390,99.5,390,109.5]},
{"from":-40,"to":-44,"fromPort":"emitter","toPort":"in","points":[450,76.5,450,86.5,450,90,510,90,510,93.5,510,103.5]},
{"from":-44,"to":-46,"fromPort":"out","toPort":"top","points":[510,136.5,510,146.5,510,146.5,510,145,510,145,510,155]},
{"from":-45,"to":-46,"fromPort":"in","toPort":"bottom","points":[510,183.5,510,173.5,510,173.5,510,175,510,175,510,165]},
{"from":-42,"to":-43,"fromPort":"out","toPort":"in","points":[390,130.5,390,140.5,390,160,390,160,390,179.5,390,189.5]},
{"from":-45,"to":-47,"fromPort":"out","toPort":"collector","points":[510,216.5,510,226.5,510,235,450,235,450,233.5,450,243.5]},
{"from":-43,"to":-48,"fromPort":"out","toPort":"top","points":[390,210.5,390,220.5,390,237.75,390,237.75,390,245,390,255]},
{"from":-48,"to":-47,"fromPort":"right","toPort":"base","points":[395,260,405,260,412.75,260,412.75,260,420.5,260,430.5,260]},
{"from":-47,"to":-49,"fromPort":"emitter","toPort":"top","points":[450,276.5,450,286.5,450,310.75,450,310.75,450,325,450,335]},
{"from":-51,"to":-49,"fromPort":"right","toPort":"left","points":[335,340,345,340,385,340,385,340,435,340,445,340]},
{"from":-50,"to":-51,"fromPort":"out","toPort":"top","points":[330,306.5,330,316.5,330,316.5,330,325,330,325,330,335]},
{"from":-2,"to":-53,"fromPort":"4","toPort":"out","points":[-20,-33.5,-20,-23.5,-20,115,-20,115,-20,263.5,-20,273.5]},
{"from":-2,"to":-52,"fromPort":"5","toPort":"in","points":[10,-33.5,10,-23.5,10,115,10,115,10,263.5,10,273.5]},
{"from":-52,"to":-54,"fromPort":"out","toPort":"top","points":[10,306.5,10,316.5,10,320.75,10,320.75,10,325,10,335]},
{"from":-53,"to":-55,"fromPort":"in","toPort":"top","points":[-20,306.5,-20,316.5,-20,320.75,-20,320.75,-20,325,-20,335]},
{"from":-9,"to":-1,"fromPort":"right","toPort":"6","points":[-419.5,-30,-409.5,-30,-198,-30,-198,-65,-198,-100,-208,-100]},
{"from":-1,"to":-4,"fromPort":"7","toPort":"right","points":[-208,-130,-198,-130,-198,-200,-348.75,-200,-499.5,-200,-509.5,-200]},
{"from":-12,"to":-13,"fromPort":"right","toPort":"left","points":[-179.5,-250,-169.5,-250,-140,-250,-140,-250,-125,-250,-115,-250]},
{"from":-2,"to":-56,"fromPort":"1","toPort":"top","points":[-110,-33.5,-110,-23.5,-110,-9.25,-110,-9.25,-110,5,-110,15]},
{"from":-2,"to":-57,"fromPort":"2","toPort":"top","points":[-80,-33.5,-80,-23.5,-80,-9.25,-80,-9.25,-80,5,-80,15]},
{"from":-57,"to":-2,"fromPort":"right","toPort":"3","points":[-75,20,-65,20,-50,20,-50,-1.75,-50,-23.5,-50,-33.5]},
{"from":-58,"to":-56,"fromPort":"right","toPort":"left","points":[-135,20,-125,20,-125,20,-125,20,-125,20,-115,20]},
{"from":-56,"to":-57,"fromPort":"right","toPort":"left","points":[-105,20,-95,20,-95,20,-95,20,-95,20,-85,20]},
{"from":-8,"to":-58,"fromPort":"bottom","toPort":"left","points":[-514.5,5,-514.5,15,-514.5,20,-155,20,-155,20,-145,20]},
{"from":-58,"to":-55,"fromPort":"bottom","toPort":"left","points":[-140,25,-140,35,-140,340,-87.5,340,-35,340,-25,340]},
{"from":-59,"to":-2,"fromPort":"in","toPort":"6","points":[90,279.5,90,269.5,90,123,40,123,40,-23.5,40,-33.5]},
{"from":-54,"to":-60,"fromPort":"right","toPort":"left","points":[15,340,25,340,50,340,50,340,75,340,85,340]},
{"from":-59,"to":-60,"fromPort":"out","toPort":"top","points":[90,300.5,90,310.5,90,317.75,90,317.75,90,325,90,335]},
{"from":-63,"to":-61,"fromPort":"in","toPort":"top","points":[230,306.5,230,316.5,230,320.75,230,320.75,230,325,230,335]},
{"from":-2,"to":-63,"fromPort":"10","toPort":"out","points":[160,-33.5,160,-23.5,160,122.42968377976197,230,122.42968377976197,230,263.5,230,273.5]},
{"from":-2,"to":-62,"fromPort":"11","toPort":"in","points":[190,-33.5,190,-23.5,190,96.93006463913697,260,96.93006463913697,260,263.5,260,273.5]},
{"from":-62,"to":-64,"fromPort":"out","toPort":"top","points":[260,306.5,260,316.5,260,320.75,260,320.75,260,325,260,335]},
{"from":-2,"to":-65,"fromPort":"12","toPort":"in","points":[220,-33.5,220,-23.5,220,50.79302362351197,320,50.79302362351197,320,63.5,320,73.5]},
{"from":-2,"to":-66,"fromPort":"9","toPort":"top","points":[130,-33.5,130,-23.5,130,150.75,170,150.75,170,325,170,335]},
{"from":-66,"to":-61,"fromPort":"right","toPort":"left","points":[175,340,185,340,175,340,175,340,215,340,225,340]},
{"from":-60,"to":-66,"fromPort":"right","toPort":"left","points":[95,340,105,340,100,340,100,340,155,340,165,340]},
{"from":-55,"to":-54,"fromPort":"right","toPort":"left","points":[-15,340,-5,340,-5,340,-5,340,-5,340,5,340]},
{"from":-61,"to":-64,"fromPort":"right","toPort":"left","points":[235,340,245,340,235,340,235,340,245,340,255,340]},
{"from":-64,"to":-51,"fromPort":"right","toPort":"left","points":[265,340,275,340,310,340,310,340,315,340,325,340]},
{"from":-48,"to":-50,"fromPort":"left","toPort":"in","points":[385,260,375,260,330,260,330,261.75,330,263.5,330,273.5]},
{"from":-38,"to":-67,"fromPort":"right","toPort":"left","points":[455,0,465,0,640,0,640,0.13522086576601566,665,0.13522086576601566,675,0.13522086576601566]},
{"from":-46,"to":-71,"fromPort":"right","toPort":"in","points":[515,160,525,160,626,160,626,160.135220865766,667,160.135220865766,677,160.135220865766]},
{"from":-37,"to":-72,"fromPort":"bottom","toPort":"top","points":[570,-145,570,-135,570,95,570,95,570,325,570,335]},
{"from":-49,"to":-72,"fromPort":"right","toPort":"left","points":[455,340,465,340,510,340,510,340,555,340,565,340]},
{"from":-72,"to":-73,"fromPort":"right","toPort":"left","points":[575,340,585,340,620,340,620,340,625,340,635,340]},
{"from":-65,"to":-46,"fromPort":"out","toPort":"left","points":[320,106.5,320,116.5,320,160,495,160,495,160,505,160]},
{"from":-27,"to":-30,"fromPort":"right","toPort":"top","points":[375,-360,385,-360,570,-360,570,-347.5,570,-335,570,-325]},
{"from":-73,"to":-71,"fromPort":"top","toPort":"out","points":[640,335,640,325,640,190.135220865766,653.5,190.135220865766,667,190.135220865766,677,190.135220865766]},
{"from":-73,"to":-68,"fromPort":"right","toPort":"left","points":[645,340,655,340,660,340,660,340.1352208657661,665,340.1352208657661,675,340.1352208657661]}
]}
</textarea>
</div>
</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 -->
<h4>Palette</h4>
<p>
A <a href="../api/symbols/Palette.html" target="api">Palette</a> is a subclass of <a href="../api/symbols/Diagram.html" target="api">Diagram</a> that is used to display a number of <a href="../api/symbols/Part.html" target="api">Part</a>s that
can be dragged into the diagram that is being modified by the user.
The initialization of a <a href="../api/symbols/Palette.html" target="api">Palette</a> is just like the initialization of any <a href="../api/symbols/Diagram.html" target="api">Diagram</a>.
Like Diagrams, you can have more than one Palette on the page at the same time.
</p>
<p>
More information can be found in the <a href="../intro/palette.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#palette">Related samples</a>
</p>
<hr>
<!-- 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>Links</h4>
<p>
The <a href="../api/symbols/Link.html" target="api">Link</a> class is used to implement a visual relationship between nodes.
Links are normally created by the presence of link data objects in the <a href="../api/symbols/GraphLinksModel.html#linkDataArray" target="api">GraphLinksModel.linkDataArray</a>
or by a parent key reference as the value of the <a href="../api/symbols/TreeModel.html#nodeParentKeyProperty" target="api">TreeModel.nodeParentKeyProperty</a> of a node data object
in a <a href="../api/symbols/TreeModel.html" target="api">TreeModel</a>.
More information can be found in the <a href="../intro/links.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#links">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Ports in Nodes</h4>
<p>
Specific elements of a Node at which links may connect are called <i>ports</i>.
There may be any number of ports in a node. By default there is just one port, the whole node,
which results in the effect of having the whole node act as the port.
Port-like GraphObjects can only be in <a href="../api/symbols/Node.html" target="api">Node</a>s or <a href="../api/symbols/Group.html" target="api">Group</a>s, not in <a href="../api/symbols/Link.html" target="api">Link</a>s or <a href="../api/symbols/Adornment.html" target="api">Adornment</a>s or simple <a href="../api/symbols/Part.html" target="api">Part</a>s.
</p>
<p>
More information can be found in the <a href="../intro/ports.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#ports">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Groups</h4>
<p>
The <a href="../api/symbols/Group.html" target="api">Group</a> class is used to treat a collection of <a href="../api/symbols/Node.html" target="api">Node</a>s and <a href="../api/symbols/Link.html" target="api">Link</a>s as if they were a single <a href="../api/symbols/Node.html" target="api">Node</a>.
Those nodes and links are members of the group; together they constitute a subgraph.
</p>
<p>
A subgraph is <em>not</em> another <a href="../api/symbols/Diagram.html" target="api">Diagram</a>, so there is no separate HTML Div element for the subgraph of a group.
All of the <a href="../api/symbols/Part.html" target="api">Part</a>s that are members of a <a href="../api/symbols/Group.html" target="api">Group</a> belong to the same Diagram as the Group.
There can be links between member nodes and nodes outside of the group as well as links between the group itself and other nodes.
There can even be links between member nodes and the containing group itself.
</p>
<p>
More information can be found in the <a href="../intro/groups.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#groups">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>