UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

734 lines (697 loc) 30.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"/> <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="TextBlocks" /> <meta property="og:title" content="TextBlocks" /> <meta name="twitter:title" content="TextBlocks" /> <meta property="og:image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" /> <meta itemprop="image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" /> <meta name="twitter:image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" /> <meta property="og:url" content="https://gojs.net/latest/intro/textBlocks.html" /> <meta property="twitter:url" content="https://gojs.net/latest/intro/textBlocks.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> TextBlocks | GoJS </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 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="sticky top-0 left-0 z-10 px-2 w-full bg-white border-b border-b-gray-200 md:hidden"> <button id="sidenavButton" class="flex p-2 text-gray-900 bg-inherit shadow-none items-center text-sm font-semibold hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation"> <svg class="h-7 w-7 block mr-2" 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> <span>Menu</span> </button> </div> <script> window.addEventListener("DOMContentLoaded", function () { // sidenav var sideButton = document.getElementById("sidenavButton"); var sidenav = document.getElementById("sidenav"); if (sideButton && sidenav) { sideButton.addEventListener("click", function (e) { sidenav .classList .toggle("hidden"); e.stopPropagation(); }); document.addEventListener("click", function (e) { // if the clicked element isn't the list, close the list if (!sidenav.classList.contains("hidden") && !e.target.closest("#sidenavList")) { sideButton.click(); } }); } }); </script> <div class="flex flex-row md:min-h-screen w-full max-w-screen-xl mx-auto"> <aside id="sidenav" class="hidden fixed top-0 left-0 z-10 w-full bg-black/10 min-h-screen max-h-screen overflow-x-hidden overflow-y-auto shrink-0 md:block md:sticky md:w-52 md:min-h-0 md:bg-inherit md:border-r md:border-r-gray-200 md:overscroll-auto"> <nav id="sidenavList" class="flex flex-col bg-white w-52 min-h-screen pl-2 pt-4 pb-24 md:w-full md:min-h-0"> <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="svelte.html">GoJS with Svelte</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="routers.html">Routers</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="theming.html">Theming</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="accessibility.html">Accessibility</a> <a href="buttons.html">Buttons</a> <a href="permissions.html">Permissions</a> <a href="validation.html">Validation</a> <a href="animation.html">Animation</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="replacingDeleting.html">Replacing and Deleting</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="SVGContext.html">Rendering to SVG</a> <a href="makingSVG.html">Snapshot to SVG</a> <a href="makingImages.html">Diagram Images</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="performance.html">Performance</a> <a href="platforms.html">Platforms</a> <a href="deployment.html">Deployment</a> </nav> </aside> <script> var navList = document.getElementById('sidenavList'); if (navList !== null) { var url = window.location.href; var lindex = url.lastIndexOf('/'); url = url .slice(lindex + 1) .toLowerCase(); var aTags = navList.getElementsByTagName('a'); var currentindex = -1; for (var i = 0; i < aTags.length; i++) { var lowerhref = aTags[i] .href .toLowerCase(); if (lowerhref.indexOf('/' + url) !== -1) { currentindex = i; aTags[i] .classList .add('active'); break; } } } </script> <div class="px-4 pb-16 w-full overflow-hidden prose"> <h1>TextBlocks</h1> <p>Use the <a>TextBlock</a> class to display text.</p> <p> Setting the <a>TextBlock.text</a> property is the only way to show a text string. Because TextBlock inherits from <a>GraphObject</a>, some GraphObject properties will affect text. But there are additional text-only options regarding how that text is formatted and drawn. You can also pass the text string as the first argument to the constructor. </p> <p> In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram. Once you learn about models and data binding you will generally not create parts (nodes or links) programmatically. </p> <h2 id="FontAndColors">Font and colors</h2> <p>The size and stylistic appearance of the text is specified by the <a>TextBlock.font</a>. The value may be any CSS font specifier string.</p> <p>The text is drawn using the <a>TextBlock.stroke</a> brush. The value may be any CSS color string or a <a>Brush</a>. By default the stroke is "black".</p> <p> You can also specify the brush to use as the background: <a>GraphObject.background</a>. This defaults to no brush at all, which results in a transparent background. The background is always rectangular. </p> <p> In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram. Once you learn about models and data binding you will generally not create parts (nodes or links) programmatically. </p> <pre class="lang-js" id="basicTextBlocks"><code> diagram.add( new go.Part("Vertical") .add( new go.TextBlock({ text: "a Text Block" }), new go.TextBlock({ text: "a Text Block", stroke: "red" }), new go.TextBlock({ text: "a Text Block", background: "lightblue" }), new go.TextBlock({ text: "a Text Block", font: "bold 14pt serif" }) )); </code></pre> <script> goCode('basicTextBlocks', 600, 100); </script> <h3 id="IconFonts">Icon Fonts</h3> <p> In some cases, you can show an icon that is provided by a font, instead of using a <a>Picture</a> or a <a>Shape</a>. First, make sure the font is loaded in the page before creating the diagram. Then you can show a string containing the Unicode code point for the desired icon. </p> <pre class="lang-js" id="awesomeFont"><code> // Explicitly load a font. Only do this once, not each time you load a model. const awesome = new FontFace("FontAwesome", "url(https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/webfonts/fa-solid-900.woff2)"); document.fonts.add(awesome); // Wait for the Font Awesome font to load before actually loading the diagram. awesome.load().then(load); function load() { diagram.add( new go.Node("Auto") .add( new go.Shape({ fill: "lightgreen" }), new go.Panel("Horizontal", { margin: 8 }) .add( new go.TextBlock("\uf030", { font: "14pt FontAwesome" }), new go.TextBlock("an example using FontAwesome", { margin: new go.Margin(0, 0, 0, 2) }) ) )); } </code></pre> <script> goCode('awesomeFont', 600, 100); </script> <h2 id="NaturalSizingOfTextBlocksVariesByBrowser">Natural Sizing of TextBlocks Varies by Browser</h2> <p> Because different browsers measure canvas text differently, TextBlocks are the only objects in <b>GoJS</b> that may have inconsistent natural sizes between browsers or different devices. For this reason, if you need objects to measure precisely and consistently across all browsers (such as for testing), TextBlocks without an explicit size (<a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> and <a>GraphObject.height</a>) should not be used to dictate the size of any objects. For example, if you have a TextBlock with natural size inside an <a>Panel.Auto</a> Panel, set the size on that Auto Panel to fix the size of the area occupied by both the panel and its text. </p> <h2 id="SizingAndClipping">Sizing and Clipping</h2> <p> The natural size of a <a>TextBlock</a> is just big enough to render the text string with the given font. However the actual size of the TextBlock can be larger or smaller in either dimension. Larger dimensions result in areas with no text; smaller dimensions result in clipping. </p> <p> To demonstrate this, the examples below start with a naturally sized TextBlock, followed by ones with decreasing explicit sizes. To better show the actual size of the TextBlocks below, we have given them lightgreen backgrounds. </p> <pre class="lang-js" id="sizingTextBlocks"><code> const settings = { background: "lightgreen", margin: 2 }; diagram.add( new go.Part("Vertical") .add( new go.TextBlock("a Text Block", settings) .set({ /* natural width and height */ }), new go.TextBlock("a Text Block", settings) .set({ width: 100, height: 33 }), new go.TextBlock("a Text Block", settings) .set({ width: 60, height: 33 }), new go.TextBlock("a Text Block", settings) .set({ width: 50, height: 22 }), new go.TextBlock("a Text Block", settings) .set({ width: 40, height: 9 }) )); </code></pre> <script> goCode('sizingTextBlocks', 600, 160); </script> <h2 id="MaxLinesAndOverflow">Max Lines and Overflow</h2> <p> You can constrain the TextBlock's available size using <a>GraphObject.desiredSize</a> (width and height), but you can also limit the vertical height with <a>TextBlock.maxLines</a>, which will limit the number allowed. When there isn't enough space to display all text, you can decide how to use the remaining space with different values for <a>TextBlock.overflow</a>. There are additional options in the wrapping section below. </p> <p> The example below starts with a naturally sized TextBlock, followed by ones with a max of 2 lines using the default <a>TextBlock.overflow</a> value of <code>OverflowClip</code>, followed by one using the <a>TextBlock.overflow</a> value of <code>OverflowEllipsis</code>. </p> <pre class="lang-js" id="sizingTextBlocks2"><code> const settings = { font: '14pt sans-serif', background: "lightblue", margin: 2, width: 90 }; diagram.add( new go.Part("Vertical") .add( // Allow any number of lines, no clipping needed: new go.TextBlock("a Text Block that takes 4 lines", settings) .set({ overflow: go.TextOverflow.Clip /* the default value */, // no maximum number of lines of text (the default) }), // Allow only 2 lines, OverflowClip: new go.TextBlock("a Text Block that takes 4 lines", settings) .set({ overflow: go.TextOverflow.Clip /* the default value */, maxLines: 2 }), // Allow only 2 lines, OverflowEllipsis: new go.TextBlock("a Text Block that takes 4 lines", settings) .set({ overflow: go.TextOverflow.Ellipsis, maxLines: 2 }) )); </code></pre> <script> goCode('sizingTextBlocks2', 600, 200); </script> <h2 id="Wrapping">Wrapping</h2> <p> Text can also be automatically wrapped onto additional lines. In order for wrapping to happen, the <a>TextBlock.wrap</a> property must not be None, and there must be some constraint on the width to be narrower than it would naturally be. </p> <p> In the following examples, the first TextBlock gets its natural size, the second is limited to 50 wide but is not allowed to wrap, and the other examples are limited to the same width but are allowed to wrap. </p> <pre class="lang-js" id="wrappingTextBlocks"><code> const settings = { background: "lightgreen", margin: 2 }; diagram.add( new go.Part("Vertical") .add( new go.TextBlock("a Text Block", settings) .set({ /* natural width and wrap */ }), new go.TextBlock("a Text Block", settings) .set({ width: 50, wrap: go.Wrap.None }), new go.TextBlock("a Text Block", settings) .set({ width: 50, wrap: go.Wrap.DesiredSize }), new go.TextBlock("a Text Block", settings) .set({ width: 50, wrap: go.Wrap.Fit }) )); </code></pre> <script> goCode('wrappingTextBlocks', 600, 120); </script> <h2 id="TextAlignment">Text Alignment</h2> <p> The <a>TextBlock.textAlign</a> property specifies where to draw the characters horizontally within the size of the <a>TextBlock</a>. The value must be a CSS string. </p> <p> This is different than the <a>GraphObject.alignment</a> property, which controls where to place the object within the area allocated by the parent <a>Panel</a>. </p> <pre class="lang-js" id="textAlignTextBlocks"><code> const settings = { background: "lightgreen", margin: 2 }; diagram.add( new go.Part("Horizontal") .add( new go.Panel("Vertical", { width: 150, defaultStretch: go.Stretch.Horizontal }) .add( new go.TextBlock("textAlign: 'left'", settings) .set({ textAlign: "left" }), new go.TextBlock("textAlign: 'center'", settings) .set({ textAlign: "center" }), new go.TextBlock("textAlign: 'right'", settings) .set({ textAlign: "right" }) ), new go.Panel("Vertical", { width: 150, defaultStretch: go.Stretch.None }) .add( new go.TextBlock("alignment: Left", settings) .set({ alignment: go.Spot.Left }), new go.TextBlock("alignment: Center", settings) .set({ alignment: go.Spot.Center }), new go.TextBlock("alignment: Right", settings) .set({ alignment: go.Spot.Right }) ) )); </code></pre> <script> goCode('textAlignTextBlocks', 600, 100); </script> <p> The <a>TextBlock.verticalAlignment</a> property controls the vertical alignment of the glyphs within the bounds. Neither <a>TextBlock.textAlign</a> nor <a>TextBlock.verticalAlignment</a> affect the sizing of the TextBlock. </p> <pre class="lang-js" id="verticalAlignment"><code> const settings = { width: 170, height: 60, background: "lightgreen", margin: 10 }; diagram.add( new go.Part("Horizontal") .add( new go.TextBlock("verticalAlignment: Top", settings) .set({ verticalAlignment: go.Spot.Top }), new go.TextBlock("verticalAlignment: Center", settings) .set({ verticalAlignment: go.Spot.Center }), new go.TextBlock("verticalAlignment: Bottom", settings) .set({ verticalAlignment: go.Spot.Bottom }) )); </code></pre> <script> goCode('verticalAlignment', 600, 100); </script> <h2 id="TextAlignAndMultilineOrWrapping">TextAlign and Multiline or Wrapping</h2> <p> The <a>TextBlock.textAlign</a> property is useful even when the TextBlock has its natural size. This occurs when the text occupies multiple lines, whether by embedded newlines causing line breaks or by wrapping. You can control whether text starting with the first newline character is ignored by setting the <a>TextBlock.isMultiline</a>. By default both multiline and wrapping are enabled. </p> <pre class="lang-js" id="multilineTextBlocks"><code> const settings = { background: "lightgreen", margin: 2 }; diagram.add( new go.Part("Vertical") .add( new go.TextBlock("a Text Block\nwith three logical lines\nof text", settings) .set({ isMultiline: false }), new go.TextBlock("a Text Block\nwith three logical lines\nof text", settings) .set({ isMultiline: true }), new go.TextBlock("a Text Block\nwith three logical lines\nof centered text", settings) .set({ isMultiline: true, textAlign: "center" }), new go.TextBlock("a single line of centered text that should" + " wrap because we will limit the width", settings) .set({ width: 80, wrap: go.Wrap.Fit, textAlign: "center" }) )); </code></pre> <script> goCode('multilineTextBlocks', 600, 300); </script> <h2 id="Indenting">Indenting</h2> <p> Each line of text is normally trimmed of leading and trailing spaces before rendering. If you wish to indent a line of text by preserving leading spaces, start the line with the zero-width space character: <code>\u200B</code>. <a href="https://en.wikipedia.org/wiki/Zero-width_space" target="_blank">Wikipedia: Zero-width space</a> </p> <pre class="lang-js" id="indent"><code> diagram.add( new go.Part("Vertical") .add( new go.TextBlock("left aligned\n\u200B indent two\n\u200B indent four", { background: "lightgreen", margin: 2, width: 150 }), new go.TextBlock("\u200B This is an indented paragraph consisting of lots of text that wraps naturally.", { background: "lightgreen", margin: 2, width: 150 }) )); </code></pre> <script> goCode('indent', 600, 200); </script> <p>You can also use a <code>\u200B</code> character if you want to preserve spaces at the end of a line.</p> <h2 id="Flipping">Flipping</h2> <p>You can flip text horizontally and vertically with the <a>TextBlock.flip</a> property:</p> <pre class="lang-js" id="flipPictures"><code> diagram.add( new go.Part("Table", { defaultColumnSeparatorStrokeWidth: 1, defaultColumnSeparatorStroke: "gray", defaultSeparatorPadding: 5 }) .add( new go.TextBlock("Hello", { column: 0, margin: 2, font: '26px serif', flip: go.Flip.None /* the default value */ }), new go.TextBlock("None (default)", { row: 1, column: 0 }), new go.TextBlock("Hello", { column: 1, margin: 2, font: '26px serif', flip: go.Flip.Horizontal }), new go.TextBlock("FlipHorizontal", { row: 1, column: 1 }), new go.TextBlock("Hello", { column: 2, margin: 2, font: '26px serif', flip: go.Flip.Vertical }), new go.TextBlock("FlipVertical", { row: 1, column: 2 }), new go.TextBlock("Hello", { column: 3, margin: 2, font: '26px serif', flip: go.Flip.Both }), new go.TextBlock("FlipBoth", { row: 1, column: 3 }) )); </code></pre> <script> goCode('flipPictures', 600, 160); </script> <h2 id="Editing">Editing</h2> <p><b>GoJS</b> also supports the in-place editing of text by the user. You just need to set the <a>TextBlock.editable</a> property to true.</p> <p> If you want to provide text validation of the user's input, you can set the <a>TextBlock.textValidation</a> property to a function. You can also provide a more customized or sophisticated text editor by setting the <a>TextBlock.textEditor</a> property. There is an example of text validation on the <a href="validation.html">Validation intro page.</a> </p> <pre class="lang-js" id="editingTextBlocks"><code> diagram.add( new go.Part() .add( new go.TextBlock("select and then click to edit", { background: "lightblue", editable: true, isMultiline: false }) )); diagram.add( new go.Part() .add( new go.TextBlock("this one allows embedded newlines", { background: "lightblue", editable: true }) )); </code></pre> <p> Note how the TextBlock on the left only supports single line text, so the text editor accepts any edits and completes editing when the user hits the Enter key. </p> <script> goCode('editingTextBlocks', 600, 100); </script> </div> </div> <footer class="bg-white text-gray-900 border-t border-t-gray-200"> <div class="w-full max-w-screen-lg mx-auto px-4 py-6"> <p id="version" class="text-xs text-gray-900 m-0"></p> <div class="text-sm px-0 mb-4 grid grid-cols-2 sm:grid-cols-3 gap-y-10"> <div> <h2 class="text-base font-semibold text-nwoods-primary">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" target="_blank" rel="noopener">GitHub</a> </li> </ul> </div> <div> <h2 class="text-base font-semibold text-nwoods-primary">Support</h2> <ul class="list-none space-y-4 md:space-y-1 px-0"> <li> <a href="https://nwoods.com/contact.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a> </li> <li> <a href="https://forum.nwoods.com/c/gojs" target="_blank" rel="noopener">Forum</a> </li> <li> <a href="https://nwoods.com/app/activate.aspx?sku=gojs" target="_blank" rel="noopener">Activate</a> </li> <li> <a href="https://nwoods.com/sales/index.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a> </li> <li> <a href="https://nwoods.com/register.html" target="_blank" rel="noopener">Register</a> </li> </ul> </div> <div> <h2 class="text-base font-semibold text-nwoods-primary">Company</h2> <ul class="list-none space-y-4 md:space-y-1 px-0"> <li> <a target="_blank" href="https://nwoods.com" target="_blank" rel="noopener">Northwoods</a> </li> <li> <a target="_blank" href="https://nwoods.com/about.html" target="_blank" rel="noopener">About Us</a> </li> <li> <a target="_blank" href="https://nwoods.com/contact.html" target="_blank" rel="noopener">Contact Us</a> </li> <li> <a target="_blank" href="https://nwoods.com/consulting.html" target="_blank" rel="noopener">Consulting</a> </li> <li> <a target="_blank" href="https://twitter.com/northwoodsgo" target="_blank" rel="noopener">Twitter</a> </li> </ul> </div> </div> <p class="text-sm text-gray-900 md:mb-6"> Copyright 1998-2025 <a href="https://nwoods.com">Northwoods Software</a> </p> </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' }); } const params = new URL(document.location).searchParams let a = params.get('a'); if (a) localStorage.setItem('a', a); a = localStorage.getItem('a'); if (a) { const links = [...document.body.getElementsByTagName("a")].filter((l) => l.href.includes('nwoods.com')); for (const l of links) { const url = new URL(l.href); url.searchParams.set('a', a); l.href = url; } } </script> <script src="../assets/js/prism.js"></script> <script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></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>