UNPKG

gojs

Version:

Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams

694 lines (653 loc) 31.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"/> <link rel="stylesheet" href="../assets/css/style.css"/> <!-- Copyright 1998-2023 by Northwoods Software Corporation. --> <title> GoJS Template Maps -- Northwoods Software </title> <link rel="stylesheet" href="../assets/css/prism.css" /> </head> <script> window.diagrams = []; window.goCode = function(pre, w, h, parentid, animation) { window.diagrams.push([pre, w, h, parentid, animation]); } </script> <body> <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary"> <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2"> <div class="md:pl-4"> <a class="text-white hover:text-white no-underline hover:no-underline font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../"> <h1 class="my-0 p-1 ">GoJS</h1> </a> </div> <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation"> <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6"> <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path> <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button> <div id="topnavList" class="hidden sm:block items-center w-auto mt-0 text-white p-0 z-20"> <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0"> <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li> <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li> </ul> </div> </div> <hr class="border-b border-gray-600 opacity-50 my-0 py-0" /> </nav> <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto"> <div id="navSide" class="flex flex-col w-full md:w-40 lg:w-48 text-gray-700 bg-white flex-shrink-0"> <div class="flex-shrink-0 px-8 py-4"> <button id="navButton" class="rounded-lg md:hidden focus:outline-none focus:ring" aria-label="Navigation"> <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6"> <path id="navOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path> <path id="navClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button> </div> <nav id="navList" class="min-h-screen hidden md:block sidebar-nav flex-grow px-1 lg:px-4 pb-4 md:pb-0 md:overflow-y-auto break-words"> <a href="index.html">Basics</a> <a href="buildingObjects.html">Building Parts</a> <a href="usingModels.html">Using Models</a> <a href="dataBinding.html">Data Binding</a> <a href="react.html">GoJS with React</a> <a href="angular.html">GoJS with Angular</a> <a href="textBlocks.html">TextBlocks</a> <a href="shapes.html">Shapes</a> <a href="pictures.html">Pictures</a> <a href="panels.html">Panels</a> <a href="tablePanels.html">Table Panels</a> <a href="brush.html">Brushes</a> <a href="sizing.html">Sizing Objects</a> <a href="itemArrays.html">Item Arrays</a> <a href="changedEvents.html">Changed Events</a> <a href="transactions.html">Transactions</a> <a href="viewport.html">Coordinates</a> <a href="initialView.html">Initial View</a> <a href="collections.html">Collections</a> <a href="links.html">Links</a> <a href="linkLabels.html">Link Labels</a> <a href="connectionPoints.html">Link Points</a> <a href="ports.html">Ports</a> <a href="nodes.html">Nodes</a> <a href="typings.html">Typings</a> <a href="debugging.html">Debugging</a> <a href="layouts.html">Layouts</a> <a href="trees.html">Trees</a> <a href="subtrees.html">SubTrees</a> <a href="groups.html">Groups</a> <a href="subgraphs.html">SubGraphs</a> <a href="sizedGroups.html">Sized Groups</a> <a href="selection.html">Selection</a> <a href="highlighting.html">Highlighting</a> <a href="animation.html">Animation</a> <a href="toolTips.html">ToolTips</a> <a href="contextmenus.html">Context Menus</a> <a href="events.html">Diagram Events</a> <a href="tools.html">Tools</a> <a href="commands.html">Commands</a> <a href="permissions.html">Permissions</a> <a href="validation.html">Validation</a> <a href="HTMLInteraction.html">HTML Interaction</a> <a href="layers.html">Layers &amp; Z-ordering</a> <a href="palette.html">Palette</a> <a href="overview.html">Overview</a> <a href="resizing.html">Resizing Diagrams</a> <a href="replacingDeleting.html">Replacing and Deleting</a> <a href="buttons.html">Buttons</a> <a href="templateMaps.html">Template Maps</a> <a href="legends.html">Legends and Titles</a> <a href="extensions.html">Extensions</a> <a href="geometry.html">Geometry Strings</a> <a href="grids.html">Grid Patterns</a> <a href="graduatedPanels.html">Graduated Panels</a> <a href="makingImages.html">Diagram Images</a> <a href="makingSVG.html">Diagram SVG</a> <a href="printing.html">Printing</a> <a href="serverSideImages.html">Server-side Images</a> <a href="nodeScript.html">GoJS in Node.js</a> <a href="testing.html">Testing</a> <a href="storage.html">Storage</a> <a href="performance.html">Performance</a> <a href="source.html">Building from Source</a> <a href="platforms.html">Platforms</a> <a href="deployment.html">Deployment</a> </nav> </div> <div class="pt-4 px-2 md:px-0 lg:px-4 pb-16 w-full overflow-hidden"> <h1>Template Maps</h1> <p> Many of the previous examples have provided custom templates for nodes, groups, or links. Those examples have shown how to make simple adaptations of the templates for particular data instances via data binding. But what if you want to have nodes with drastically different appearances or behaviors in a single diagram at the same time? </p> <p> It is possible to define a node template that includes all possible configurations for all of the kinds of nodes that you want to display. There would need to be a lot of data binding and/or code to make the needed changes. Often you will want to make not-<a>GraphObject.visible</a> large parts of the template in order to make visible the one panel that you want to show. But this technique is difficult to use -- templates get way too complicated too quickly. </p> <p> Instead <b>GoJS</b> supports as many templates as you want -- you choose dynamically which one you want to use to represent a particular node data. This does mean potentially a lot of templates, but each one will be much simpler, easier to write, and easier to maintain. </p> <p> Each <a>Diagram</a> actually holds a <a>Map</a> of templates for each type of <a>Part</a>: <a>Node</a>, <a>Group</a>, and <a>Link</a>. Each Map associates a "category" name with a template. For example, when the diagram wants to create a <a>Node</a> for a particular node data object, the diagram uses that node data's category to look up the node template in the <a>Diagram.nodeTemplateMap</a>. Similar lookups are done using the <a>Diagram.groupTemplateMap</a> and the <a>Diagram.linkTemplateMap</a>. </p> <p> Each <a>Diagram</a> initially has its own template maps stocked with predefined categories. The default category for any data object is the empty string, "". The <a>Diagram.nodeTemplateMap</a> initially contains for the empty string a very simple <a>Node</a> template holding a <a>TextBlock</a> whose <a>TextBlock.text</a> property is data bound to the data converted to a string. You can see the default templates for nodes, groups, and links in a number of the previous examples, such as the <a href="groups.html#GroupsLinks">Groups and Links</a> example. </p> <p> The value of <a>Diagram.nodeTemplate</a> is just the value of <code>thatDiagram.nodeTemplateMap.get("")</code>. Setting <a>Diagram.nodeTemplate</a> just replaces the template in <a>Diagram.nodeTemplateMap</a> named with the empty string. </p> <p> The implementations of all predefined templates are provided in <a href="../extensions/Templates.js">Templates.js</a> in the Extensions directory. You may wish to copy and adapt these definitions when creating your own templates. </p> <h2 id="ExampleOfNodeTemplates">Example of Node templates</h2> <pre class="lang-js" id="templates"><code> // the "simple" template just shows the key string and the color in the background, // but it also includes a tooltip that shows the description const simpletemplate = $(go.Node, "Auto", $(go.Shape, "Ellipse", new go.Binding("fill", "color")), $(go.TextBlock, new go.Binding("text", "key")), { toolTip: $("ToolTip", $(go.TextBlock, { margin: 4 }, new go.Binding("text", "desc")) ) } ); // the "detailed" template shows all of the information in a Table Panel const detailtemplate = $(go.Node, "Auto", $(go.Shape, "RoundedRectangle", new go.Binding("fill", "color")), $(go.Panel, "Table", { defaultAlignment: go.Spot.Left }, $(go.TextBlock, { row: 0, column: 0, columnSpan: 2, font: "bold 12pt sans-serif" }, new go.Binding("text", "key")), $(go.TextBlock, { row: 1, column: 0 }, "Description:"), $(go.TextBlock, { row: 1, column: 1 }, new go.Binding("text", "desc")), $(go.TextBlock, { row: 2, column: 0 }, "Color:"), $(go.TextBlock, { row: 2, column: 1 }, new go.Binding("text", "color")) ) ); // create the nodeTemplateMap, holding three node templates: const templmap = new go.Map(); // In TypeScript you could write: new go.Map&lt;string, go.Node&gt;(); // for each of the node categories, specify which template to use templmap.add("simple", simpletemplate); templmap.add("detailed", detailtemplate); // for the default category, "", use the same template that Diagrams use by default; // this just shows the key value as a simple TextBlock templmap.add("", diagram.nodeTemplate); diagram.nodeTemplateMap = templmap; diagram.model.nodeDataArray = [ { key: "Alpha", desc: "first letter", color: "green" }, // uses default category: "" { key: "Beta", desc: "second letter", color: "lightblue", category: "simple" }, { key: "Gamma", desc: "third letter", color: "pink", category: "detailed" }, { key: "Delta", desc: "fourth letter", color: "cyan", category: "detailed" } ]; </code></pre> <script>goCode("templates", 600, 150)</script> <p> If you hover the mouse over the "Beta" node, you will see the tooltip showing the description string. The detailed template does not bother using tooltips to show extra information because everything is already shown. </p> <p> By default the way that the model and diagram know about the category of a node data or a link data is by looking at its category property. If you want to use a different property on the data, for example because you want to use the category property to mean something different, set <a>Model.nodeCategoryProperty</a> to be the name of the property that results in the actual category string value. Or set <a>Model.nodeCategoryProperty</a> to be the empty string to cause all nodes to use the default node template. </p> <h2 id="ExampleOfItemTemplates">Example of Item Templates</h2> <p> For Panels with a value for <a>Panel.itemArray</a>, there is also the <a>Panel.itemTemplateMap</a>. As with Nodes and Groups and Links, the <a>Panel.itemTemplate</a> is just a reference to the template named with the empty string in the <a>Panel.itemTemplateMap</a>. Similarly, the <a>Panel.itemCategoryProperty</a> names the property on the item data that identifies the template to use from the itemTemplateMap. </p> <pre class="lang-js" id="itemTemplates"><code> // create a template map for items const itemtemplates = new go.Map(); // In TypeScript you could write: new go.Map&lt;string, go.Panel&gt;(); // the template when type == "text" itemtemplates.add("text", $(go.Panel, $(go.TextBlock, new go.Binding("text")) )); // the template when type == "button" itemtemplates.add("button", $("Button", $(go.TextBlock, new go.Binding("text")), // convert a function name into a function value, // because functions cannot be represented in JSON format new go.Binding("click", "handler", name => { if (name === "alert") return raiseAlert; // defined below return null; }) )); diagram.nodeTemplate = $(go.Node, "Vertical", $(go.TextBlock, new go.Binding("text", "key")), $(go.Panel, "Auto", $(go.Shape, { fill: "white" }), $(go.Panel, "Vertical", { margin: 3, defaultAlignment: go.Spot.Left, itemCategoryProperty: "type", // this property controls the template used itemTemplateMap: itemtemplates // map was defined above }, new go.Binding("itemArray", "info")) ) ); function raiseAlert(e, obj) { // here OBJ will be the item Panel const node = obj.part; alert(node.data.key + ": " + obj.data.text); } // The model data includes item arrays in the node data. diagram.model = new go.GraphLinksModel( [ { key: "Alpha", info: [ { type: "text", text: "some text" }, { type: "button", text: "Click me!", handler: "alert"} ] }, { key: "Beta", info: [ { type: "text", text: "first line" }, { type: "button", text: "First Button", handler: "alert"}, { type: "text", text: "second line" }, { type: "button", text: "Second Button", handler: "alert" } ] } ],[ { from: "Alpha", to: "Beta" } ]); </code></pre> <script>goCode("itemTemplates", 600, 150)</script> <h2 id="ExampleOfTableHeaderShowingItemData">Example of Table Header Showing Item Data</h2> <p> The natural way to have a distinct header for a Table Panel is to have the first row (i.e. the first item) hold the data for the header, but have it be styled differently. In this example we define a "Header" item template in the <a>Panel.itemTemplateMap</a>. </p> <pre class="lang-js" id="header"><code> const itemTemplateMap = new go.Map(); itemTemplateMap.add("", $(go.Panel, "TableRow", $(go.TextBlock, new go.Binding("text", "name"), { column: 0, margin: 2, font: "bold 10pt sans-serif" }), $(go.TextBlock, new go.Binding("text", "phone"), { column: 1, margin: 2 }), $(go.TextBlock, new go.Binding("text", "loc"), { column: 2, margin: 2 }) )); itemTemplateMap.add("Header", $(go.Panel, "TableRow", $(go.TextBlock, new go.Binding("text", "name"), { column: 0, margin: 2, font: "bold 10pt sans-serif" }), $(go.TextBlock, new go.Binding("text", "phone"), { column: 1, margin: 2, font: "bold 10pt sans-serif" }), $(go.TextBlock, new go.Binding("text", "loc"), { column: 2, margin: 2, font: "bold 10pt sans-serif" }) )); diagram.nodeTemplate = $(go.Node, "Auto", $(go.Shape, { fill: "white" }), $(go.Panel, "Table", new go.Binding("itemArray", "people"), { defaultAlignment: go.Spot.Left, defaultColumnSeparatorStroke: "black", itemTemplateMap: itemTemplateMap }, $(go.RowColumnDefinition, { row: 0, background: "lightgray" }), $(go.RowColumnDefinition, { row: 1, separatorStroke: "black" }) ) ); diagram.model = new go.GraphLinksModel( { nodeDataArray: [ { key: "group1", people: [ { name: "Person", phone: "Phone", loc: "Location", category: "Header" }, { name: "Alice", phone: "2345", loc: "C4-E18" }, { name: "Bob", phone: "9876", loc: "E1-B34" }, { name: "Carol", phone: "1111", loc: "C4-E23" }, { name: "Ted", phone: "2222", loc: "C4-E197" }, { name: "Robert", phone: "5656", loc: "B1-A27" }, { name: "Natalie", phone: "5698", loc: "B1-B6" } ] } ], linkDataArray: [ ] } ); </code></pre> <script>goCode("header", 600, 200)</script> <p> If you do not want to have the header data in the itemArray, and you want to define the header in the node template rather than as an item template, see the example in <a href="itemArrays.html">Item Arrays</a>. </p> <h2 id="ChangingCategoryOfPart">Changing category of a Part</h2> <p> To change the representation of a data object, call <a>Model.setCategoryForNodeData</a> or <a>GraphLinksModel.setCategoryForLinkData</a>. (If you set the <a>Part.category</a> of a data bound <a>Part</a>, it will call the Model method for you.) This causes the diagram to discard any existing Part for the data and re-create it using the new template that is associated with the new category value. </p> <pre class="lang-js" id="changingCategory"><code> // this function changes the category of the node data to cause the Node to be replaced function changeCategory(e, obj) { const node = obj.part; if (node) { const diagram = node.diagram; diagram.startTransaction("changeCategory"); let cat = diagram.model.getCategoryForNodeData(node.data); if (cat === "simple") cat = "detailed"; else cat = "simple"; diagram.model.setCategoryForNodeData(node.data, cat); diagram.commitTransaction("changeCategory"); } } // The "simple" template just shows the key string and the color in the background. // There is a Button to invoke the changeCategory function. const simpletemplate = $(go.Node, "Spot", $(go.Panel, "Auto", $(go.Shape, "Ellipse", new go.Binding("fill", "color")), $(go.TextBlock, new go.Binding("text", "key")) ), $("Button", { alignment: go.Spot.TopRight }, $(go.Shape, "AsteriskLine", { width: 8, height: 8 }), { click: changeCategory }) ); // The "detailed" template shows all of the information in a Table Panel. // There is a Button to invoke the changeCategory function. const detailtemplate = $(go.Node, "Spot", $(go.Panel, "Auto", $(go.Shape, "RoundedRectangle", new go.Binding("fill", "color")), $(go.Panel, "Table", { defaultAlignment: go.Spot.Left }, $(go.TextBlock, { row: 0, column: 0, columnSpan: 2, font: "bold 12pt sans-serif" }, new go.Binding("text", "key")), $(go.TextBlock, { row: 1, column: 0 }, "Description:"), $(go.TextBlock, { row: 1, column: 1 }, new go.Binding("text", "desc")), $(go.TextBlock, { row: 2, column: 0 }, "Color:"), $(go.TextBlock, { row: 2, column: 1 }, new go.Binding("text", "color")) ) ), $("Button", { alignment: go.Spot.TopRight }, $(go.Shape, "AsteriskLine", { width: 8, height: 8 }), { click: changeCategory }) ); const templmap = new go.Map(); // In TypeScript you could write: new go.Map&lt;string, go.Node&gt;(); templmap.add("simple", simpletemplate); templmap.add("detailed", detailtemplate); diagram.nodeTemplateMap = templmap; diagram.layout = $(go.TreeLayout); diagram.model.nodeDataArray = [ { key: "Beta", desc: "second letter", color: "lightblue", category: "simple" }, { key: "Gamma", desc: "third letter", color: "pink", category: "detailed" }, { key: "Delta", desc: "fourth letter", color: "cyan", category: "detailed" } ]; diagram.model.linkDataArray = [ { from: "Beta", to: "Gamma" }, { from: "Gamma", to: "Delta" } ]; </code></pre> <script>goCode("changingCategory", 600, 150)</script> <p> Click on the "asterisk" button on any node to toggle dynamically between the "simple" and the "detailed" category for each node. </p> <h2 id="ChangingTemplateMaps">Changing template maps</h2> <p> You can also replace one or all of the diagram's template maps (e.g. <a>Diagram.nodeTemplateMap</a>) in order to discard and re-create all of the nodes in the diagram. If you are only using the default template for nodes, you would only need to replace the <a>Diagram.nodeTemplate</a>. </p> <p> One common circumstance for doing this is as the <a>Diagram.scale</a> changes. When the user zooms out far enough, there is no point in having too much detail about each of the nodes. </p> <p> If you zoom out in this example, the <a>DiagramEvent</a> listener will detect when the <a>Diagram.scale</a> becomes small enough to use the simpler template for all of the nodes. Zoom in again and suddenly it uses the more detailed template. </p> <pre class="lang-js" id="changingMap"><code> // The "simple" template just shows the key string and the color in the background. const simpletemplate = $(go.Node, "Spot", $(go.Panel, "Auto", $(go.Shape, "Ellipse", new go.Binding("fill", "color")), $(go.TextBlock, new go.Binding("text", "key")) ) ); // The "detailed" template shows all of the information in a Table Panel. const detailtemplate = $(go.Node, "Spot", $(go.Panel, "Auto", $(go.Shape, "RoundedRectangle", new go.Binding("fill", "color")), $(go.Panel, "Table", { defaultAlignment: go.Spot.Left }, $(go.TextBlock, { row: 0, column: 0, columnSpan: 2, font: "bold 12pt sans-serif" }, new go.Binding("text", "key")), $(go.TextBlock, { row: 1, column: 0 }, "Description:"), $(go.TextBlock, { row: 1, column: 1 }, new go.Binding("text", "desc")), $(go.TextBlock, { row: 2, column: 0 }, "Color:"), $(go.TextBlock, { row: 2, column: 1 }, new go.Binding("text", "color")) ) ) ); diagram.layout = $(go.TreeLayout); diagram.model.nodeDataArray = [ { key: "Beta", desc: "second letter", color: "lightblue" }, { key: "Gamma", desc: "third letter", color: "pink" }, { key: "Delta", desc: "fourth letter", color: "cyan" } ]; diagram.model.linkDataArray = [ { from: "Beta", to: "Gamma" }, { from: "Gamma", to: "Delta" } ]; // initially use the detailed templates diagram.nodeTemplate = detailtemplate; diagram.addDiagramListener("ViewportBoundsChanged", e => { if (diagram.scale &lt; 0.9) { diagram.nodeTemplate = simpletemplate; } else { diagram.nodeTemplate = detailtemplate; } }); // make accessible to the HTML buttons myDiagram = diagram; </code></pre> <script>goCode("changingMap", 600, 150)</script> <input id="ZoomOut" type="button" onclick="myDiagram.commandHandler.decreaseZoom()" value="Zoom Out" /> <input id="ZoomIn" type="button" onclick="myDiagram.commandHandler.increaseZoom()" value="Zoom In" /> <p> Caution: if you modify a template <a>Map</a>, there is no notification that the map has changed. You will need to call <a>Diagram.rebuildParts</a> explicitly. If you are replacing the <a>Diagram.nodeTemplate</a> or the <a>Diagram.nodeTemplateMap</a> or the corresponding properties for Groups or Links, the Diagram property setters will automatically call <a>Diagram.rebuildParts</a>. </p> <p> When one or more templates are replaced in a diagram, layouts are automatically performed again. </p> </div> </div> <div class="bg-nwoods-primary"> <section class="max-w-screen-lg text-white container mx-auto py-2 px-12"> <p id="version" class="leading-none mb-2 my-4">GoJS</p> </section> </div><footer class="bg-nwoods-primary text-white"> <div class="container max-w-screen-lg mx-auto px-8"> <div class="w-full py-6"> <div class="max-w-screen-lg xl:max-w-screen-xl mx-auto px-4 sm:px-6 md:px-8"> <ul class="text-sm font-medium pb-6 grid grid-cols-2 sm:grid-cols-3 gap-y-10"> <li class="list-none row-span-2"> <h2 class="text-base font-semibold tracking-wide">GoJS</h2> <ul class="list-none space-y-4 md:space-y-1 px-0"> <li> <a href="../samples/index.html">Samples</a> </li> <li> <a href="../learn/index.html">Learn</a> </li> <li> <a href="../intro/index.html">Intro</a> </li> <li> <a href="../api/index.html">API</a> </li> <li> <a href="../changelog.html">Changelog</a> </li> <li> <a href="https://github.com/NorthwoodsSoftware/GoJS">GitHub</a> </li> </ul> </li> <li class="list-none row-span-2"> <h2 class="text-base font-semibold tracking-wide">Support</h2> <ul class="list-none space-y-4 md:space-y-1 px-0"> <li> <a href="https://www.nwoods.com/contact.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a> </li> <li> <a href="https://forum.nwoods.com/c/gojs">Forum</a> </li> <li> <a href="https://www.nwoods.com/app/activate.aspx?sku=gojs">Activate</a> </li> <li> <a href="https://www.nwoods.com/sales/index.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a> </li> <li> <a href="https://www.youtube.com/channel/UC9We8EoX596-6XFjJDtZIDg">Videos</a> </li> </ul> </li> <li class="list-none row-span-2"> <h2 class="text-base font-semibold tracking-wide">Company</h2> <ul class="list-none space-y-4 md:space-y-1 px-0"> <li> <a target="_blank" href="https://www.nwoods.com">Northwoods</a> </li> <li> <a target="_blank" href="https://www.nwoods.com/about.html">About Us</a> </li> <li> <a target="_blank" href="https://www.nwoods.com/contact.html">Contact Us</a> </li> <li> <a target="_blank" href="https://www.nwoods.com/consulting.html">Consulting</a> </li> <li> <a target="_blank" href="https://twitter.com/northwoodsgo">Twitter</a> </li> </ul> </li> </ul> <p class="text-sm text-gray-100 md:mb-6"> Copyright 1998-2023 <a class="text-white" href="https://www.nwoods.com">Northwoods Software</a> </p> </div> </div> </footer> </body> <script async src="https://www.googletagmanager.com/gtag/js?id=G-S5QK8VSK84"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-S5QK8VSK84'); var getOutboundLink = function(url, label) { gtag('event', 'click', { 'event_category': 'outbound', 'event_label': label, 'transport_type': 'beacon' }); } // topnav var topButton = document.getElementById("topnavButton"); var topnavList = document.getElementById("topnavList"); topButton.addEventListener("click", function() { this.classList.toggle("active"); topnavList.classList.toggle("hidden"); document.getElementById("topnavOpen").classList.toggle("hidden"); document.getElementById("topnavClosed").classList.toggle("hidden"); }); </script> <script src="../assets/js/prism.js"></script> <script src="../release/go.js"></script> <script src="../extensions/Figures.js"></script> <script src="../assets/js/goDoc.js"></script> <script> document.addEventListener("DOMContentLoaded", function() { if (window.go) document.getElementById('version').textContent = "GoJS version " + go.version; if (window.goDoc) window.goDoc(); var d = window.diagrams; for (var i = 0; i < d.length; i++) { var dargs = d[i]; goCodeExecute(dargs[0], dargs[1], dargs[2], dargs[3], dargs[4]); } if (window.extra) window.extra(); }); </script> </html>