create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
579 lines (529 loc) • 24.2 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="An org chart that also shows non-tree-structure relationships." />
<meta itemprop="description" content="An org chart that also shows non-tree-structure relationships." />
<meta property="og:description" content="An org chart that also shows non-tree-structure relationships." />
<meta name="twitter:description" content="An org chart that also shows non-tree-structure relationships." />
<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="Org Chart With Extra Relationships" />
<meta property="og:title" content="Org Chart With Extra Relationships" />
<meta name="twitter:title" content="Org Chart With Extra Relationships" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/orgchartextras.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/orgchartextras.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/orgchartextras.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/orgChartExtras.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/orgChartExtras.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Org Chart With Extra Relationships | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<div id="allSampleContent" class="p-4 w-full">
<script id="code">
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
// make sure users can only create trees
validCycle: go.CycleMode.DestinationTree,
// users can select only one part at a time
maxSelectionCount: 1,
layout: new go.TreeLayout({
treeStyle: go.TreeStyle.LastParents,
arrangement: go.TreeArrangement.Horizontal,
// properties for most of the tree:
angle: 90,
layerSpacing: 35,
// properties for the "last parents":
alternateAngle: 0,
alternateLayerSpacing: 35,
alternateAlignment: go.TreeAlignment.Start,
alternateNodeIndent: 10,
alternateNodeIndentPastParent: 1.0,
alternateNodeSpacing: 10,
alternateLayerSpacing: 30,
alternateLayerSpacingParentOverlap: 1.0,
alternatePortSpot: new go.Spot(0.01, 1, 10, 0),
alternateChildPortSpot: go.Spot.Left
}),
// support editing the properties of the selected person in HTML
ChangedSelection: onSelectionChanged,
TextEdited: onTextEdited,
// newly drawn links are of type "Support" -- not a regular boss-employee relationship
'linkingTool.archetypeLinkData': { category: 'Support', text: '100%' },
// enable undo & redo
'undoManager.isEnabled': true
});
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener('Modified', e => {
const button = document.getElementById('SaveButton');
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);
}
});
const graygrad = new go.Brush('Linear', { 0: 'rgb(125, 125, 125)', 0.5: 'rgb(86, 86, 86)', 1: 'rgb(86, 86, 86)' });
// when a node is double-clicked, add a child to it
function nodeDoubleClick(e, obj) {
const clicked = obj.part;
if (clicked !== null) {
const thisemp = clicked.data;
myDiagram.startTransaction('add employee');
const nextkey = (myDiagram.model.nodeDataArray.length + 1).toString();
const newemp = { key: nextkey, name: '(new person)', title: '' };
myDiagram.model.addNodeData(newemp);
myDiagram.model.addLinkData({ from: thisemp.key, to: nextkey });
myDiagram.commitTransaction('add employee');
}
}
// this is used to determine feedback during drags
function mayWorkFor(node1, node2) {
if (!(node1 instanceof go.Node)) return false; // must be a Node
if (node1 === node2) return false; // cannot work for yourself
if (node2.isInTreeOf(node1)) return false; // cannot work for someone who works for you
return true;
}
// This function provides a common style for most of the TextBlocks.
// Some of these values may be overridden in a particular TextBlock.
function textStyle(tb) {
tb.font = '9pt sans-serif';
tb.stroke = 'white';
}
function nodeEvents(node) {
node.doubleClick = nodeDoubleClick;
// handle dragging a Node onto a Node to (maybe) change the reporting relationship
node.mouseDragEnter = (e, node, prev) => {
const diagram = node.diagram;
const selnode = diagram.selection.first();
if (!mayWorkFor(selnode, node)) return;
const shape = node.findObject('SHAPE');
if (shape) shape.fill = 'darkred';
};
node.mouseDragLeave = (e, node, next) => {
const shape = node.findObject('SHAPE');
if (shape) shape.fill = graygrad;
};
node.mouseDrop = (e, node) => {
const diagram = node.diagram;
const selnode = diagram.selection.first(); // assume just one Node in selection
if (mayWorkFor(selnode, node)) {
// find any existing link into the selected node
const link = selnode.findTreeParentLink();
if (link !== null) {
// reconnect any existing link
link.fromNode = node;
} else {
// else create a new link
diagram.toolManager.linkingTool.insertLink(node, node.port, selnode, selnode.port);
}
}
};
}
// define the Node template
myDiagram.nodeTemplate =
new go.Node('Auto')
.apply(nodeEvents)
// for sorting, have the Node.text be the data.name
.bind('text', 'name')
// bind the Part.layerName to control the Node's layer depending on whether it isSelected
.bindObject('layerName', 'isSelected', sel => sel ? 'Foreground' : '')
.add(
// define the node's outer shape
new go.Shape('RoundedRectangle', {
name: 'SHAPE',
fill: graygrad,
stroke: 'black',
portId: '',
fromLinkable: true,
toLinkable: true,
cursor: 'pointer'
}),
// define the panel where the text will appear
new go.Panel('Table', {
maxSize: new go.Size(150, 999),
margin: new go.Margin(3, 3, 0, 3),
defaultAlignment: go.Spot.Left
})
.addColumnDefinition(2, { width: 4 })
.add(
new go.TextBlock({ // the name
row: 0,
column: 0,
columnSpan: 5,
font: 'bold 9pt sans-serif',
editable: true,
isMultiline: false,
stroke: 'white',
minSize: new go.Size(10, 14),
name: 'name'
})
.bindTwoWay('text', 'name'),
new go.TextBlock('Title: ')
.apply(textStyle)
.set({ row: 1, column: 0 }),
new go.TextBlock()
.apply(textStyle)
.set({
row: 1,
column: 1,
columnSpan: 4,
editable: true,
isMultiline: false,
minSize: new go.Size(10, 14),
margin: new go.Margin(0, 0, 0, 3),
name: 'title'
})
.bindTwoWay('text', 'title'),
new go.TextBlock('ID: ')
.apply(textStyle)
.set({ row: 2, column: 0 }), // the ID and the boss
new go.TextBlock()
.apply(textStyle)
.set({ row: 2, column: 1 })
.bind('text', 'key'),
new go.TextBlock('Boss: ')
.apply(textStyle)
.set({ row: 2, column: 3 }),
new go.TextBlock()
.apply(textStyle)
.set({ row: 2, column: 4 })
.bind('text', 'parent'),
new go.TextBlock({ // the comments
row: 3,
column: 0,
columnSpan: 5,
font: 'italic 9pt sans-serif',
wrap: go.Wrap.Fit,
editable: true, // by default newlines are allowed
stroke: 'white',
minSize: new go.Size(10, 14),
name: 'comments'
})
.bind('text', 'comments'),
go.GraphObject.build('TreeExpanderButton', { row: 4, columnSpan: 99, alignment: go.Spot.Center })
) // end Table Panel
); // end Node
// define the Link template
myDiagram.linkTemplate =
new go.Link({ routing: go.Routing.Orthogonal, corner: 5 })
.add(new go.Shape({ strokeWidth: 2 })); // the link shape
myDiagram.linkTemplateMap.add('Support',
new go.Link({
curve: go.Curve.Bezier,
isLayoutPositioned: false,
isTreeLink: false,
curviness: -50,
relinkableFrom: true,
relinkableTo: true
})
.add(
new go.Shape({ stroke: 'green', strokeWidth: 2 }),
new go.Shape({ toArrow: 'OpenTriangle', stroke: 'green', strokeWidth: 2 }),
new go.TextBlock({
stroke: 'green',
background: 'rgba(255,255,255,0.75)',
maxSize: new go.Size(80, NaN),
editable: true
})
.bindTwoWay('text')
)
);
myDiagram.linkTemplateMap.add('Motion',
new go.Link({
curve: go.Curve.Bezier,
isLayoutPositioned: false,
isTreeLink: false,
curviness: -50,
relinkableFrom: true,
relinkableTo: true
})
.add(
new go.Shape({ stroke: 'orange', strokeWidth: 2 }),
new go.Shape({ toArrow: 'OpenTriangle', stroke: 'orange', strokeWidth: 2 }),
new go.TextBlock({
stroke: 'orange',
background: 'rgba(255,255,255,0.75)',
maxSize: new go.Size(80, NaN),
editable: true
})
.bindTwoWay('text')
)
);
// read in the JSON-format data from the "mySavedModel" element
load();
}
// Allow the user to edit text when a single node is selected
function onSelectionChanged(e) {
const node = e.diagram.selection.first();
if (node instanceof go.Node) {
updateProperties(node.data);
} else {
updateProperties(null);
}
}
// Update the HTML elements for editing the properties of the currently selected node, if any
function updateProperties(data) {
if (data === null) {
document.getElementById('propertiesPanel').style.display = 'none';
document.getElementById('name').value = '';
document.getElementById('title').value = '';
document.getElementById('comments').value = '';
} else {
document.getElementById('propertiesPanel').style.display = 'block';
document.getElementById('name').value = data.name || '';
document.getElementById('title').value = data.title || '';
document.getElementById('comments').value = data.comments || '';
}
}
// This is called when the user has finished inline text-editing
function onTextEdited(e) {
const tb = e.subject;
if (tb === null || !tb.name) return;
const node = tb.part;
if (node instanceof go.Node) {
updateData(tb.text, tb.name);
updateProperties(node.data);
}
}
// Update the data fields when the text is changed
function updateData(text, field) {
const node = myDiagram.selection.first();
// maxSelectionCount = 1, so there can only be one Part in this collection
const data = node.data;
if (node instanceof go.Node && data !== null) {
const model = myDiagram.model;
model.startTransaction('modified ' + field);
if (field === 'name') {
model.setDataProperty(data, 'name', text);
} else if (field === 'title') {
model.setDataProperty(data, 'title', text);
} else if (field === 'comments') {
model.setDataProperty(data, 'comments', text);
}
model.commitTransaction('modified ' + field);
}
}
// 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);
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; height: 550px"></div>
<div>
<div id="propertiesPanel" style="display: none; background-color: aliceblue; border: solid 1px black">
<b>Properties</b><br />
Name: <input type="text" id="name" value="" onchange="updateData(this.value, 'name')" /><br />
Title: <input type="text" id="title" value="" onchange="updateData(this.value, 'title')" /><br />
Comments: <input type="text" id="comments" value="" onchange="updateData(this.value, 'comments')" /><br />
</div>
</div>
<p>Double click on a node in order to add a person. Drag a node onto another in order to change relationships.</p>
<p>
This is the <a href="orgChartEditor.html">Org Chart Editor</a> sample, but each node includes a TreeExpanderButton, and there are additional non-tree links
connecting some of the nodes.
</p>
<div>
<div>
<button id="SaveButton" onclick="save()">Save</button>
<button onclick="load()">Load</button>
Diagram Model saved in JSON format:
</div>
<textarea id="mySavedModel" style="width: 100%; height: 300px">
{ "class": "go.GraphLinksModel",
"nodeDataArray": [
{"key":"1", "name":"Corrado 'Junior' Soprano", "title":"The Boss"},
{"key":"2", "name":"Tony Soprano", "title":"Underboss"},
{"key":"3", "name":"Herman 'Hesh' Rabkin", "title":"Advisor"},
{"key":"4", "name":"Paulie Walnuts", "title":"Capo"},
{"key":"5", "name":"Ralph Cifaretto", "title":"Capo MIA"},
{"key":"6", "name":"Silvio Dante", "title":"Consigliere"},
{"key":"7", "name":"Bobby Baccilien", "title":"Capo"},
{"key":"8", "name":"Sal Bonpensiero", "title":"MIA"},
{"key":"9", "name":"Christopher Moltisanti", "title":"Made Man"},
{"key":"10", "name":"Furio Giunta", "title":"Muscle"},
{"key":"11", "name":"Patsy Parisi", "title":"Accountant"}
],
"linkDataArray": [
{"from":"1", "to":"2"},
{"from":"1", "to":"3"},
{"from":"2", "to":"4"},
{"from":"2", "to":"5"},
{"from":"2", "to":"6"},
{"from":"2", "to":"7"},
{"from":"4", "to":"8"},
{"from":"4", "to":"9"},
{"from":"4", "to":"10"},
{"from":"4", "to":"11"},
{"from":"11", "to":"6", "category":"Support", "text":"50% support"},
{"from":"9", "to":"7", "category":"Motion", "text":"will change here in 2 months"}
]
}
</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>Table Panels</h4>
<p>
The "Table" Panel, <a href="../api/symbols/Panel.html#static-Table" target="api">Panel.Table</a>, arranges objects in rows and columns.
Each object in a Table Panel is put into the cell indexed by the value of <a href="../api/symbols/GraphObject.html#row" target="api">GraphObject.row</a> and <a href="../api/symbols/GraphObject.html#column" target="api">GraphObject.column</a>.
The panel will look at the rows and columns for all of the objects in the panel to determine how many rows and columns the table should have.
More information can be found in the <a href="../intro/tablePanels.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#tables">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Tree Layout</h4>
<p>
This predefined layout is used for placing Nodes of a tree-structured graph in layers (rows or columns).
For discussion and examples of the most commonly used properties of the <a href="../api/symbols/TreeLayout.html">TreeLayout</a>,
see the <a href="../intro/trees.html">Trees</a> page in the Introduction.
More information can be found in the <a href="../intro/layouts.html#TreeLayout">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#treelayout">Related samples</a>
</p>
<hr>
<!-- 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>Buttons</h4>
<p>
GoJS defines several <a href="../api/symbols/Panel.html" target="api">Panel</a>s for common uses.
These include "Button", "TreeExpanderButton", "SubGraphExpanderButton", "PanelExpanderButton", "ContextMenuButton", and "CheckBoxButton".
"ContextMenuButton"s are typically used inside of "ContextMenu" Panels;
"CheckBoxButton"s are used in the implementation of "CheckBox" Panels.
</p>
<p>
These predefined panels can be used as if they were <a href="../api/symbols/Panel.html" target="api">Panel</a>-derived classes in calls to <a href="../api/symbols/GraphObject.html#build" target="api">GraphObject.build</a>.
They are implemented as simple visual trees of <a href="../api/symbols/GraphObject.html" target="api">GraphObject</a>s in <a href="../api/symbols/Panel.html" target="api">Panel</a>s,
with pre-set properties and event handlers.
</p>
<p>
More information can be found in the <a href="../intro/buttons.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#buttons">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>