UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

579 lines (529 loc) 24.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/> <meta name="description" content="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>