create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
798 lines (738 loc) • 34.6 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 organization chart editor with assistants shown on the side -- edit details and change relationships." />
<meta itemprop="description" content="An organization chart editor with assistants shown on the side -- edit details and change relationships." />
<meta property="og:description" content="An organization chart editor with assistants shown on the side -- edit details and change relationships." />
<meta name="twitter:description" content="An organization chart editor with assistants shown on the side -- edit details and change 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 Assistants on the Side" />
<meta property="og:title" content="Org Chart with Assistants on the Side" />
<meta name="twitter:title" content="Org Chart with Assistants on the Side" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/orgchartassistants.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/orgchartassistants.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/orgchartassistants.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/orgChartAssistants.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/orgChartAssistants.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 Assistants on the Side | 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/DataInspector.js"></script>
<script id="code">
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
allowCopy: false,
allowDelete: false,
initialAutoScale: go.AutoScale.Uniform,
maxSelectionCount: 1, // users can select only one part at a time
validCycle: go.CycleMode.DestinationTree, // make sure users can only create trees
layout: new SideTreeLayout({
treeStyle: go.TreeStyle.LastParents,
arrangement: go.TreeArrangement.Horizontal,
// properties for most of the tree:
angle: 90,
layerSpacing: 35,
// properties for the "last parents":
alternateAngle: 90,
alternateLayerSpacing: 35,
alternateAlignment: go.TreeAlignment.Bus,
alternateNodeSpacing: 20
}),
'undoManager.isEnabled': true // enable undo & redo
});
// 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);
}
});
// This function provides a common style for most of the TextBlocks.
// Some of these values may be overridden in a particular TextBlock.
function textStyle(obj) {
return Object.assign({ font: '9pt Segoe UI,sans-serif' }, obj || {});
}
// This converter is used by the Picture.
function findHeadShot(pic) {
if (!pic) return 'images/user.svg'; // There are only 16 images on the server
return 'images/hs' + pic;
}
// define the Node template
myDiagram.nodeTemplate = new go.Node('Auto')
// 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('Rectangle', {
name: 'SHAPE',
fill: 'white',
stroke: null
}),
new go.Panel('Horizontal')
.add(
new go.Picture({
name: 'Picture',
desiredSize: new go.Size(39, 50),
margin: new go.Margin(6, 8, 6, 10),
source: 'images/user.svg' // the default image
})
.bind('source', 'pic', findHeadShot),
// define the panel where the text will appear
new go.Panel('Table', {
maxSize: new go.Size(150, 999),
margin: new go.Margin(6, 10, 0, 3),
defaultAlignment: go.Spot.Left
})
.addColumnDefinition(2, { width: 4 })
.add(
new go.TextBlock(
textStyle(
{
name: 'NAMETB',
row: 0,
column: 0,
columnSpan: 5,
font: '12pt Segoe UI, sans-serif',
editable: true,
isMultiline: false,
minSize: new go.Size(10, 16)
}
)) // the name
.bindTwoWay('text', 'name'),
new go.TextBlock('Title: ', textStyle({ row: 1, column: 0 })),
new go.TextBlock(
textStyle(
{
row: 1,
column: 1,
columnSpan: 4,
editable: true,
isMultiline: false,
minSize: new go.Size(10, 14),
margin: new go.Margin(0, 0, 0, 3)
}
))
.bindTwoWay('text', 'title'),
new go.TextBlock(textStyle({ row: 2, column: 0 })).bind('text', 'key', (v) => 'ID: ' + v),
new go.TextBlock(
textStyle({ name: 'boss', row: 2, column: 3 }) // we include a name so we can access this TextBlock when deleting Nodes/Links
)
.bind('text', 'parent', (v) => 'Boss: ' + v),
new go.TextBlock(
textStyle(
{
row: 3,
column: 0,
columnSpan: 5,
font: 'italic 9pt sans-serif',
wrap: go.Wrap.Fit,
editable: true, // by default newlines are allowed
minSize: new go.Size(10, 14)
}
)) // the comments
.bindTwoWay('text', 'comments')
) // end Table Panel
) // end Horizontal Panel
); // end Node
// the context menu allows users to make a position vacant,
// remove a role and reassign the subtree, or remove a department
myDiagram.nodeTemplate.contextMenu = go.GraphObject.build('ContextMenu')
.add(
go.GraphObject.build('ContextMenuButton', {
click: (e, button) => {
const node = button.part.adornedPart;
if (node !== null) {
const thisemp = node.data;
myDiagram.startTransaction('add employee');
const newemp = { name: '(new person)', title: '', comments: '', parent: thisemp.key };
myDiagram.model.addNodeData(newemp);
const newnode = myDiagram.findNodeForData(newemp);
if (newnode) newnode.location = node.location;
myDiagram.commitTransaction('add employee');
}
}
})
.add(new go.TextBlock('Add Employee')),
go.GraphObject.build('ContextMenuButton', {
click: (e, button) => {
// remove the whole subtree, including the node itself
const node = button.part.adornedPart;
if (node !== null) {
myDiagram.startTransaction('toggle assistant');
myDiagram.model.setDataProperty(node.data, 'isAssistant', !node.data.isAssistant);
myDiagram.layout.invalidateLayout();
myDiagram.commitTransaction('toggle assistant');
}
}
})
.add(new go.TextBlock('Toggle Assistant'))
);
// define the Link template
myDiagram.linkTemplate = new go.Link({ routing: go.Routing.Orthogonal, corner: 5 })
.add(new go.Shape({ strokeWidth: 4, stroke: '#00a4a4' })); // the link shape
// read in the JSON-format data from the "mySavedModel" element
load();
// support editing the properties of the selected person in HTML
myInspector = new Inspector('myInspector', myDiagram, {
properties: {
key: { readOnly: true },
comments: {},
isAssistant: { type: 'boolean', defaultValue: false }
},
propertyModified: (name, val) => {
if (name === 'isAssistant') myDiagram.layout.invalidateLayout();
}
});
} // end init
// Assume that the SideTreeLayout determines whether a node is an "assistant" if a particular data property is true.
// You can adapt this code to decide according to your app's needs.
function isAssistant(n) {
if (n === null) return false;
return n.data.isAssistant;
}
// This is a custom TreeLayout that knows about "assistants".
// A Node for which isAssistant(n) is true will be placed at the side below the parent node
// but above all of the other child nodes.
// An assistant node may be the root of its own subtree.
// An assistant node may have its own assistant nodes.
class SideTreeLayout extends go.TreeLayout {
constructor(init) {
super();
if (init) Object.assign(this, init);
}
makeNetwork(coll) {
const net = super.makeNetwork(coll);
// copy the collection of TreeVertexes, because we will modify the network
const vertexcoll = new go.Set(/*go.TreeVertex*/);
vertexcoll.addAll(net.vertexes);
for (const it = vertexcoll.iterator; it.next(); ) {
const parent = it.value;
// count the number of assistants
let acount = 0;
const ait = parent.destinationVertexes;
while (ait.next()) {
if (isAssistant(ait.value.node)) acount++;
}
// if a vertex has some number of children that should be assistants
if (acount > 0) {
// remember the assistant edges and the regular child edges
const asstedges = new go.Set(/*go.TreeEdge*/);
const childedges = new go.Set(/*go.TreeEdge*/);
let eit = parent.destinationEdges;
while (eit.next()) {
const e = eit.value;
if (isAssistant(e.toVertex.node)) {
asstedges.add(e);
} else {
childedges.add(e);
}
}
// first remove all edges from PARENT
eit = asstedges.iterator;
while (eit.next()) {
parent.deleteDestinationEdge(eit.value);
}
eit = childedges.iterator;
while (eit.next()) {
parent.deleteDestinationEdge(eit.value);
}
// if the number of assistants is odd, add a dummy assistant, to make the count even
if (acount % 2 == 1) {
const dummy = net.createVertex();
net.addVertex(dummy);
net.linkVertexes(parent, dummy, asstedges.first().link);
}
// now PARENT should get all of the assistant children
eit = asstedges.iterator;
while (eit.next()) {
parent.addDestinationEdge(eit.value);
}
// create substitute vertex to be new parent of all regular children
const subst = net.createVertex();
net.addVertex(subst);
// reparent regular children to the new substitute vertex
eit = childedges.iterator;
while (eit.next()) {
const ce = eit.value;
ce.fromVertex = subst;
subst.addDestinationEdge(ce);
}
// finally can add substitute vertex as the final odd child,
// to be positioned at the end of the PARENT's immediate subtree.
const newedge = net.linkVertexes(parent, subst, null);
}
}
return net;
}
assignTreeVertexValues(v) {
// if a vertex has any assistants, use Bus alignment
let any = false;
const children = v.children;
for (let i = 0; i < children.length; i++) {
const c = children[i];
if (isAssistant(c.node)) {
any = true;
break;
}
}
if (any) {
// this is the parent for the assistant(s)
v.alignment = go.TreeAlignment.Bus; // this is required
v.nodeSpacing = 50; // control the distance of the assistants from the parent's main links
} else if (v.node == null && v.childrenCount > 0) {
// found the substitute parent for non-assistant children
//v.alignment = go.TreeAlignment.CenterChildren;
//v.breadthLimit = 3000;
v.layerSpacing = 0;
}
}
commitLinks() {
super.commitLinks();
// make sure the middle segment of an orthogonal link does not cross over the assistant subtree
const eit = this.network.edges.iterator;
while (eit.next()) {
const e = eit.value;
if (e.link == null) continue;
const r = e.link;
// does this edge come from a substitute parent vertex?
const subst = e.fromVertex;
if (subst.node == null && r.routing == go.Routing.Orthogonal) {
r.updateRoute();
r.startRoute();
// middle segment goes from point 2 to point 3
const p1 = subst.center; // assume artificial vertex has zero size
const p2 = r.getPoint(2).copy();
const p3 = r.getPoint(3).copy();
const p5 = r.getPoint(r.pointsCount - 1);
let dist = 10;
if (subst.angle == 270 || subst.angle == 180) dist = -20;
if (subst.angle == 90 || subst.angle == 270) {
p2.y = p5.y - dist; // (p1.y+p5.y)/2;
p3.y = p5.y - dist; // (p1.y+p5.y)/2;
} else {
p2.x = p5.x - dist; // (p1.x+p5.x)/2;
p3.x = p5.x - dist; // (p1.x+p5.x)/2;
}
r.setPoint(2, p2);
r.setPoint(3, p3);
r.commitRoute();
}
}
}
}
// end of SideTreeLayout
// Assume that the SideTreeLayout determines whether a node is an "assistant" if a particular data property is true.
// You can adapt this code to decide according to your app's needs.
function isAssistant(n) {
if (n === null) return false;
return n.data.isAssistant;
}
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
allowCopy: false,
allowDelete: false,
initialAutoScale: go.AutoScale.Uniform,
maxSelectionCount: 1, // users can select only one part at a time
validCycle: go.CycleMode.DestinationTree, // make sure users can only create trees
layout: new SideTreeLayout({
treeStyle: go.TreeStyle.LastParents,
arrangement: go.TreeArrangement.Horizontal,
// properties for most of the tree:
angle: 90,
layerSpacing: 35,
// properties for the "last parents":
alternateAngle: 90,
alternateLayerSpacing: 35,
alternateAlignment: go.TreeAlignment.Bus,
alternateNodeSpacing: 20
}),
'undoManager.isEnabled': true // enable undo & redo
});
// 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);
}
});
// 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 Segoe UI,sans-serif';
}
// This converter is used by the Picture.
function findHeadShot(pic) {
if (!pic) return 'images/user.svg'; // There are only 16 images on the server
return 'images/hs' + pic;
}
// define the Node template
myDiagram.nodeTemplate =
new go.Node('Auto')
// 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('Rectangle', { name: 'SHAPE', fill: 'white', stroke: null }),
new go.Panel('Horizontal')
.add(
new go.Picture({
name: 'Picture',
desiredSize: new go.Size(39, 50),
margin: new go.Margin(6, 8, 6, 10),
source: 'images/user.svg' // the default image
})
.bind('source', 'pic', findHeadShot),
// define the panel where the text will appear
new go.Panel('Table', {
maxSize: new go.Size(150, 999),
margin: new go.Margin(6, 10, 0, 3),
defaultAlignment: go.Spot.Left
})
.addColumnDefinition(2, { width: 4 })
.add(
new go.TextBlock()
.apply(textStyle)
.set({
name: 'NAMETB',
row: 0,
column: 0,
columnSpan: 5,
font: '12pt Segoe UI, sans-serif',
editable: true,
isMultiline: false,
minSize: new go.Size(10, 16)
})
.bindTwoWay('text', 'name'),
new go.TextBlock('Title: ')
.apply(textStyle)
.set({ row: 1, column: 0 }),
new go.TextBlock()
.apply(textStyle)
.set({ // the name
row: 1,
column: 1,
columnSpan: 4,
editable: true,
isMultiline: false,
minSize: new go.Size(10, 14),
margin: new go.Margin(0, 0, 0, 3)
})
.bindTwoWay('text', 'title'),
new go.TextBlock()
.apply(textStyle)
.set({ row: 2, column: 0 })
.bind('text', 'key', v => 'ID: ' + v),
new go.TextBlock()
.apply(textStyle)
.set({
name: 'boss', row: 2, column: 3 // we include a name so we can access this TextBlock when deleting Nodes/Links
})
.bind('text', 'parent', v => 'Boss: ' + v),
new go.TextBlock()
.apply(textStyle)
.set({ // the comments
row: 3,
column: 0,
columnSpan: 5,
font: 'italic 9pt sans-serif',
wrap: go.Wrap.Fit,
editable: true, // by default newlines are allowed
minSize: new go.Size(10, 14)
})
.bindTwoWay('text', 'comments')
) // end Table Panel
) // end Horizontal Panel
); // end Node
// the context menu allows users to make a position vacant,
// remove a role and reassign the subtree, or remove a department
myDiagram.nodeTemplate.contextMenu =
go.GraphObject.build('ContextMenu')
.add(
go.GraphObject.build('ContextMenuButton', {
click: (e, button) => {
const node = button.part.adornedPart;
if (node !== null) {
const thisemp = node.data;
myDiagram.startTransaction('add employee');
const newemp = { name: '(new person)', title: '', comments: '', parent: thisemp.key };
myDiagram.model.addNodeData(newemp);
const newnode = myDiagram.findNodeForData(newemp);
if (newnode) newnode.location = node.location;
myDiagram.commitTransaction('add employee');
}
}
})
.add(new go.TextBlock('Add Employee')),
go.GraphObject.build('ContextMenuButton', {
click: (e, button) => {
// remove the whole subtree, including the node itself
const node = button.part.adornedPart;
if (node !== null) {
myDiagram.startTransaction('toggle assistant');
myDiagram.model.setDataProperty(node.data, 'isAssistant', !node.data.isAssistant);
myDiagram.layout.invalidateLayout();
myDiagram.commitTransaction('toggle assistant');
}
}
})
.add(new go.TextBlock('Toggle Assistant'))
);
// define the Link template
myDiagram.linkTemplate =
new go.Link({ routing: go.Routing.Orthogonal, corner: 5 })
.add(
new go.Shape({ strokeWidth: 4, stroke: '#00a4a4' })
);
// read in the JSON-format data from the "mySavedModel" element
load();
// support editing the properties of the selected person in HTML
myInspector = new Inspector('myInspector', myDiagram, {
properties: {
key: { readOnly: true },
comments: {},
isAssistant: { type: 'boolean', defaultValue: false }
},
propertyModified: (name, val) => {
if (name === 'isAssistant') myDiagram.layout.invalidateLayout();
}
});
} // end init
// 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 new data keys are unique positive integers
let lastkey = 1;
myDiagram.model.makeUniqueKeyFunction = (model, data) => {
let k = data.key || lastkey;
while (model.findNodeDataForKey(k)) k++;
data.key = lastkey = k;
return k;
};
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div id="myDiagramDiv" style="background-color: #f3f4f6; border: solid 1px black; height: 500px"></div>
<div>
Edit details:<br />
<div id="myInspector"></div>
</div>
<p>
This editable organizational chart sample is a simplified <a href="orgChartEditor">Org Chart Editor</a> that adds support for "assistant" nodes that are
laid out by a custom <a>TreeLayout</a> below the side of the parent node and above the regular child nodes. See that sample for an org chart with more
stylized nodes.
</p>
<p>
Whether or not a node is considered to be an "assistant" node is determined by the <code>isAssistant</code> property of the node data. The user can modify
this data property via an additional context menu command.
</p>
<p>Assistant employees may themselves be bosses of their own employees. All of a boss's reports may be assistants.</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: 270px">
{ "class": "go.TreeModel",
"nodeDataArray": [
{"key":1, "name":"Stella Payne Diaz", "title":"CEO", "pic":"1.jpg"},
{"key":2, "name":"Luke Warm", "title":"VP Marketing/Sales", "pic":"2.jpg", "parent":1},
{"key":3, "name":"Meg Meehan Hoffa", "title":"Sales", "pic":"3.jpg", "parent":2},
{"key":4, "name":"Peggy Flaming", "title":"VP Engineering", "pic":"4.jpg", "parent":1},
{"key":5, "name":"Saul Wellingood", "title":"Manufacturing", "pic":"5.jpg", "parent":4},
{"key":6, "name":"Al Ligori", "title":"Marketing", "pic":"6.jpg", "parent":2},
{"key":7, "name":"Dot Stubadd", "title":"Sales Rep", "pic":"7.jpg", "parent":3},
{"key":8, "name":"Les Ismore", "title":"Project Mgr", "pic":"8.jpg", "parent":5},
{"key":9, "name":"April Lynn Parris", "title":"Events Mgr", "pic":"9.jpg", "parent":6},
{"key":10, "name":"Xavier Breath", "title":"Engineering", "pic":"10.jpg", "parent":4},
{"key":11, "name":"Anita Hammer", "title":"Process", "pic":"11.jpg", "parent":5},
{"key":12, "name":"Billy Aiken", "title":"Software", "pic":"12.jpg", "parent":10},
{"key":13, "name":"Stan Wellback", "title":"Testing", "pic":"13.jpg", "parent":10},
{"key":14, "name":"Marge Innovera", "title":"Hardware", "pic":"14.jpg", "parent":10},
{"key":15, "name":"Evan Elpus", "title":"Quality", "pic":"15.jpg", "parent":5},
{"key":16, "name":"Lotta B. Essen", "title":"Sales Rep", "pic":"16.jpg", "parent":3},
{"key":17, "name":"Joaquin Closet", "title":"Wardrobe Assistant", "isAssistant":true, "parent":1},
{"key":18, "name":"Hannah Twomey", "title":"Engineering Assistant", "parent":10, "isAssistant":true}
]
}
</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>