create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
466 lines (411 loc) • 19 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 diagram where nodes may belong to multiple groups." />
<meta itemprop="description" content="A diagram where nodes may belong to multiple groups." />
<meta property="og:description" content="A diagram where nodes may belong to multiple groups." />
<meta name="twitter:description" content="A diagram where nodes may belong to multiple groups." />
<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="Shared States not using Groups" />
<meta property="og:title" content="Shared States not using Groups" />
<meta name="twitter:title" content="Shared States not using Groups" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/sharedstates.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/sharedstates.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/sharedstates.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/sharedStates.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/sharedStates.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Shared States not using Groups | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<div id="allSampleContent" class="p-4 w-full">
<script id="code">
// A custom layout that sizes each "Super" node to be big enough to cover all of it member nodes
class CustomLayout extends go.Layout {
constructor(init) {
super();
if (init) Object.assign(this, init);
}
doLayout(coll) {
coll = this.collectParts(coll);
const supers = new go.Set(/*go.Node*/);
coll.each(p => {
if (p instanceof go.Node && p.category === 'Super') supers.add(p);
});
function membersOf(sup, diag) {
const set = new go.Set(/*go.Part*/);
const arr = sup.data._members;
for (let i = 0; i < arr.length; i++) {
const d = arr[i];
set.add(diag.findNodeForData(d));
}
return set;
}
function isReady(sup, supers, diag) {
const arr = sup.data._members;
for (let i = 0; i < arr.length; i++) {
const d = arr[i];
if (d.category !== 'Super') continue;
const n = diag.findNodeForData(d);
if (supers.has(n)) return false;
}
return true;
}
// implementations of doLayout that do not make use of a LayoutNetwork
// need to perform their own transactions
this.diagram.startTransaction('Custom Layout');
while (supers.count > 0) {
let ready = null;
const it = supers.iterator;
while (it.next()) {
if (isReady(it.value, supers, this.diagram)) {
ready = it.value;
break;
}
}
supers.remove(ready);
const b = this.diagram.computePartsBounds(membersOf(ready, this.diagram));
ready.location = b.position;
const body = ready.findObject('BODY');
if (body) body.desiredSize = b.size;
}
this.diagram.commitTransaction('Custom Layout');
}
}
// end CustomLayout
// Define a custom DraggingTool
class CustomDraggingTool extends go.DraggingTool {
constructor(init) {
super();
if (init) Object.assign(this, init);
}
moveParts(parts, offset, check) {
super.moveParts(parts, offset, check);
this.diagram.layoutDiagram(true);
}
computeEffectiveCollection(parts) {
const coll = new go.Set(/*go.Part*/).addAll(parts);
parts.each(p => this.walkSubTree(p, coll));
return super.computeEffectiveCollection(coll);
}
// Find other attached nodes.
walkSubTree(sup, coll) {
if (sup === null) return;
coll.add(sup);
if (sup.category !== 'Super') return;
// recurse through this super state's members
const model = this.diagram.model;
const mems = sup.data._members;
if (mems) {
for (let i = 0; i < mems.length; i++) {
const mdata = mems[i];
this.walkSubTree(this.diagram.findNodeForData(mdata), coll);
}
}
}
}
// end CustomDraggingTool class
function init() {
myDiagram = new go.Diagram('myDiagramDiv', { // must name or refer to the DIV HTML element
allowCopy: false,
allowDelete: false,
draggingTool: new CustomDraggingTool(),
layout: new CustomLayout(),
// enable undo & redo
'undoManager.isEnabled': true
});
// define the Node template
myDiagram.nodeTemplate =
new go.Node('Auto')
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.add(
// define the node's outer shape, which will surround the TextBlock
new go.Shape('RoundedRectangle', {
fill: 'rgb(254, 201, 0)',
stroke: 'black',
parameter1: 20, // the corner has a large radius
portId: '',
fromSpot: go.Spot.AllSides,
toSpot: go.Spot.AllSides
}),
new go.TextBlock()
.bindTwoWay('text')
);
myDiagram.nodeTemplateMap.add('Super',
new go.Node('Auto', { locationObjectName: 'BODY' })
.add(
new go.Shape('RoundedRectangle', {
fill: 'rgba(128, 128, 64, 0.5)',
strokeWidth: 1.5,
parameter1: 20,
spot1: go.Spot.TopLeft,
spot2: go.Spot.BottomRight,
minSize: new go.Size(30, 30)
}),
new go.Panel('Vertical', { margin: 10 })
.add(
new go.TextBlock({
font: 'bold 10pt sans-serif',
margin: new go.Margin(0, 0, 5, 0)
})
.bind('text'),
new go.Shape({ name: 'BODY', opacity: 0 })
)
)
);
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
new go.Link({ routing: go.Routing.Orthogonal, corner: 10 })
.add(
new go.Shape({ strokeWidth: 1.5 }),
new go.Shape({ toArrow: 'Standard', stroke: null })
);
// read in the JSON-format data from the "mySavedModel" element
load();
}
// Show the diagram's model in JSON format
function save() {
document.getElementById('mySavedModel').value = myDiagram.model.toJson();
myDiagram.isModified = false;
}
function load() {
myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
// make sure all data have up-to-date "members" collections
// this does not prevent any "cycles" of membership, which would result in undefined behavior
const arr = myDiagram.model.nodeDataArray;
for (let i = 0; i < arr.length; i++) {
const data = arr[i];
const supers = data.supers;
if (supers) {
for (let j = 0; j < supers.length; j++) {
const sdata = myDiagram.model.findNodeDataForKey(supers[j]);
if (sdata) {
// update _supers to be an Array of references to node data
if (!data._supers) {
data._supers = [sdata];
} else {
data._supers.push(sdata);
}
// update _members to be an Array of references to node data
if (!sdata._members) {
sdata._members = [data];
} else {
sdata._members.push(data);
}
}
}
}
}
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 400px"></div>
<p>
This demonstrates the ability to simulate having nodes be members of multiple "groups". Regular <a>Group</a>s only support a single
<a>Part.containingGroup</a> for nodes. This sample does not make use of <a>Group</a>s at all, but simulates one possible "grouping" relationship using a
custom <a>Layout</a> and a custom <a>DraggingTool</a>.
</p>
<p>
The CustomLayout assumes regular nodes already have real locations, and only
assigns <a>Part.location</a> to "Super" nodes. These are the nodes that
represent <a>Group</a>s. It also sets the <a>GraphObject.desiredSize</a> on
the "BODY" element of each "Super" node, based on the area occupied by all
of its member nodes. The CustomDraggingTool overrides the
<a>DraggingTool.computeEffectiveCollection</a> method to determine what
nodes to drag around.
</p>
<p>This sample does not support dynamic restructuring of the relationships in the graph.</p>
<button id="SaveButton" onclick="save()">Save</button>
<button onclick="load()">Load</button>
Diagram Model saved in JSON format:
<br />
<textarea id="mySavedModel" style="width: 100%; height: 300px">
{ "class": "go.GraphLinksModel",
"nodeDataArray": [
{ "key": -1, "text": "Operating", "category": "Super" },
{ "key": -2, "text": "Drying", "category": "Super", "supers": [-1] },
{ "key": -3, "text": "Non Drying", "category": "Super" },
{ "key": 1, "loc": "100 100", "text": "Starting.End", "supers": [-2] },
{ "key": 2, "loc": "250 100", "text": "Running", "supers": [-2] },
{ "key": 3, "loc": "100 200", "text": "Starting.Init", "supers": [-1, -3] },
{ "key": 4, "loc": "250 200", "text": "Washing", "supers": [-1, -3] },
{ "key": 5, "loc": "100 300", "text": "Stopped", "supers": [-3] },
{ "key": 6, "loc": "250 300", "text": "Stopping", "supers": [-3] }
],
"linkDataArray": [
{ "from": 1, "to": 2 },
{ "from": 3, "to": 1 },
{ "from": 4, "to": 2 },
{ "from": -2, "to": 4 },
{ "from": 5, "to": 3 },
{ "from": 6, "to": 5 },
{ "from": -1, "to": 5 }
]
}
</textarea
>
</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>Collections</h4>
<p>
<b>GoJS</b> provides its own collection classes: <a href="../api/symbols/List.html" target="api">List</a>, <a href="../api/symbols/Set.html" target="api">Set</a>, and <a href="../api/symbols/Map.html" target="api">Map</a>.
You can iterate over a collection by using an <a href="../api/symbols/Iterator.html" target="api">Iterator</a>.
More information can be found in the <a href="../intro/collections.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#collections">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Custom Layouts</h4>
<p>
GoJS allows for the creation of custom layouts to meet specific needs.
</p>
<p>
There are also many layouts that are extensions -- not predefined in the <code>go.js</code> or <code>go-debug.js</code> library,
but available as source code in one of the three extension directories, with some documentation and corresponding samples.
More information can be found in the <a href="../intro/layouts.html#CustomLayouts">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#customlayout">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>