UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

595 lines (540 loc) 27.7 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="Use the TableLayout extension to arrange nodes in a tabular or grid-like form." /> <meta itemprop="description" content="Use the TableLayout extension to arrange nodes in a tabular or grid-like form." /> <meta property="og:description" content="Use the TableLayout extension to arrange nodes in a tabular or grid-like form." /> <meta name="twitter:description" content="Use the TableLayout extension to arrange nodes in a tabular or grid-like form." /> <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="Table Layout of Nodes in Rows and Columns" /> <meta property="og:title" content="Table Layout of Nodes in Rows and Columns" /> <meta name="twitter:title" content="Table Layout of Nodes in Rows and Columns" /> <meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/table.png" /> <meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/table.png" /> <meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/table.png" /> <meta property="og:url" content="https://gojs.net/latest/samples/Table.html" /> <meta property="twitter:url" content="https://gojs.net/latest/samples/Table.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Table Layout of Nodes in Rows and Columns | 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/TableLayout.js"></script> <script id="code"> // define a custom ResizingTool to limit how far one can shrink a row or column class LaneResizingTool extends go.ResizingTool { constructor(init) { super(); if (init) Object.assign(this, init); } computeMinSize() { const diagram = this.diagram; if (this.adornedObject === null) return new go.Size(); const lane = this.adornedObject.part; // might be row or column if (lane === null) return new go.Size(); const horiz = lane.category === 'Column Header'; // or "Row Header" const margin = diagram.nodeTemplate.margin; let bounds = new go.Rect(); diagram.findTopLevelGroups().each(g => { if (lane === null) return; if (horiz ? g.column === lane.column : g.row === lane.row) { const b = diagram.computePartsBounds(g.memberParts); if (b.isEmpty()) return; // nothing in there? ignore it b.unionPoint(g.location); // keep any empty space on the left and top b.addMargin(margin); // assume the same node margin applies to all nodes if (bounds.isEmpty()) { bounds = b; } else { bounds.unionRect(b); } } }); // limit the result by the standard value of computeMinSize const msz = super.computeMinSize(); if (bounds.isEmpty()) return msz; return new go.Size(Math.max(msz.width, bounds.width), Math.max(msz.height, bounds.height)); } resize(newr) { const diagram = this.diagram; if (this.adornedObject === null) return; const lane = this.adornedObject.part; if (lane === null) return; const horiz = lane.category === 'Column Header'; const lay = diagram.layout; // the TableLayout if (horiz) { const col = lane.column; const coldef = lay.getColumnDefinition(col); coldef.width = newr.width; } else { const row = lane.row; const rowdef = lay.getRowDefinition(row); rowdef.height = newr.height; } lay.invalidateLayout(); } } // end LaneResizingTool class function init() { const tableLayout = new TableLayout(); tableLayout.getRowDefinition(1).height = 22; // fixed size column headers tableLayout.getColumnDefinition(1).width = 22; // fixed size row headers myDiagram = new go.Diagram('myDiagramDiv', { layout: tableLayout, SelectionMoved: e => e.diagram.layoutDiagram(true), resizingTool: new LaneResizingTool(), // feedback that dropping in the background is not allowed mouseDragOver: e => (e.diagram.currentCursor = 'not-allowed'), // when dropped in the background, not on a Node or a Group, cancel the drop mouseDrop: e => e.diagram.currentTool.doCancel(), 'animationManager.isInitial': false, 'undoManager.isEnabled': true }); myDiagram.nodeTemplateMap.add('Header', // an overall table header, at the top new go.Part('Auto', { row: 0, column: 1, columnSpan: 9999, stretch: go.Stretch.Horizontal, selectable: false, pickable: false }) .add( new go.Shape({ fill: 'transparent', strokeWidth: 0 }), new go.TextBlock({ alignment: go.Spot.Center, font: 'bold 12pt sans-serif' }) .bind('text') )); myDiagram.nodeTemplateMap.add('Sider', // an overall table header, on the left side new go.Part('Auto', { row: 1, rowSpan: 9999, column: 0, stretch: go.Stretch.Vertical, selectable: false, pickable: false }) .add( new go.Shape({ fill: 'transparent', strokeWidth: 0 }), new go.TextBlock({ alignment: go.Spot.Center, font: 'bold 12pt sans-serif', angle: 270 }) .bind('text') )); myDiagram.nodeTemplateMap.add('Column Header', // for each column header new go.Part('Spot', { row: 1, rowSpan: 9999, column: 2, minSize: new go.Size(100, NaN), stretch: go.Stretch.Fill, movable: false, resizable: true, resizeAdornmentTemplate: new go.Adornment('Spot') .add( new go.Placeholder(), new go.Shape({ // for changing the length of a lane alignment: go.Spot.Right, desiredSize: new go.Size(7, 50), fill: 'lightblue', stroke: 'dodgerblue', cursor: 'col-resize' }) ) }) .bind('column', 'col') .add( new go.Shape({ fill: null }) .bind('fill', 'color'), new go.Panel('Auto', { // this is positioned above the Shape, in row 1 alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom, stretch: go.Stretch.Horizontal, height: myDiagram.layout.getRowDefinition(1).height }) .add( new go.Shape({ fill: 'transparent', strokeWidth: 0 }), new go.TextBlock({ font: 'bold 10pt sans-serif', isMultiline: false, wrap: go.Wrap.None, overflow: go.TextOverflow.Ellipsis }) .bind('text') ) )); myDiagram.nodeTemplateMap.add('Row Sider', // for each row header new go.Part('Spot', { row: 2, column: 1, columnSpan: 9999, minSize: new go.Size(NaN, 100), stretch: go.Stretch.Fill, movable: false, resizable: true, resizeAdornmentTemplate: new go.Adornment('Spot') .add( new go.Placeholder(), new go.Shape({ // for changing the breadth of a lane alignment: go.Spot.Bottom, desiredSize: new go.Size(50, 7), fill: 'lightblue', stroke: 'dodgerblue', cursor: 'row-resize' }) ) }) .bind('row') .add( new go.Shape({ fill: null }) .bind('fill', 'color'), new go.Panel('Auto', { // this is positioned to the left of the Shape, in column 1 alignment: go.Spot.Left, alignmentFocus: go.Spot.Right, stretch: go.Stretch.Vertical, angle: 270, height: myDiagram.layout.getColumnDefinition(1).width }) .add( new go.Shape({ fill: 'transparent', strokeWidth: 0 }), new go.TextBlock({ font: 'bold 10pt sans-serif', isMultiline: false, wrap: go.Wrap.None, overflow: go.TextOverflow.Ellipsis }) .bind('text') ) )); myDiagram.nodeTemplate = // for regular nodes within cells (groups); you'll want to extend this new go.Node('Auto', { width: 100, height: 50, margin: 4 }) // assume uniform Margin, all around .bind('row') .bind('column', 'col') .add( new go.Shape({ fill: 'white' }) .bind('fill', 'color'), new go.TextBlock() .bind('text', 'key') ); myDiagram.groupTemplate = // for cells new go.Group('Auto', { layerName: 'Background', stretch: go.Stretch.Fill, selectable: false, computesBoundsAfterDrag: true, computesBoundsIncludingLocation: true, handlesDragDropForMembers: true, // don't need to define handlers on member Nodes and Links mouseDragEnter: (e, group, prev) => { group.isHighlighted = true; }, mouseDragLeave: (e, group, next) => { group.isHighlighted = false; }, mouseDrop: (e, group) => { // if any dropped part wasn't already a member of this group, we'll want to let the group's row // column allow themselves to be resized automatically, in case the row height or column width // had been set manually by the LaneResizingTool var anynew = e.diagram.selection.any(p => p.containingGroup !== group); // Don't allow headers/siders to be dropped var anyHeadersSiders = e.diagram.selection.any(p => p.category === 'Column Header' || p.category === 'Row Sider'); if (!anyHeadersSiders && group.addMembers(e.diagram.selection, true)) { if (anynew) { e.diagram.layout.getRowDefinition(group.row).height = NaN; e.diagram.layout.getColumnDefinition(group.column).width = NaN; } } else { // failure upon trying to add parts to this group e.diagram.currentTool.doCancel(); } } }) .bind('row') .bind('column', 'col') .add( // the group is normally unseen -- it is completely transparent except when given a color or when highlighted new go.Shape({ fill: 'transparent', stroke: 'transparent', strokeWidth: myDiagram.nodeTemplate.margin.left, stretch: go.Stretch.Fill }) .bind('fill', 'color') .bindObject('stroke', 'isHighlighted', h => h ? 'red' : 'transparent'), new go.Placeholder({ // leave a margin around the member nodes of the group which is the same as the member node margin alignment: (m => new go.Spot(0, 0, m.top, m.left))(myDiagram.nodeTemplate.margin), padding: (m => new go.Margin(0, m.right, m.bottom, 0))(myDiagram.nodeTemplate.margin) }) ); myDiagram.model = new go.GraphLinksModel([ // headers { key: 'Header', text: 'Vacation Procedures', category: 'Header' }, { key: 'Sider', text: 'Personnel', category: 'Sider' }, // column and row headers { key: 'Request', text: 'Request', col: 2, category: 'Column Header' }, { key: 'Approval', text: 'Approval', col: 3, category: 'Column Header' }, { key: 'Employee', text: 'Employee', row: 2, category: 'Row Sider' }, { key: 'Manager', text: 'Manager', row: 3, category: 'Row Sider' }, { key: 'Administrator', text: 'Administrator', row: 4, category: 'Row Sider' }, // cells, each a group assigned to a row and column { key: 'EmpReq', row: 2, col: 2, isGroup: true, color: 'lightyellow' }, { key: 'EmpApp', row: 2, col: 3, isGroup: true, color: 'lightgreen' }, { key: 'ManReq', row: 3, col: 2, isGroup: true, color: 'lightgreen' }, { key: 'ManApp', row: 3, col: 3, isGroup: true, color: 'lightyellow' }, { key: 'AdmReq', row: 4, col: 2, isGroup: true, color: 'lightyellow' }, { key: 'AdmApp', row: 4, col: 3, isGroup: true, color: 'lightgreen' }, // nodes, each assigned to a group/cell { key: 'Delta', color: 'orange', size: '100 100', group: 'EmpReq' }, { key: 'Epsilon', color: 'coral', size: '100 50', group: 'EmpReq' }, { key: 'Zeta', color: 'tomato', size: '50 70', group: 'ManReq' }, { key: 'Eta', color: 'coral', size: '50 50', group: 'ManApp' }, { key: 'Theta', color: 'tomato', size: '100 50', group: 'AdmApp' } ]); myPalette = new go.Palette('myPaletteDiv', { nodeTemplateMap: myDiagram.nodeTemplateMap, 'model.nodeDataArray': [ { key: 'Alpha', color: 'orange' }, { key: 'Beta', color: 'tomato' }, { key: 'Gamma', color: 'goldenrod' } ] }); } window.addEventListener('DOMContentLoaded', init); </script> <div id="sample"> <div style="width: 100%; display: flex; justify-content: space-between"> <div id="myPaletteDiv" style="width: 120px; height: 600px; margin-right: 2px; border: solid 1px black"></div> <div id="myDiagramDiv" style="flex-grow: 1; height: 600px; border: solid 1px black"></div> </div> <p> This sample demonstrates a custom Layout, TableLayout, that is very much like a simplified "Table" Panel layout, but working on non-Link Parts in a Diagram or a Group rather than on GraphObjects in a Panel. The layout is defined in its own file, as <a href="../extensions/TableLayout.js">TableLayout.js</a>. </p> <p> You can drag-and-drop nodes from the Palette into any Group. Dragging into a Group highlights the Group. Drops must occur inside Groups; otherwise the action is cancelled. </p> <p> Each row and each column is <a>Part.resizable</a> and has a custom <a>Part.resizeAdornmentTemplate</a> showing a single resize handle on the right side or on the bottom. There is a custom LaneResizingTool to provide a minimum width or height based on the contents of all of the groups (cells) in that column or row. </p> <p> This example assumes the Groups are predefined and exist in each cell at a particular row/column, but this sample could be extended to allow adding and removing rows and/or columns. </p> </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>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> <!-- 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>GoJS Extensions</h4> <p> <b>GoJS</b> can be extended in a variety of ways. The most common way to change the standard behavior is to set properties on the <a href="../api/symbols/GraphObject.html" target="api">GraphObject</a>, <a href="../api/symbols/Diagram.html" target="api">Diagram</a>, <a href="../api/symbols/CommandHandler.html" target="api">CommandHandler</a>, <a href="../api/symbols/Tool.html" target="api">Tool</a>, or <a href="../api/symbols/Layout.html" target="api">Layout</a>. But when the desired property does not exist, you might need to override methods of CommandHandler, Tool, Layout, Link, or Node. Methods that you can override are documented in the API reference. Various features of GoJS can be overriden, either by replacing a method on an instance (a feature of JavaScript) or by defining a subclass. You should not modify the prototypes of any of the <b>GoJS</b> classes. </p> <p> In addition to our samples, <b>GoJS</b> provides an <strong><a href="../samples/#extensions">extensions gallery</a></strong>, showcasing the creation of custom tools and layouts. Those classes and samples are written in TypeScript, available at <code>../extensionsJSM/</code>, as ECMAScript/JavaScript modules -- these use the <code>../release/go-module.js</code> library. We recommend that you copy the files that you need into your project, so that you can adjust how they refer to the GoJS library that you choose and so that you can include them into your own building and packaging procedures. </p> <p> More information can be found in the <a href="../intro/extensions.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#extensions">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>