create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
983 lines (905 loc) • 42.7 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="A simple logic circuit editor and simulator." />
<meta itemprop="description" content="A simple logic circuit editor and simulator." />
<meta property="og:description" content="A simple logic circuit editor and simulator." />
<meta name="twitter:description" content="A simple logic circuit editor and simulator." />
<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="Logic Circuit Diagram with Simple Simulation" />
<meta property="og:title" content="Logic Circuit Diagram with Simple Simulation" />
<meta name="twitter:title" content="Logic Circuit Diagram with Simple Simulation" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/logiccircuit.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/logiccircuit.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/logiccircuit.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/logicCircuit.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/logicCircuit.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Logic Circuit Diagram with Simple Simulation | 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/Figures.js"></script>
<script src="../extensions/PolylineLinkingTool.js"></script>
<script src="../extensions/OrthogonalLinkReshapingTool.js"></script>
<script id="code">
const red = '#b91c1c';
const red2 = '#fca5a5';
const green = '#15803d';
const green2 = '#86efac';
const gray = '#cbd5e1';
const darkGray = '#334155';
const CELLSIZE = 10;
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
'draggingTool.isGridSnapEnabled': true, // dragged nodes will snap to a grid of 10x10 cells
linkReshapingTool: new OrthogonalLinkReshapingTool(),
'grid.gridCellSize': new go.Size(CELLSIZE, CELLSIZE),
'undoManager.isEnabled': true,
'grid.visible': true,
initialAutoScale: go.AutoScale.Uniform
});
// Enables the polyline linking tool to manually add link points
myDiagram.toolManager.linkingTool = new PolylineLinkingTool();
myDiagram.toolManager.linkingTool.temporaryLink.routing = go.Routing.Orthogonal;
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener('Modified', (e) => {
const button = document.getElementById('saveModel');
if (button) button.disabled = !myDiagram.isModified;
const idx = document.title.indexOf('*');
if (myDiagram.isModified) {
if (idx < 0) document.title += '*';
} else {
if (idx >= 0) document.title = document.title.slice(0, idx);
}
});
// create a new Palette in the HTML DIV element "palette"
const palette = new go.Palette('palette', {
layout: new go.GridLayout({
wrappingColumn: 1,
spacing: new go.Size(0, 20)
}),
});
// creates relinkable Links that will avoid crossing Nodes when possible and will jump over other Links in their paths
myDiagram.linkTemplate = new go.Link({
routing: go.Routing.Orthogonal,
adjusting: go.LinkAdjusting.End, // Allows nodes to be dragged while keeping most link points
curve: go.Curve.JumpOver,
corner: 3,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
selectionAdorned: false, // Links are not adorned when selected so that their color remains visible
shadowOffset: new go.Point(0, 0),
shadowBlur: 5,
shadowColor: 'blue',
layerName: 'Background'
})
.bindTwoWay('points') // Saves link points to model data
.bindObject('isShadowed', 'isSelected')
.add(new go.Shape({ name: 'SHAPE', strokeWidth: 2, stroke: red }));
// node template helpers
const sharedToolTip = go.GraphObject.build('ToolTip', {
'Border.figure': 'RoundedRectangle'
}).add(new go.TextBlock({ margin: 2 }).bind('text', '', (d) => d.category));
// define some common property settings
function nodeStyle() {
return {
locationSpot: go.Spot.Center,
selectionAdorned: false,
shadowOffset: new go.Point(0, 0),
shadowBlur: 15,
shadowColor: 'blue',
toolTip: sharedToolTip
};
}
function applyNodeBindings(node) {
node.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify);
node.bindObject('isShadowed', 'isSelected');
return node;
}
function shapeStyle() {
return {
name: 'NODESHAPE',
fill: gray,
stroke: darkGray,
desiredSize: new go.Size(40, 40),
strokeWidth: 2
};
}
function portStyle(input, spot) {
return {
figure: 'Rectangle',
desiredSize: new go.Size(4, 4),
fill: darkGray,
stroke: 'transparent',
strokeWidth: 6,
fromLinkable: !input,
fromSpot: spot ?? new go.Spot(1, 0.5, -3, 0),
toSpot: spot ?? new go.Spot(0, 0.5, 3, 0),
toLinkable: input,
toMaxLinks: 1,
cursor: 'pointer'
};
}
// define templates for each type of node
const inputTemplate = new go.Node('Spot', nodeStyle())
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.set({
cursor: 'pointer',
margin: new go.Margin(-15, 0, 0, 0),
click: (e, obj) => {
if (e.diagram instanceof go.Palette) return;
e.diagram.startTransaction('Toggle Input');
const isOn = !obj.data.isOn;
myDiagram.model.setDataProperty(obj.data, 'isOn', isOn);
updateStates();
e.diagram.commitTransaction('Toggle Input');
},
mouseEnter: (e, obj) => {
while (obj.part && obj.part !== obj) obj = obj.part;
e.diagram.commit(diag => {
obj.isShadowed = true;
})
},
mouseLeave: (e, obj) => {
while (obj.part && obj.part !== obj) obj = obj.part;
e.diagram.commit(diag => {
obj.isShadowed = false;
})
}
})
.add(
new go.Shape(shapeStyle())
.set({
fill: go.Brush.lighten(green),
margin: 3,
strokeWidth: 1.5,
desiredSize: new go.Size(NaN, NaN),
scale: 1.75,
geometry: go.Geometry.parse('F M19.5 3 L19.875 3 C20.4963 3 21 3.5037000000000003 21 4.125 L21 6.375 C21 6.9963 20.4963 7.5 19.875 7.5 L19.5 7.5 M2.25 10.5 L17.25 10.5 C18.4926 10.5 19.5 9.4926 19.5 8.25 L19.5 2.25 C19.5 1.0073600000000003 18.4926 0 17.25 0 L2.25 0 C1.0073599999999998 0 0 1.0073600000000003 0 2.25 L0 8.25 C0 9.4926 1.0073599999999998 10.5 2.25 10.5z', true)
})
.bind('fill', 'isOn', isOn => go.Brush.lighten(isOn ? green : red)),
new go.Shape('BpmnEventError', {
alignment: new go.Spot(0.5, 0.5, -1, 0),
width: 18,
height: 10,
fill: green2,
strokeWidth: 0
})
.bind('fill', 'isOn', isOn => isOn ? green2 : red2),
new go.Shape(portStyle(false)) // the only port
.set({
opacity: 0,
portId: '',
alignment: new go.Spot(1, 0.5, -2, 0)
})
);
const switchTemplate = new go.Node('Spot', nodeStyle())
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.set({
isShadowed: true,
margin: new go.Margin(-35, 0, 0, 0),
mouseEnter: (e, obj) => {
while (obj.part && obj !== obj.part) obj = obj.part;
e.diagram.commit(diag => {
// Shadows the shadow shape when the switch is closed, shadows the
// switch itself when its open
if (!obj.data.isOn) {
obj.findObject('NODESHAPE').shadowVisible = true;
} else {
obj.findObject('SHADOWSHAPE').visible = true;
}
})
},
mouseLeave: (e, obj) => {
while (obj.part && obj !== obj.part) obj = obj.part;
e.diagram.commit(diag => {
if (!obj.data.isOn) {
obj.findObject('NODESHAPE').shadowVisible = false;
} else {
obj.findObject('SHADOWSHAPE').visible = false;
}
})
}
})
.add(
// Creates shadow for the switch shape since it is being clipped when closed
new go.Shape('Rectangle', {
name: 'SHADOWSHAPE',
desiredSize: new go.Size(40, 5),
fill: 'white',
stroke: null,
alignment: go.Spot.Center,
shadowVisible: true,
visible: false,
shadowBlur: 15,
shadowOffset: new go.Point(0, 0),
shadowColor: 'blue'
}),
new go.Panel('Horizontal', {
// this prevents the ports from moving when the shape rotates
minSize: new go.Size(42, 60)
})
.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(shapeStyle())
.set({
strokeWidth: 1,
fill: green,
width: 40,
height: 4,
position: new go.Point(40,0),
shadowVisible: false
})
)
.bind('angle', 'isOn', isOn => isOn ? 359.99 : 359.99 - 30)
.trigger('angle', {
duration: 250,
easing: go.Animation.EaseOutQuad,
finished: updateStates
})
)
),
// this rectangle is the clickable area
new go.Shape('Rectangle', {
fill: 'transparent',
strokeWidth: 0,
width: 40,
height: 30,
alignment: go.Spot.Center,
alignmentFocus: new go.Spot(0.5, 1, 0, -8),
cursor: 'pointer',
click: (e, obj) => {
if (e.diagram instanceof go.Palette) return;
e.diagram.startTransaction('Toggle Switch');
while (obj.part && obj.part !== obj) obj = obj.part; // find node
const shp = obj.findObject('NODESHAPE');
const isOn = !obj.data.isOn;
myDiagram.model.setDataProperty(obj.data, 'isOn', isOn);
obj.findObject('SHADOWSHAPE').visible = false;
e.diagram.commitTransaction('Toggle Switch');
if (!obj.data.isOn)
updateStates();
}
}),
// ports
new go.Shape(portStyle(false)) // the only port
.set({
figure: 'Circle',
portId: 'out',
desiredSize: new go.Size(7, 7),
alignment: new go.Spot(1, 0.5),
fromSpot: new go.Spot(1, 0.5, -5, 0),
toSpot: new go.Spot(1, 0.5, -5, 0)
}),
new go.Shape(portStyle(true)) // the only port
.set({
figure: 'Circle',
portId: 'in',
desiredSize: new go.Size(7, 7),
alignment: new go.Spot(0, 0.5),
fromSpot: new go.Spot(0, 0.5, 5, 0),
toSpot: new go.Spot(0, 0.5, 5, 0)
})
);
// brush for the "light" in the LED
const outBrush = new go.Brush('Radial', {
0.0: 'rgba(255, 255, 255, 0.2)',
0.5: 'rgba(250,215,87,0.8)',
0.75: 'rgba(250,215,87,0.5)',
0.85: 'rgba(250,215,87,0.2)',
0.95: 'rgba(250,215,87,0.1)',
1: 'rgba(250,215,87,0)',
start: new go.Spot(0.5, 0.8)
})
const outputTemplate = new go.Node('Spot', nodeStyle())
.set({
isShadowed: true
})
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.add(
new go.Panel('Spot')
.add(
new go.Shape('RoundedRectangle', {
fill: 'transparent',
parameter1: Infinity,
parameter2: 0b0011, // top rounded
width: 25,
height: 22,
strokeWidth: 2,
shadowVisible: false
}),
new go.Shape('Rectangle', {
alignment: go.Spot.Bottom,
alignmentFocus: new go.Spot(0.5, 0.8),
strokeWidth: 0,
fill: null,
width: 40,
height: 43
})
.bind('fill', 'isOn', isOn => isOn ? outBrush : 'transparent'),
new go.Shape('Rectangle', shapeStyle())
.set({
width: 32,
height: 15,
alignment: go.Spot.Bottom,
alignmentFocus: new go.Spot(0.5, 0, 0, 2)
})
.bindObject('shadowVisible', 'isSelected')
),
new go.Shape(portStyle(true, new go.Spot(0.5, 1, 0, -3))).set({
// the only port
portId: '',
alignment: new go.Spot(0.5, 1)
})
);
// Nodes space their ports based on the cell size so that links can line up horizontally
const andTemplate = applyNodeBindings(new go.Node('Spot', nodeStyle()))
.add(
new go.Shape('AndGate', shapeStyle()),
new go.Shape(portStyle(true)).set({
portId: 'in1',
alignment: new go.Spot(0, 0.5, 0, -CELLSIZE)
}),
new go.Shape(portStyle(true)).set({
portId: 'in2',
alignment: new go.Spot(0, 0.5, 0, CELLSIZE)
}),
new go.Shape(portStyle(false)).set({
portId: 'out',
alignment: new go.Spot(1, 0.5)
})
);
const orTemplate = applyNodeBindings(new go.Node('Spot', nodeStyle()))
.add(
new go.Shape('OrGate', shapeStyle()),
new go.Shape(portStyle(true)).set({
portId: 'in1',
alignment: new go.Spot(0.16, 0.5, 0, -CELLSIZE)
}),
new go.Shape(portStyle(true)).set({
portId: 'in2',
alignment: new go.Spot(0.16, 0.5, 0, CELLSIZE)
}),
new go.Shape(portStyle(false)).set({
portId: 'out',
alignment: new go.Spot(1, 0.5)
})
);
const xorTemplate = applyNodeBindings(new go.Node('Spot', nodeStyle()))
.add(
new go.Shape('XorGate', shapeStyle()),
new go.Shape(portStyle(true)).set({
portId: 'in1',
alignment: new go.Spot(0.26, 0.5, 0, -CELLSIZE)
}),
new go.Shape(portStyle(true)).set({
portId: 'in2',
alignment: new go.Spot(0.26, 0.5, 0, CELLSIZE)
}),
new go.Shape(portStyle(false)).set({
portId: 'out',
alignment: new go.Spot(1, 0.5)
})
);
const norTemplate = applyNodeBindings(new go.Node('Spot', nodeStyle()))
.add(
new go.Shape('NorGate', shapeStyle()),
new go.Shape(portStyle(true)).set({
portId: 'in1',
alignment: new go.Spot(0.16, 0.5, 0, -CELLSIZE)
}),
new go.Shape(portStyle(true)).set({
portId: 'in2',
alignment: new go.Spot(0.16, 0.5, 0, CELLSIZE)
}),
new go.Shape(portStyle(false)).set({
portId: 'out',
opacity: 0,
alignment: new go.Spot(1, 0.5, -5, 0)
})
);
const xnorTemplate = applyNodeBindings(new go.Node('Spot', nodeStyle()))
.add(
new go.Shape('XnorGate', shapeStyle()),
new go.Shape(portStyle(true)).set({
portId: 'in1',
alignment: new go.Spot(0.26, 0.5, 0, -CELLSIZE)
}),
new go.Shape(portStyle(true)).set({
portId: 'in2',
alignment: new go.Spot(0.26, 0.5, 0, CELLSIZE)
}),
new go.Shape(portStyle(false)).set({
portId: 'out',
opacity: 0,
alignment: new go.Spot(1, 0.5, -5, 0)
})
);
const nandTemplate = applyNodeBindings(new go.Node('Spot', nodeStyle()))
.add(
new go.Shape('NandGate', shapeStyle()),
new go.Shape(portStyle(true)).set({
portId: 'in1',
alignment: new go.Spot(0, 0.5, 0, -CELLSIZE)
}),
new go.Shape(portStyle(true)).set({
portId: 'in2',
alignment: new go.Spot(0, 0.5, 0, CELLSIZE)
}),
new go.Shape(portStyle(false)).set({
portId: 'out',
opacity: 0,
alignment: new go.Spot(1, 0.5, -5, 0)
})
);
const notTemplate = applyNodeBindings(new go.Node('Spot', nodeStyle()))
.add(
new go.Shape('Inverter', shapeStyle()),
new go.Shape(portStyle(true)).set({
portId: 'in',
alignment: new go.Spot(0, 0.5)
}),
new go.Shape(portStyle(false)).set({
portId: 'out',
opacity: 0,
alignment: new go.Spot(1, 0.5, -5, 0)
})
);
// add the templates created above to myDiagram and palette
myDiagram.nodeTemplateMap.add('input', inputTemplate);
myDiagram.nodeTemplateMap.add('switch', switchTemplate);
myDiagram.nodeTemplateMap.add('output', outputTemplate);
myDiagram.nodeTemplateMap.add('and', andTemplate);
myDiagram.nodeTemplateMap.add('or', orTemplate);
myDiagram.nodeTemplateMap.add('xor', xorTemplate);
myDiagram.nodeTemplateMap.add('not', notTemplate);
myDiagram.nodeTemplateMap.add('nand', nandTemplate);
myDiagram.nodeTemplateMap.add('nor', norTemplate);
myDiagram.nodeTemplateMap.add('xnor', xnorTemplate);
// share the template map with the Palette
palette.nodeTemplateMap = myDiagram.nodeTemplateMap;
palette.model.nodeDataArray = [
{ category: 'input', isOn: true },
{ category: 'switch', isOn: true },
{ category: 'output' },
{ category: 'and' },
{ category: 'or' },
{ category: 'xor' },
{ category: 'not' },
{ category: 'nand' },
{ category: 'nor' },
{ category: 'xnor' }
];
// load the initial diagram
load();
// continually update the diagram
loop();
}
// update the diagram every 250 milliseconds
function loop() {
setTimeout(() => {
updateStates();
loop();
}, 250);
}
// update the value and appearance of each node according to its type and input values
function updateStates() {
const oldskip = myDiagram.skipsUndoManager;
myDiagram.skipsUndoManager = true;
// do all "input" nodes first
myDiagram.nodes.each((node) => {
if (node.category === 'input') {
doInput(node);
}
});
// now we can do all other kinds of nodes
myDiagram.nodes.each((node) => {
switch (node.category) {
case 'switch':
doSwitch(node);
break;
case 'and':
doAnd(node);
break;
case 'or':
doOr(node);
break;
case 'xor':
doXor(node);
break;
case 'not':
doNot(node);
break;
case 'nand':
doNand(node);
break;
case 'nor':
doNor(node);
break;
case 'xnor':
doXnor(node);
break;
case 'output':
doOutput(node);
break;
case 'input':
break; // doInput already called, above
}
});
myDiagram.skipsUndoManager = oldskip;
}
// helper predicate
function linkIsTrue(link) {
// assume the given Link has a Shape named "SHAPE"
return link.findObject('SHAPE').stroke === green;
}
// helper function for propagating results
function setOutputLinks(node, color) {
node.findLinksOutOf().each((link) => (link.findObject('SHAPE').stroke = color));
}
// update nodes by the specific function for its type
// determine the color of links coming out of this node based on those coming in and node type
function doInput(node) {
setOutputLinks(node, node.data.isOn ? green : red);
}
function doSwitch(node) {
const linksInto = node.findLinksInto();
const color = linksInto.count > 0 && linksInto.all(linkIsTrue) ? green : red;
node.findObject('NODESHAPE').fill = color;
let ang = node.findObject('NODESHAPE').panel.angle;
let isGoodAngle = ang >= 357 || ang <= 3;
setOutputLinks(node, node.data.isOn && isGoodAngle ? color : red);
}
function doAnd(node) {
const linksInto = node.findLinksInto();
const color = linksInto.count > 0 && linksInto.all(linkIsTrue) && linksInto.count % 2 == 0 ? green : red;
setOutputLinks(node, color);
}
function doNand(node) {
const color = !node.findLinksInto().all(linkIsTrue) ? green : red;
setOutputLinks(node, color);
}
function doNot(node) {
const color = !node.findLinksInto().all(linkIsTrue) ? green : red;
setOutputLinks(node, color);
}
function doOr(node) {
const color = node.findLinksInto().any(linkIsTrue) ? green : red;
setOutputLinks(node, color);
}
function doNor(node) {
const color = !node.findLinksInto().any(linkIsTrue) ? green : red;
setOutputLinks(node, color);
}
function doXor(node) {
let truecount = 0;
node.findLinksInto().each((link) => {
if (linkIsTrue(link)) truecount++;
});
const color = truecount % 2 !== 0 ? green : red;
setOutputLinks(node, color);
}
function doXnor(node) {
let truecount = 0;
node.findLinksInto().each((link) => {
if (linkIsTrue(link)) truecount++;
});
const color = truecount % 2 === 0 ? green : red;
setOutputLinks(node, color);
}
function doOutput(node) {
// assume there is just one input link
// we just need to update the node's data.isOn
node.linksConnected.each((link) => {
myDiagram.model.setDataProperty(node.data, 'isOn', link.findObject('SHAPE').stroke == green);
});
}
// 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);
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div style="width: 100%; display: flex; justify-content: space-between">
<div
id="palette"
style="
width: 100px;
height: 600px;
margin-right: 2px;
background-color: #f3f4f6;
border: solid 1px black;
"></div>
<div id="myDiagramDiv" style="background-color: #f3f4f6; flex-grow: 1; height: 600px; border: solid 1px black"></div>
</div>
<p>
Try clicking on the switch or the battery to toggle them.
</p>
<p>
This logic circuit implements a three-bit adder (see
<a href="https://en.wikipedia.org/wiki/Adder_(electronics)">Wikipedia</a> for more information).
The switches connected to the batteries on the left side of the diagram are the inputs, and the
lamps at the top are the outputs. By opening or closing the switches, different numbers can be
added (up to 7+7=14!), with the lamps reflecting the sum of those numbers in real time.
</p>
<p>
This sample allows the user to make circuits using gates and wires, which are
updated whenever a Link is modified and at intervals by a looped setTimeout function.
</p>
<p>
The <b>updateStates</b> function calls a function to update each node according to type, which
uses the color of the links into the node to determine the color of those exiting it. Red means
zero or false; green means one or true. Clicking an input node (battery or switch) will toggle
true/false.
</p>
<p>
Mouse over a node to see its category, displayed using a shared <a>Adornment</a> set as the
tooltip. A <a>Palette</a> to the left of the main diagram allows the user to drag and drop new
nodes. These nodes can then be linked using ports which are defined on the various node
templates. Each input port can only have one input link, while output ports can have many output
links. This is controlled by the <a>GraphObject.toMaxLinks</a> property.
</p>
<div>
<div>
<button id="saveModel" 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": [
{"category":"xor","key":-1,"loc":"-210 -870"},
{"category":"xor","key":-2,"loc":"-120 -900"},
{"category":"and","key":-3,"loc":"-150 -810"},
{"category":"and","key":-4,"loc":"-150 -730"},
{"category":"switch","isOn":true,"key":-5,"loc":"-330 -880"},
{"category":"switch","isOn":true,"key":-6,"loc":"-330 -720"},
{"category":"or","key":-7,"loc":"-80 -770"},
{"category":"or","key":-8,"loc":"60 -550"},
{"category":"switch","isOn":true,"key":-9,"loc":"-230 -500"},
{"category":"switch","isOn":true,"key":-10,"loc":"-230 -690"},
{"category":"and","key":-11,"loc":"-10 -510"},
{"category":"and","key":-12,"loc":"-10 -590"},
{"category":"xor","key":-13,"loc":"60 -640"},
{"category":"xor","key":-14,"loc":"-150 -650"},
{"category":"or","key":-15,"loc":"230 -400"},
{"category":"switch","isOn":true,"key":-16,"loc":"-230 -400"},
{"category":"switch","isOn":true,"key":-17,"loc":"-230 -450"},
{"category":"and","key":-18,"loc":"-10 -390"},
{"category":"and","key":-19,"loc":"160 -430"},
{"category":"xor","key":-20,"loc":"160 -510"},
{"category":"xor","key":-21,"loc":"60 -440"},
{"category":"output","key":-22,"loc":"40 -960","isOn":true},
{"category":"output","key":-23,"loc":"90 -960","isOn":true},
{"category":"output","key":-24,"loc":"140 -960","isOn":false},
{"category":"output","key":-25,"loc":"-10 -960","isOn":true},
{"category":"switch","isOn":true,"key":-26,"loc":"-430 -680"},
{"category":"switch","isOn":true,"key":-27,"loc":"-430 -650"},
{"category":"switch","isOn":true,"key":-28,"loc":"-430 -620"},
{"category":"switch","isOn":true,"key":-29,"loc":"-430 -530"},
{"category":"switch","isOn":true,"key":-30,"loc":"-430 -500"},
{"category":"switch","isOn":true,"key":-31,"loc":"-430 -470"},
{"category":"input","isOn":true,"key":-32,"loc":"-520 -650"},
{"category":"input","isOn":true,"key":-33,"loc":"-520 -500"},
{"category":"input","isOn":false,"key":-34,"loc":"-520 -800"}
],
"linkDataArray": [
{"from":-5,"to":-1,"fromPort":"out","toPort":"in1","points":[-307.5,-880,-297.5,-880,-280.04,-880,-280.04,-880,-234.58,-880,-224.58,-880]},
{"from":-6,"to":-1,"fromPort":"out","toPort":"in2","points":[-307.5,-720,-297.5,-720,-275.8650862905213,-720,-275.8650862905213,-860,-234.58,-860,-224.58,-860]},
{"from":-1,"to":-2,"fromPort":"out","toPort":"in2","points":[-189.5,-870,-179.5,-870,-177.2874653558896,-870,-177.2874653558896,-890,-144.58,-890,-134.58,-890]},
{"from":-1,"to":-3,"fromPort":"out","toPort":"in1","points":[-189.5,-870,-179.5,-870,-177.58820721908566,-870,-177.58820721908566,-820,-183,-820,-173,-820]},
{"from":-6,"to":-4,"fromPort":"out","toPort":"in2","points":[-307.5,-720,-297.5,-720,-234.25,-720,-234.25,-720,-183,-720,-173,-720]},
{"from":-3,"to":-7,"fromPort":"out","toPort":"in1","points":[-127,-810,-117,-810,-112.89,-810,-112.89,-780,-108.78,-780,-98.78,-780]},
{"from":-4,"to":-7,"fromPort":"out","toPort":"in2","points":[-127,-730,-117,-730,-112.89,-730,-112.89,-760,-108.78,-760,-98.78,-760]},
{"from":-9,"to":-11,"fromPort":"out","toPort":"in2","points":[-207.5,-500,-197.5,-500,-179.7643563068775,-500,-179.7643563068775,-500,-43,-500,-33,-500]},
{"from":-11,"to":-8,"fromPort":"out","toPort":"in2","points":[13,-510,23,-510,28.39911648605326,-510,28.39911648605326,-540,31.22,-540,41.22,-540]},
{"from":-12,"to":-8,"fromPort":"out","toPort":"in1","points":[13,-590,23,-590,28.462564136516566,-590,28.462564136516566,-560,31.22,-560,41.22,-560]},
{"from":-10,"to":-14,"fromPort":"out","toPort":"in1","points":[-207.5,-690,-197.5,-690,-179.91225278010745,-690,-179.91225278010745,-660,-174.58,-660,-164.58,-660]},
{"from":-9,"to":-14,"fromPort":"out","toPort":"in2","points":[-207.5,-500,-197.5,-500,-179.6604290554186,-500,-179.6604290554186,-640,-174.58,-640,-164.58,-640]},
{"from":-14,"to":-13,"fromPort":"out","toPort":"in1","points":[-129.5,-650,-119.5,-650,-47.04,-650,-47.04,-650,35.42,-650,45.42,-650]},
{"from":-14,"to":-12,"fromPort":"out","toPort":"in1","points":[-129.5,-650,-119.5,-650,-42.41809671174902,-650,-42.41809671174902,-600,-43,-600,-33,-600]},
{"from":-7,"to":-13,"fromPort":"out","toPort":"in2","points":[-59.5,-770,-49.5,-770,-25.941227682576745,-770,-25.941227682576745,-630,35.42,-630,45.42,-630]},
{"from":-7,"to":-12,"fromPort":"out","toPort":"in2","points":[-59.5,-770,-49.5,-770,-52.404614822261685,-770,-52.404614822261685,-580,-43,-580,-33,-580]},
{"from":-16,"to":-18,"fromPort":"out","toPort":"in2","points":[-207.5,-400,-197.5,-400,-100.16135474889069,-400,-100.16135474889069,-380,-43,-380,-33,-380]},
{"from":-18,"to":-15,"fromPort":"out","toPort":"in2","points":[13,-390,23,-390,127.11,-390,127.11,-390,201.22,-390,211.22,-390]},
{"from":-19,"to":-15,"fromPort":"out","toPort":"in1","points":[183,-430,193,-430,191.39010469700875,-430,191.39010469700875,-410,201.22,-410,211.22,-410]},
{"from":-17,"to":-21,"fromPort":"out","toPort":"in1","points":[-207.5,-450,-197.5,-450,-20.04,-450,-20.04,-450,35.42,-450,45.42,-450]},
{"from":-16,"to":-21,"fromPort":"out","toPort":"in2","points":[-207.5,-400,-197.5,-400,-100.09531642451535,-400,-100.09531642451535,-430,35.42,-430,45.42,-430]},
{"from":-21,"to":-20,"fromPort":"out","toPort":"in1","points":[80.5,-440,90.5,-440,99.61149109468168,-440,99.61149109468168,-520,135.42,-520,145.42,-520]},
{"from":-21,"to":-19,"fromPort":"out","toPort":"in1","points":[80.5,-440,90.5,-440,98.75,-440,98.75,-440,127,-440,137,-440]},
{"from":-8,"to":-20,"fromPort":"out","toPort":"in2","points":[80.5,-550,90.5,-550,130.01322353171253,-550,130.01322353171253,-500,135.42,-500,145.42,-500]},
{"from":-8,"to":-19,"fromPort":"out","toPort":"in2","points":[80.5,-550,90.5,-550,117.28941911474058,-550,117.28941911474058,-420,127,-420,137,-420]},
{"from":-2,"to":-24,"fromPort":"out","toPort":"","points":[-99.5,-900,-89.5,-900,140,-900,140,-925.8000000000001,140,-925.8000000000001,140,-935.8000000000001]},
{"from":-13,"to":-23,"fromPort":"out","toPort":"","points":[80.5,-640,90.5,-640,90,-640,90,-925.8000000000001,90,-925.8000000000001,90,-935.8000000000001]},
{"from":-20,"to":-22,"fromPort":"out","toPort":"","points":[180.5,-510,190.5,-510,190.5,-731.9000000000001,40,-731.9000000000001,40,-925.8000000000001,40,-935.8000000000001]},
{"from":-15,"to":-25,"fromPort":"out","toPort":"","points":[250.5,-400,260.5,-400,260.5,-666.9000000000001,-10,-666.9000000000001,-10,-925.8000000000001,-10,-935.8000000000001]},
{"from":-32,"to":-26,"fromPort":"","toPort":"in","points":[-500.3125,-650,-490.3125,-650,-477.40625,-650,-477.40625,-680,-462.5,-680,-452.5,-680]},
{"from":-32,"to":-27,"fromPort":"","toPort":"in","points":[-500.3125,-650,-490.3125,-650,-477.40625,-650,-477.40625,-650,-462.5,-650,-452.5,-650]},
{"from":-32,"to":-28,"fromPort":"","toPort":"in","points":[-500.3125,-650,-490.3125,-650,-477.40625,-650,-477.40625,-620,-462.5,-620,-452.5,-620]},
{"from":-33,"to":-29,"fromPort":"","toPort":"in","points":[-500.3125,-500,-490.3125,-500,-477.40625,-500,-477.40625,-530,-462.5,-530,-452.5,-530]},
{"from":-33,"to":-30,"fromPort":"","toPort":"in","points":[-500.3125,-500,-490.3125,-500,-477.40625,-500,-477.40625,-500,-462.5,-500,-452.5,-500]},
{"from":-33,"to":-31,"fromPort":"","toPort":"in","points":[-500.3125,-500,-490.3125,-500,-477.40625,-500,-477.40625,-470,-462.5,-470,-452.5,-470]},
{"from":-26,"to":-5,"fromPort":"out","toPort":"in","points":[-407.5,-680,-397.5,-680,-385,-680,-385,-880,-362.5,-880,-352.5,-880]},
{"from":-29,"to":-6,"fromPort":"out","toPort":"in","points":[-407.5,-530,-397.5,-530,-375,-530,-375,-720,-362.5,-720,-352.5,-720]},
{"from":-34,"to":-3,"fromPort":"","toPort":"in2","points":[-500.3125,-800,-490.3125,-800,-316.65625,-800,-316.65625,-800,-183,-800,-173,-800]},
{"from":-27,"to":-10,"fromPort":"out","toPort":"in","points":[-407.5,-650,-397.5,-650,-300.1347886532465,-650,-300.1347886532465,-690,-262.5,-690,-252.5,-690]},
{"from":-30,"to":-9,"fromPort":"out","toPort":"in","points":[-407.5,-500,-397.5,-500,-326.35678231764655,-500,-326.35678231764655,-500,-262.5,-500,-252.5,-500]},
{"from":-28,"to":-17,"fromPort":"out","toPort":"in","points":[-407.5,-620,-397.5,-620,-299.7719252242147,-620,-299.7719252242147,-450,-262.5,-450,-252.5,-450]},
{"from":-31,"to":-16,"fromPort":"out","toPort":"in","points":[-407.5,-470,-397.5,-470,-324.6448525201331,-470,-324.6448525201331,-400,-262.5,-400,-252.5,-400]},
{"from":-10,"to":-11,"fromPort":"out","toPort":"in1","points":[-207.5,-690,-197.5,-690,-85.75108112090732,-690,-85.75108112090732,-520,-43,-520,-33,-520]},
{"from":-17,"to":-18,"fromPort":"out","toPort":"in1","points":[-207.5,-450,-197.5,-450,-60.6494063519714,-450,-60.6494063519714,-400,-43,-400,-33,-400]},
{"from":-5,"to":-4,"fromPort":"out","toPort":"in1","points":[-307.5,-880,-297.5,-880,-250.5495083496439,-880,-250.5495083496439,-740,-183,-740,-173,-740]},
{"from":-34,"to":-2,"fromPort":"","toPort":"in1","points":[-500.3125,-800,-490.3125,-800,-405.2145817859016,-800,-405.2145817859016,-910,-144.58,-910,-134.58,-910]}
]}
</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>ToolTips</h4>
<p>
A tooltip is an <a href="../api/symbols/Adornment.html" target="api">Adornment</a> that is shown when the mouse hovers over an object that has its <a href="../api/symbols/GraphObject.html#toolTip" target="api">GraphObject.toolTip</a> set.
The tooltip part is bound to the same data as the part itself.
</p>
<p>
It is typical to implement a tooltip as a "ToolTip" Panel holding a <a href="../api/symbols/TextBlock.html" target="api">TextBlock</a> or a Panel of TextBlocks and other objects.
Each "ToolTip" is just an "Auto" Panel <a href="../api/symbols/Adornment.html" target="api">Adornment</a> that is shadowed, and where the border is a rectangular <a href="../api/symbols/Shape.html" target="api">Shape</a> with a light gray fill.
However you can implement the tooltip as any arbitrarily complicated Adornment.
</p>
<p>
More information can be found in the <a href="../intro/tooltips.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#tooltips">Related samples</a>
</p>
<hr>
<!-- 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>Grid Patterns</h4>
<p>
<b>GoJS</b> provides functionality to display a grid of lines drawn at regular intervals.
Grid Panels can also force dragged parts to be aligned on grid points, and resize parts to be multiples of the grid cell size.
</p>
<p>
Grids are implemented using a type of <a href="../api/symbols/Panel.html" target="api">Panel</a>, <a href="../api/symbols/PanelLayout.html#Grid" target="api">Panel.Grid</a>.
Grid Panels, like most other types of Panels, can be used within <a href="../api/symbols/Node.html" target="api">Node</a>s or any other kind of <a href="../api/symbols/Part.html" target="api">Part</a>.
However when they are used as the <a href="../api/symbols/Diagram.html#grid" target="api">Diagram.grid</a>, they are effectively infinite in extent.
</p>
<p>
More information can be found in the <a href="../intro/grids.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#grid">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
</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>