UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

689 lines (634 loc) 31.5 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 interactive Kanban board editor, a visual control used for organizing work items." /> <meta itemprop="description" content="An interactive Kanban board editor, a visual control used for organizing work items." /> <meta property="og:description" content="An interactive Kanban board editor, a visual control used for organizing work items." /> <meta name="twitter:description" content="An interactive Kanban board editor, a visual control used for organizing work items." /> <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="Interactive Kanban Board Diagram Using Collapsible Groups as Task Lists" /> <meta property="og:title" content="Interactive Kanban Board Diagram Using Collapsible Groups as Task Lists" /> <meta name="twitter:title" content="Interactive Kanban Board Diagram Using Collapsible Groups as Task Lists" /> <meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/kanban.png" /> <meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/kanban.png" /> <meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/kanban.png" /> <meta property="og:url" content="https://gojs.net/latest/samples/kanban.html" /> <meta property="twitter:url" content="https://gojs.net/latest/samples/kanban.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Interactive Kanban Board Diagram Using Collapsible Groups as Task Lists | 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"> <link href="https://fonts.googleapis.com/css?family=Lato:300,400,700" rel="stylesheet" type="text/css" /> <script id="code"> // define a custom grid layout that makes sure the length of each lane is the same // and that each lane is broad enough to hold its subgraph class PoolLayout extends go.GridLayout { constructor(init) { super(); this.MINLENGTH = 200; // this controls the minimum length of any swimlane this.MINBREADTH = 100; // this controls the minimum breadth of any non-collapsed swimlane this.cellSize = new go.Size(1, 1); this.wrappingColumn = Infinity; this.wrappingWidth = Infinity; this.spacing = new go.Size(0, 0); this.alignment = go.GridAlignment.Position; if (init) Object.assign(this, init); } doLayout(coll) { const diagram = this.diagram; if (diagram === null) return; diagram.startTransaction('PoolLayout'); // make sure all of the Group Shapes are big enough const minlen = this.computeMinPoolLength(); diagram.findTopLevelGroups().each(lane => { if (!(lane instanceof go.Group)) return; const shape = lane.selectionObject; if (shape !== null) { // change the desiredSize to be big enough in both directions const sz = this.computeLaneSize(lane); shape.width = !isNaN(shape.width) ? Math.max(shape.width, sz.width) : sz.width; // if you want the height of all of the lanes to shrink as the maximum needed height decreases: shape.height = minlen; // if you want the height of all of the lanes to remain at the maximum height ever needed: //shape.height = (isNaN(shape.height) ? minlen : Math.max(shape.height, minlen)); const cell = lane.resizeCellSize; if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) { shape.width = Math.ceil(shape.width / cell.width) * cell.width; } if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) { shape.height = Math.ceil(shape.height / cell.height) * cell.height; } } }); // now do all of the usual stuff, according to whatever properties have been set on this GridLayout super.doLayout(coll); diagram.commitTransaction('PoolLayout'); } // compute the minimum length of the whole diagram needed to hold all of the Lane Groups computeMinPoolLength() { let len = this.MINLENGTH; myDiagram.findTopLevelGroups().each(lane => { const holder = lane.placeholder; if (holder !== null) { const sz = holder.actualBounds; len = Math.max(len, sz.height); } }); return len; } // compute the minimum size for a particular Lane Group computeLaneSize(lane) { // assert(lane instanceof go.Group); const sz = new go.Size(lane.isSubGraphExpanded ? this.MINBREADTH : 1, this.MINLENGTH); if (lane.isSubGraphExpanded) { const holder = lane.placeholder; if (holder !== null) { const hsz = holder.actualBounds; sz.width = Math.max(sz.width, hsz.width); } } // minimum breadth needs to be big enough to hold the header const hdr = lane.findObject('HEADER'); if (hdr !== null) sz.width = Math.max(sz.width, hdr.actualBounds.width); return sz; } } // end PoolLayout class // Put the dragged Part in the "Foreground" Layer with partial opacity class CustomDraggingTool extends go.DraggingTool { constructor(init) { super(); if (init) Object.assign(this, init); } doActivate() { super.doActivate(); this.currentPart.opacity = 0.6; this.currentPart.layerName = 'Foreground'; } doDeactivate() { this.currentPart.opacity = 1; this.currentPart.layerName = ''; super.doDeactivate(); } } function init() { myDiagram = new go.Diagram('myDiagramDiv', { // make sure the top-left corner of the viewport is occupied contentAlignment: go.Spot.TopLeft, // use a simple layout to stack the top-level Groups next to each other layout: new PoolLayout(), // disallow nodes to be dragged to the diagram's background mouseDrop: e => e.diagram.currentTool.doCancel(), // a clipboard copied node is pasted into the original node's group (i.e. lane). 'commandHandler.copiesGroupKey': true, // automatically re-layout the swim lanes after dragging the selection SelectionMoved: relayoutDiagram, // this DiagramEvent listener is SelectionCopied: relayoutDiagram, // defined above draggingTool: new CustomDraggingTool(), 'undoManager.isEnabled': true, // allow TextEditingTool to start without selecting first 'textEditingTool.starting': go.TextEditingStarting.SingleClick }); // this is called after nodes have been moved function relayoutDiagram() { myDiagram.selection.each(n => n.invalidateLayout()); myDiagram.layoutDiagram(); } // There are only three note colors by default, blue, red, and yellow but you could add more here: const noteColors = ['#009CCC', '#CC293D', '#FFD700']; function getNoteColor(num) { return noteColors[Math.min(num, noteColors.length - 1)]; } myDiagram.nodeTemplate = new go.Node('Horizontal') .bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify) .add( new go.Shape('Rectangle', { fill: '#009CCC', strokeWidth: 1, stroke: '#009CCC', width: 6, stretch: go.Stretch.Vertical, alignment: go.Spot.Left, // if a user clicks the colored portion of a node, cycle through colors click: (e, obj) => { myDiagram.startTransaction('Update node color'); let newColor = parseInt(obj.part.data.color) + 1; if (newColor > noteColors.length - 1) newColor = 0; myDiagram.model.setDataProperty(obj.part.data, 'color', newColor); myDiagram.commitTransaction('Update node color'); } }) .bind('fill', 'color', getNoteColor) .bind('stroke', 'color', getNoteColor) ) .add( new go.Panel('Auto') .add( new go.Shape('Rectangle', { fill: 'white', stroke: '#CCCCCC' }), new go.Panel('Table', { width: 130, minSize: new go.Size(NaN, 50) }), new go.TextBlock({ name: 'TEXT', margin: 6, font: '11px Lato, sans-serif', editable: true, stroke: '#000', maxSize: new go.Size(130, NaN), alignment: go.Spot.TopLeft }) .bindTwoWay('text') ) ); // While dragging, highlight the dragged-over group function highlightGroup(grp, show) { if (show) { const part = myDiagram.toolManager.draggingTool.currentPart; if (part.containingGroup !== grp) { grp.isHighlighted = true; return; } } grp.isHighlighted = false; } myDiagram.groupTemplate = new go.Group('Vertical', { selectable: false, selectionObjectName: 'SHAPE', // even though its not selectable, this is used in the layout layerName: 'Background', // all lanes are always behind all nodes and links layout: new go.GridLayout({ // automatically lay out the lane's subgraph wrappingColumn: 1, cellSize: new go.Size(1, 1), spacing: new go.Size(5, 5), alignment: go.GridAlignment.Position, comparer: (a, b) => { // can re-order tasks within a lane const ay = a.location.y; const by = b.location.y; if (isNaN(ay) || isNaN(by)) return 0; if (ay < by) return -1; if (ay > by) return 1; return 0; } }), click: (e, grp) => { // allow simple click on group to clear selection if (!e.shift && !e.control && !e.meta) e.diagram.clearSelection(); }, doubleClick: addNewNode, computesBoundsIncludingLocation: true, computesBoundsAfterDrag: true, // needed to prevent recomputing Group.placeholder bounds too soon handlesDragDropForMembers: true, // don't need to define handlers on member Nodes and Links mouseDragEnter: (e, grp, prev) => highlightGroup(grp, true), mouseDragLeave: (e, grp, next) => highlightGroup(grp, false), mouseDrop: (e, grp) => { // dropping a copy of some Nodes and Links onto this Group adds them to this Group // don't allow drag-and-dropping a mix of regular Nodes and Groups if (e.diagram.selection.all(n => !(n instanceof go.Group))) { const ok = grp.addMembers(grp.diagram.selection, true); if (!ok) grp.diagram.currentTool.doCancel(); } }, subGraphExpandedChanged: grp => { const shp = grp.selectionObject; if (grp.diagram.undoManager.isUndoingRedoing) return; if (grp.isSubGraphExpanded) { shp.width = grp.data.savedBreadth; } else { // remember the original width if (!isNaN(shp.width)) grp.diagram.model.set(grp.data, 'savedBreadth', shp.width); shp.width = NaN; } } }) .bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify) .bindTwoWay('isSubGraphExpanded', 'expanded') // the lane header consisting of a TextBlock and an expander button .add( new go.Panel('Horizontal', { name: 'HEADER', alignment: go.Spot.Left }) .add( go.GraphObject.build('SubGraphExpanderButton', { margin: 5 }), // this remains always visible new go.TextBlock({ // the lane label font: '15px Lato, sans-serif', editable: true, margin: new go.Margin(2, 0, 0, 0) }) // this is hidden when the swimlane is collapsed .bindObject('visible', 'isSubGraphExpanded') .bindTwoWay('text') ), // end Horizontal Panel new go.Panel('Auto') // the lane consisting of a background Shape and a Placeholder representing the subgraph .add( new go.Shape('Rectangle', { // this is the resized object name: 'SHAPE', fill: '#F1F1F1', stroke: null, strokeWidth: 4 }) // strokeWidth controls space between lanes .bindObject('fill', 'isHighlighted', h => h ? '#D6D6D6' : '#F1F1F1') .bindTwoWay('desiredSize', 'size', go.Size.parse, go.Size.stringify), new go.Placeholder({ padding: 12, alignment: go.Spot.TopLeft }), new go.TextBlock({ // this TextBlock is only seen when the swimlane is collapsed name: 'LABEL', font: '15px Lato, sans-serif', editable: true, angle: 90, alignment: go.Spot.TopLeft, margin: new go.Margin(4, 0, 0, 2) }) .bindObject('visible', 'isSubGraphExpanded', e => !e) .bindTwoWay('text') ) ); // end Auto Panel // Set up an unmodeled Part as a legend, and place it directly on the diagram. myDiagram.add( new go.Part('Table', { position: new go.Point(10, 10), selectable: false }) .add( new go.TextBlock('Key', { row: 0, font: '700 14px Droid Serif, sans-serif' }), // end row 0 new go.Panel('Horizontal', { row: 1, alignment: go.Spot.Left }) .add( new go.Shape('Rectangle', { desiredSize: new go.Size(10, 10), fill: '#CC293D', margin: 5 }), new go.TextBlock('Halted', { font: '700 13px Droid Serif, sans-serif' }) ), new go.Panel('Horizontal', { row: 2, alignment: go.Spot.Left }) .add( new go.Shape('Rectangle', { desiredSize: new go.Size(10, 10), fill: '#FFD700', margin: 5 }), new go.TextBlock('In Progress', { font: '700 13px Droid Serif, sans-serif' }) ), new go.Panel('Horizontal', { row: 3, alignment: go.Spot.Left }) .add( new go.Shape('Rectangle', { desiredSize: new go.Size(10, 10), fill: '#009CCC', margin: 5 }), new go.TextBlock('Completed', { font: '700 13px Droid Serif, sans-serif' }) ), new go.Panel('Horizontal', { row: 4, click: addNewNode, background: 'white', margin: new go.Margin(10, 4, 4, 4) }) .add( new go.Panel('Auto') .add( new go.Shape('Rectangle', { strokeWidth: 0, stroke: null, fill: '#6FB583' }), new go.Shape('PlusLine', { margin: 6, strokeWidth: 2, width: 12, height: 12, stroke: 'white', background: '#6FB583' }) ), new go.TextBlock('New item', { font: '10px Lato, sans-serif', margin: 6 }) ) ) ); load(); } // end init function addNewNode(e, obj) { const diagram = e ? e.diagram : myDiagram; // maybe add to clicked group let sel = obj ? obj.part : null; if (sel && !(sel instanceof go.Group)) sel = sel.containingGroup; // else add to group of selected node if (!sel) sel = diagram.selection.first(); if (sel && !(sel instanceof go.Group)) sel = sel.containingGroup; // else add to first group if (!sel) sel = diagram.findTopLevelGroups().first(); if (!sel) return; diagram.startTransaction('add node'); const newdata = { group: sel.key, loc: '0 9999', text: 'New item ' + sel.memberParts.count, color: 0 }; diagram.model.addNodeData(newdata); diagram.commitTransaction('add node'); const newnode = diagram.findNodeForData(newdata); diagram.select(newnode); diagram.commandHandler.scrollToPart(newnode); diagram.commandHandler.editTextBlock(); } // 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="border: solid 1px black; width: 100%; height: 500px"></div> <p> A Kanban board is a work and workflow visualization used to communicate the status and progress of work items. Click on the color of a note to cycle through colors. </p> <p> This design and implementation were adapted from the <a href="swimLanesVertical.html">Swim Lanes (vertical)</a> sample. Unlike that sample: </p> <ul> <li>there are no Links</li> <li>lanes cannot be nested into "pools"</li> <li>lanes cannot be resized</li> <li>the user cannot drop tasks into the diagram's background</li> <li>all tasks are ordered within a single column; the user can rearrange the order</li> <li>tasks can freely be moved into other lanes</li> <li>lanes are not movable or copyable or deletable</li> </ul> <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":"Problems", "text":"Problems", "isGroup":true, "loc":"0 23.52284749830794" }, {"key":"Reproduced", "text":"Reproduced", "isGroup":true, "color":"0", "loc":"109 23.52284749830794" }, {"key":"Identified", "text":"Identified", "isGroup":true, "color":"0", "loc":"235 23.52284749830794" }, {"key":"Fixing", "text":"Fixing", "isGroup":true, "color":"0", "loc":"343 23.52284749830794" }, {"key":"Reviewing", "text":"Reviewing", "isGroup":true, "color":"0", "loc":"451 23.52284749830794"}, {"key":"Testing", "text":"Testing", "isGroup":true, "color":"0", "loc":"562 23.52284749830794" }, {"key":"Customer", "text":"Customer", "isGroup":true, "color":"0", "loc":"671 23.52284749830794" }, {"key":1, "text":"text for oneA", "group":"Problems", "color":"0", "loc":"12 35.52284749830794"}, {"key":2, "text":"text for oneB", "group":"Problems", "color":"1", "loc":"12 65.52284749830794"}, {"key":3, "text":"text for oneC", "group":"Problems", "color":"0", "loc":"12 95.52284749830794"}, {"key":4, "text":"text for oneD", "group":"Problems", "color":"1", "loc":"12 125.52284749830794"}, {"key":5, "text":"text for twoA", "group":"Reproduced", "color":"1", "loc":"121 35.52284749830794"}, {"key":6, "text":"text for twoB", "group":"Reproduced", "color":"1", "loc":"121 65.52284749830794"}, {"key":7, "text":"text for twoC", "group":"Identified", "color":"0", "loc":"247 35.52284749830794"}, {"key":8, "text":"text for twoD", "group":"Fixing", "color":"0", "loc":"355 35.52284749830794"}, {"key":9, "text":"text for twoE", "group":"Reviewing", "color":"0", "loc":"463 35.52284749830794"}, {"key":10, "text":"text for twoF", "group":"Reviewing", "color":"1", "loc":"463 65.52284749830794"}, {"key":11, "text":"text for twoG", "group":"Testing", "color":"0", "loc":"574 35.52284749830794"}, {"key":12, "text":"text for fourA", "group":"Customer", "color":"1", "loc":"683 35.52284749830794"}, {"key":13, "text":"text for fourB", "group":"Customer", "color":"1", "loc":"683 65.52284749830794"}, {"key":14, "text":"text for fourC", "group":"Customer", "color":"1", "loc":"683 95.52284749830794"}, {"key":15, "text":"text for fourD", "group":"Customer", "color":"0", "loc":"683 125.52284749830794"}, {"key":16, "text":"text for fiveA", "group":"Customer", "color":"0", "loc":"683 155.52284749830795"} ], "linkDataArray": []} </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>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>Grid Layouts</h4> <p> This predefined layout is used for placing Nodes in a grid-like arrangement. Nodes can be ordered, spaced apart, and wrapped as needed. This Layout ignores any Links connecting the nodes being laid out. More information can be found in the <a href="../intro/layouts.html#GridLayout">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#gridlayout">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>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> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <h4>Legend</h4> <p> A Legend can be created for a Diagram using a simple <a>Part</a>. Typically that is added directly to the Diagram as an unmodeled Part, not as a template with data in the Model. However you may want to define a template and add a legend data object to the model so that you can parameterize the legend with information persisted with the model. </p> <p> Usually a legend will be created as an "Auto" Panel for a border around a "Table" Panel holding information about the types of nodes and/or links that are in the diagram. </p> <p> It probably will want to be in the "ViewportBackground" or "ViewportForeground" Layer so that it is always visible in the viewport despite scrolling or zooming. Set the <a>GraphObject.alignment</a> property to position it where you want it to be; by default it will be in the lower right corner. However you may want to treat the legend Part as a regular Part in the Diagram, possibly laid out by the diagram's <a>Diagram.layout</a>. </p> <p> More information can be found in the <a href="../intro/legends.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#legend">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>