UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

1,353 lines 61.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="Panels" />
<meta property="og:title" content="Panels" />
<meta name="twitter:title" content="Panels" />


<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/panels.html" />
<meta property="twitter:url" content="https://gojs.net/latest/intro/panels.html" />

<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />

    <title>
      Panels | 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>Panels</h1>
<p>
  <a>Panel</a>s are <a>GraphObject</a>s that hold other <a>GraphObject</a>s as their elements.
  A Panel is responsible for sizing and positioning all of its elements.
  Each Panel establishes its own coordinate system.
  The elements of a panel are drawn in order, thereby establishing an implicit Z-ordering of those elements.
</p>
<p>
  Although there is only one Panel class, there are many different kinds of panels, each with its own purpose in how it arranges its elements.
  When you construct a <a>Panel</a> you usually specify its <a>Panel.type</a> as the constructor argument.
  These are the predefined kinds of panels that you can use:
</p>
<ul>
  <li><a>Panel.Position</a>, arranges elements by their <a>GraphObject.position</a></li>
  <li><a>Panel.Vertical</a>, arranges elements in a vertical stack</li>
  <li><a>Panel.Horizontal</a>, arranges elements in a horizontal row</li>
  <li><a>Panel.Auto</a>, arranges the main element around all of the other elements, for a border</li>
  <li><a>Panel.Spot</a>, arranges elements according to their <a>GraphObject.alignment</a></li>
  <li><a>Panel.Table</a> (see the next section about <a href="tablePanels.html">Table Panels</a>)</li>
  <li><a>Panel.Viewbox</a>, scales its one element to fit inside the panel</li>
  <li><a>Panel.Link</a> (see the section about <a href="linkLabels.html">Link Labels</a>)</li>
  <li><a>Panel.Grid</a> (see the section about <a href="grids.html">Grid Patterns</a>)</li>
  <li><a>Panel.Graduated</a> (see the section about <a href="graduatedPanels.html">Graduated Panels</a>)</li>
</ul>
<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>
<p>
  Note also that one can only add <a>Part</a>s (i.e. <a>Node</a>s and <a>Link</a>s) to <a>Diagram</a>s,
  and that a Part cannot be an element of a Panel.
  But all Parts are Panels because the <a>Part</a> class inherits from <a>Panel</a> -- Parts are basically "top-level" Panels.
  Thus these examples make use of Parts as top-level objects whereas within a Node you would use a Panel instead of a Part.
</p>
<p>
  Panels have no visual elements of their own, so to display their size the <a>GraphObject.background</a> is often used.
  Panels also have <a>Panel.padding</a> in addition to <a>GraphObject.margin</a>.
  The background brush covers the padding area, but does not cover the margin area.
  Setting a padding when the Panel is constrained in size will reduce the total area that it has to arrange its elements.
  Setting a margin will not do this -- instead the Panel will expand in size.
</p>

<h2 id="PositionPanels">Position Panels</h2>
<p>
  The simplest kind of <a>Panel</a> is "Position" (<a>Panel.Position</a>). Each element gets its normal size, whether its natural size or a specified
  <a>GraphObject.desiredSize</a> (or equivalently the <a>GraphObject.width</a> and <a>GraphObject.height</a>).
</p>
<p>
  Each element's position is given by the <a>GraphObject.position</a> property. If no position is specified, the element is positioned at (0,0). All positions
  are in the Panel's own coordinate system, not in the document-wide coordinate system. Positions may include negative coordinates.
</p>
<p>
  The Panel's size is just big enough to hold all of its elements. If you want it to be a bit bigger than that, you can set the <a>Panel.padding</a> property.
</p>
<pre class="lang-js" id="positionPanels"><code>
  diagram.add(
    // all Parts are Panels
    new go.Part(go.Panel.Position,  // or "Position"
        { background: "lightgray" })
      .add(
        new go.TextBlock("default, at (0,0)", { background: "lightgreen" }),
        new go.TextBlock("(100, 0)", { position: new go.Point(100, 0), background: "lightgreen" }),
        new go.TextBlock("(0, 100)", { position: new go.Point(0, 100), background: "lightgreen" }),
        new go.TextBlock("(55, 28)", { position: new go.Point(55, 28), background: "lightgreen" }),
        new go.TextBlock("(33, 70)", { position: new go.Point(33, 70), background: "lightgreen" }),
        new go.TextBlock("(100, 100)", { position: new go.Point(100, 100), background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('positionPanels', 600, 150);
</script>

<p>
  A Position Panel will always include the (0,0) origin point in its own panel coordinate system. Thus a Position Panel that has elements whose collective
  bounds does not include (0,0) is always extended to include the origin.
</p>
<pre class="lang-js" id="zeroPositionPanel"><code>
  diagram.add(
    new go.Part("Position", { background: "lightgray" })
      .add(
        new go.TextBlock("(-50,50)", { position: new go.Point(-50, 50), background: "lightgreen" }),
        new go.TextBlock("(50, 50)", { position: new go.Point(50, 50), background: "lightgreen" }),
        new go.TextBlock("(0, 100)", { position: new go.Point(0, 100), background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('zeroPositionPanel', 600, 140);
</script>

<p>
  Note that when you position <a>Shape</a>s within a Position Panel the thickness of their strokes, <a>Shape.strokeWidth</a>, will be included. If you wish to
  position multiple Shapes so that their geometries line up with each other, independent of how thick their strokes are, set
  <a>Shape.isGeometryPositioned</a> to true on each of those Shapes.
</p>

<h2 id="VerticalPanels">Vertical Panels</h2>
<p>
  A very common kind of <a>Panel</a> is "Vertical" (<a>Panel.Vertical</a>). In this Panel all of the panel elements are arranged vertically from top to bottom.
  Each element gets its normal height and either its normal width or, if stretched, the width of the panel. If the element's <a>GraphObject.stretch</a> property
  has any vertical stretch component, it is ignored.
</p>
<p>
  If the element's width does not happen to be the same as the width of the panel, it is aligned horizontally according to its
  <a>GraphObject.alignment</a> property.
</p>
<p>
  The following Vertical Panel shows how narrow objects are aligned horizontally and how a narrow object may be stretched horizontally. The width of the whole
  Panel is determined by the width of the widest object, which in this case is the first element. Note how the last element does not set the desired
  <a>GraphObject.width</a> property, so that the <a>GraphObject.stretch</a> value is effective.
</p>
<pre class="lang-js" id="verticalPanels"><code>
  diagram.add(
    new go.Part(go.Panel.Vertical,  // or "Vertical"
        { background: "lightgray" })
      .add(
        new go.TextBlock("a longer string", { background: "lightgreen" }),
        new go.TextBlock("left", { alignment: go.Spot.Left, background: "lightgreen" }),
        new go.TextBlock("center", { alignment: go.Spot.Center, background: "lightgreen" }),
        new go.TextBlock("right", { alignment: go.Spot.Right, background: "lightgreen" }),
        new go.TextBlock("stretch", { stretch: go.Stretch.Fill, background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('verticalPanels', 600, 150);
</script>

<h2 id="ConstrainedWidthVerticalPanels">Constrained Width Vertical Panels</h2>
<p>
  A Vertical <a>Panel</a> normally has the width of its widest element and the height that is the sum of all of its elements. However, you can also set the
  width and/or height to be larger or smaller than the natural size. Or if there is a Panel containing this panel, it might impose size constraints on this
  panel. If the width and/or height are larger than the natural size, the panel is bigger, leaving empty space that may be filled with the background brush. If
  the width and/or height are smaller than the natural size, the content elements may be clipped.
</p>
<p>The Vertical Panel below sets the width to be 140, much wider than needed. You can see how the last element's width is stretched.</p>
<pre class="lang-js" id="excessWidth"><code>
  diagram.add(
    new go.Part("Vertical",
        { background: "lightgray", width: 140 })
      .add(
        new go.TextBlock("a longer string", { background: "lightgreen" }),
        new go.TextBlock("left", { alignment: go.Spot.Left, background: "lightgreen" }),
        new go.TextBlock("center", { alignment: go.Spot.Center, background: "lightgreen" }),
        new go.TextBlock("right", { alignment: go.Spot.Right, background: "lightgreen" }),
        new go.TextBlock("stretch", { stretch: go.Stretch.Fill, background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('excessWidth', 600, 150);
</script>
<p>
  These two Vertical Panels both have a width of 50, much less than natural. The latter one also has a restricted height. Note how the text is automatically
  wrapped to try to fit within the limited width, because the default value for <a>TextBlock.wrap</a> is to allow wrapping.
</p>
<pre class="lang-js" id="limitedWidth"><code>
  diagram.add(
    new go.Part("Vertical",
        { position: new go.Point(0, 0), background: "lightgray", width: 50 })
      .add(
        new go.TextBlock("a longer string", { background: "lightgreen" }),
        new go.TextBlock("left", { alignment: go.Spot.Left, background: "lightgreen" }),
        new go.TextBlock("center", { alignment: go.Spot.Center, background: "lightgreen" }),
        new go.TextBlock("right", { alignment: go.Spot.Right, background: "lightgreen" }),
        new go.TextBlock("stretch", { stretch: go.Stretch.Fill, background: "lightgreen" })
      ));
  diagram.add(
    new go.Part("Vertical",
        { position: new go.Point(70, 0), background: "lightgray", width: 50, height: 65 })
      .add(
        new go.TextBlock("a longer string", { background: "lightgreen" }),
        new go.TextBlock("left", { alignment: go.Spot.Left, background: "lightgreen" }),
        new go.TextBlock("center", { alignment: go.Spot.Center, background: "lightgreen" }),
        new go.TextBlock("right", { alignment: go.Spot.Right, background: "lightgreen" }),
        new go.TextBlock("stretch", { stretch: go.Stretch.Fill, background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('limitedWidth', 600, 150);
</script>

<p>
  Here is a Vertical Panel with a default <a>GraphObject.stretch</a> of <a>Stretch.Horizontal</a>. Because no width is specified for the whole panel, its width
  is the width of the widest element, in this case the second one. Note how all of the <a>TextBlock</a>s have the same long width, as highlighted by the
  lightgreen backgrounds. However the last TextBlock has a limited width, so it is not stretched. One can limit the width but not the height by supplying a
  value of <code>NaN</code> or <code>Infinity</code> for the height.
</p>
<pre class="lang-js" id="defaultStretch"><code>
  diagram.add(
    new go.Part("Vertical",
        { background: "lightgray", defaultStretch: go.Stretch.Horizontal })
      .add(
        new go.TextBlock("short", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("a much longer string", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("medium length", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("short2", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("max 50", { margin: 2, maxSize: new go.Size(50, NaN), background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('defaultStretch', 600, 150);
</script>

<p>
  If you change that sample to set the <a>GraphObject.width</a> or <a>GraphObject.desiredSize</a>.width on one or more of the elements (just the last one in
  this case), the panel will get a width that is equal to the maximum of the set widths. The reduced width will cause the other, stretched, elements to be
  measured with the limited width (50 in this case), which cause those <a>TextBlock</a>s to wrap to fit within the available width.
</p>
<pre class="lang-js" id="defaultStretch2"><code>
  diagram.add(
    new go.Part("Vertical",
        { background: "lightgray", defaultStretch: go.Stretch.Horizontal })
      .add(
        new go.TextBlock("short", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("a much longer string", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("medium length", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("short2", { margin: 2, background: "lightgreen" }),
        new go.TextBlock("= 50", { margin: 2, width: 50, background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('defaultStretch2', 600, 150);
</script>

<h2 id="HorizontalPanels">Horizontal Panels</h2>
<p>
  Horizontal <a>Panel</a>s are just like Vertical Panels, except that the elements are arranged horizontally instead of vertically. Elements are never stretched
  horizontally, but they may be stretched vertically. Because elements are never stretched horizontally, a stretch value of <a>Stretch.Fill</a> is the same as
  <a>Stretch.Vertical</a>.
</p>
<p>
  Note that the last element in both panels do not specify a desired <a>GraphObject.height</a>, so that the <a>GraphObject.stretch</a> value may be effective.
</p>
<pre class="lang-js" id="horizontalPanels"><code>
  diagram.add(
    new go.Part(go.Panel.Horizontal,  // or "Horizontal"
        { position: new go.Point(0, 0), background: "lightgray" })
      .add(
        new go.Shape({ width: 30, height: 100, fill: "lightgreen" }),
        new go.Shape({ width: 30, height: 50, alignment: go.Spot.Top, fill: "lightgreen" }),
        new go.Shape({ width: 30, height: 50, alignment: go.Spot.Center, fill: "lightgreen" }),
        new go.Shape({ width: 30, height: 50, alignment: go.Spot.Bottom, fill: "lightgreen" }),
        new go.Shape({ width: 30, stretch: go.Stretch.Fill, fill: "lightgreen" })
      ));
  diagram.add(
    new go.Part("Horizontal",
        { position: new go.Point(200, 0), background: "lightgray", height: 120 })
      .add(
        new go.Shape({ width: 30, height: 50, alignment: go.Spot.Top, fill: "lightgreen" }),
        new go.Shape({ width: 30, height: 50, alignment: go.Spot.Center, fill: "lightgreen" }),
        new go.Shape({ width: 30, height: 50, alignment: go.Spot.Bottom, fill: "lightgreen" }),
        new go.Shape({ width: 30, stretch: go.Stretch.Fill, fill: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('horizontalPanels', 600, 150);
</script>

<h3 id="FillingHorizontalAndVerticalPanelsInOppositeDirection">Filling Horizontal and Vertical Panels in Opposite Direction</h3>
<p>
  Both Vertical and Horizontal <a>Panel</a>s can have their elements be arranged in the opposite direction: bottom-to-top for Vertical Panels and right-to-left
  for Horizontal Panels. Just set <a>Panel.isOpposite</a> to true.
</p>
<pre class="lang-js" id="opposite"><code>
  diagram.add(
    new go.Part("Horizontal",
        { background: "lightgray", isOpposite: true })
      .add(
        new go.TextBlock("0", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("1", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("2", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("3", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("4", { margin: 5, background: "lightgreen" })
      ));

  diagram.add(
    new go.Part("Vertical",
        { background: "lightgray", isOpposite: true })
      .add(
        new go.TextBlock("0", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("1", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("2", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("3", { margin: 5, background: "lightgreen" }),
        new go.TextBlock("4", { margin: 5, background: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('opposite', 600, 150);
</script>

<h2 id="DefaultAlignmentAndStretch">Default Alignment and Stretch</h2>
<p>
  Both Vertical and Horizontal <a>Panel</a>s support the <a>Panel.defaultAlignment</a> and <a>Panel.defaultStretch</a> properties. This is a convenience so that
  you do not need to set the <a>GraphObject.alignment</a> or <a>GraphObject.stretch</a> property on each element.
</p>
<p>
  Here is a Horizontal Panel with a default <a>GraphObject.alignment</a> of <a>Spot.Bottom</a>. All of the <a>Shape</a>s are aligned at the bottom, even though
  the default alignment would normally be <a>Spot.Center</a>. However, the last Shape has its height stretched to the full height of the panel, 90. In this case
  the <a>GraphObject.margin</a> provides a little extra space around the object.
</p>
<pre class="lang-js" id="defaultAlignment"><code>
  diagram.add(
    new go.Part("Horizontal",
        { background: "lightgray", height: 90, defaultAlignment: go.Spot.Bottom })
      .add(
        new go.Shape({ width: 30, margin: 2, height: 60, fill: "lightgreen" }),
        new go.Shape({ width: 30, margin: 2, height: 30, fill: "lightgreen" }),
        new go.Shape({ width: 30, margin: 2, height: 40, fill: "lightgreen" }),
        new go.Shape({ width: 30, margin: 2, stretch: go.Stretch.Fill, fill: "lightgreen" })
      ));
</code></pre>
<script>
  goCode('defaultAlignment', 600, 150);
</script>
<p>
  Vertical and Horizontal Panels are relatively simple ways of arranging a column or a row of objects. For more options, you may need to use a
  <a href="tablePanels.html">Table Panel</a>, even with the same set of objects. This is especially true when you want more control over the stretching of one
  or more elements.
</p>

<h2 id="Spots">Spots</h2>
<p>
  Before we discuss other kinds of <a>Panel</a>s, we should elaborate a bit about the concept of spots.
  <a>Spot</a>s are a way of providing both relative and absolute positioning information.
</p>

<p>
  You have already seen many of the most common uses of Spots, for specifying the alignment of objects within a panel,
  as constant values of the <a>Spot</a> class:
</p>
<table>
  <tr>
    <td><a>Spot.TopLeft</a></td>
    <td><a>Spot.Top</a></td>
    <td><a>Spot.TopRight</a></td>
  </tr>
  <tr>
    <td><a>Spot.Left</a></td>
    <td><a>Spot.Center</a></td>
    <td><a>Spot.Right</a></td>
  </tr>
  <tr>
    <td><a>Spot.BottomLeft</a></td>
    <td><a>Spot.Bottom</a></td>
    <td><a>Spot.BottomRight</a></td>
  </tr>
</table>

<p>
  But Spots are more general than that. The <a>Spot.x</a> and <a>Spot.y</a> properties can be any number between zero and one, inclusive. Those values are the
  fractional distances along the X and Y axes from the top-left corner of an arbitrary rectangle. So <a>Spot.TopLeft</a> is the same as new go.Spot(0, 0),
  <a>Spot.BottomRight</a> is the same as new go.Spot(1, 1), and <a>Spot.Right</a> is the same as new go.Spot(1, 0.5).
</p>

<p>Here are the standard nine Spots shown on a rectangular shape.</p>
<pre class="lang-js" id="standardSpots"><code>
  diagram.add(
    new go.Part(go.Panel.Spot)  // or "Spot"
      .add(
        new go.Shape("Rectangle",  // the main element
            { fill: "lightgreen", stroke: null, width: 100, height: 50 }),
        new go.TextBlock("0,0",     { alignment: new go.Spot(0, 0) }),
        new go.TextBlock("0.5,0",   { alignment: new go.Spot(0.5, 0) }),
        new go.TextBlock("1,0",     { alignment: new go.Spot(1, 0) }),
        new go.TextBlock("0,0.5",   { alignment: new go.Spot(0, 0.5) }),
        new go.TextBlock("0.5,0.5", { alignment: new go.Spot(0.5, 0.5) }),
        new go.TextBlock("1,0.5",   { alignment: new go.Spot(1, 0.5) }),
        new go.TextBlock("0,1",     { alignment: new go.Spot(0, 1) }),
        new go.TextBlock("0.5,1",   { alignment: new go.Spot(0.5, 1) }),
        new go.TextBlock("1,1",     { alignment: new go.Spot(1, 1) })
      ));
</code></pre>
<script>
  goCode('standardSpots', 600, 100);
</script>

<p>
  Besides the fractional positioning of a spot relative to some rectangular area, you can also specify an absolute offset. The <a>Spot.offsetX</a> and
  <a>Spot.offsetY</a> properties determine a point that is a distance from the fractional point given by <a>Spot.x</a> and <a>Spot.y</a>. Here we show three
  TextBlocks near the bottom-left corner and three TextBlocks near the bottom-right corner. The ones on the left are offset along the X-axis plus or minus 40
  units; the ones on the right are offset along the Y-axis plus or minus 20 units. TextBlocks are also given a semi-transparent red background to help
  distinguish their bounds.
</p>
<pre class="lang-js" id="spotOffsets"><code>
  const pink = "rgba(255,0,0,.2)";
  diagram.add(
    new go.Part("Spot")
      .add(
        new go.Shape("Rectangle",
          { fill: "lightgreen", stroke: null, width: 200, height: 50 }),

        // Near bottom-left corner:
        new go.TextBlock("(-40,0)",  { alignment: new go.Spot(0, 1, -40, 0), background: "pink" }),
        new go.TextBlock("(0,0)",    { alignment: new go.Spot(0, 1, 0, 0), background: "pink" }),
        new go.TextBlock("(40,0)",   { alignment: new go.Spot(0, 1, 40, 0), background: "pink" }),

        // Near bottom-right corner:
        new go.TextBlock("(0,-20)",  { alignment: new go.Spot(1, 1, 0, -20), background: "pink" }),
        new go.TextBlock("(0,0)",    { alignment: new go.Spot(1, 1, 0, 0), background: "pink" }),
        new go.TextBlock("(0,20)",   { alignment: new go.Spot(1, 1, 0, 20), background: "pink" })
      ));
</code></pre>
<script>
  goCode('spotOffsets', 600, 100);
</script>

<h2 id="AutoPanels">Auto Panels</h2>
<p>
  Auto <a>Panel</a>s fit a "main" element just around the other elements of the panel. The main element is usually the furthest back in the Z-order, i.e. the
  first element, so that the other elements are not obscured by it. The main element is declared by setting <a>GraphObject.isPanelMain</a> to true; but often no
  such element is present, so it uses the very first element of the panel.
</p>
<p>
  Typically the Auto Panel will measure the non-"main" elements, determine a width and a height that can enclose all of them, and make the "main" element that
  size or slightly bigger. You do <em>not</em> set the <a>GraphObject.desiredSize</a> (or <a>GraphObject.width</a> or <a>GraphObject.height</a>) of the "main"
  element.
</p>
<p>
  An Auto Panel is the normal way to implement a border around an object. Use a <a>Shape</a> as the first/"main" element -- it becomes the border. The
  <a>Shape.figure</a> is normally "Rectangle" or "RoundedRectangle" or "Ellipse", as shown below. The other elements become the "content" for the panel inside
  the border. In the examples below there is only a single "content" element, a <a>TextBlock</a>. We have set the <a>GraphObject.background</a> and
  <a>Shape.fill</a> properties to help show the sizes and positions of objects.
</p>
<p>Auto Panels should have two or more elements in them.</p>
<pre class="lang-js" id="autoPanels"><code>
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(0, 0), background: "lightgray"
      })
      .add(
        new go.Shape("Rectangle", { fill: "lightgreen" }),
        new go.TextBlock("some text", { background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(100, 0), background: "lightgray"
      })
      .add(
        new go.Shape("RoundedRectangle", { fill: "lightgreen" }),
        new go.TextBlock("some text", { background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(200, 0), background: "lightgray"
      })
      .add(
        new go.Shape("Ellipse", { fill: "lightgreen" }),
        new go.TextBlock("some text", { background: "yellow" })
      ));
</code></pre>
<script>
  goCode('autoPanels', 600, 100);
</script>

<p>
  If you add a <a>GraphObject.margin</a> to the <a>TextBlock</a> in each of the same three panels, you will add a little space all around the "content" element
  inside the "main" element.  In this example, that means more "lightgreen" area around the text.
</p>
<pre class="lang-js" id="marginAutoPanels"><code>
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(0, 0), background: "lightgray"
      })
      .add(
        new go.Shape("Rectangle", { fill: "lightgreen" }),
        new go.TextBlock("some text", { margin: 4, background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(100, 0), background: "lightgray"
      })
      .add(
        new go.Shape("RoundedRectangle", { fill: "lightgreen" }),
        new go.TextBlock("some text", { margin: 4, background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(200, 0), background: "lightgray"
      })
      .add(
        new go.Shape("Ellipse", { fill: "lightgreen" }),
        new go.TextBlock("some text", { margin: 4, background: "yellow" })
      ));
</code></pre>
<script>
  goCode('marginAutoPanels', 600, 100);
</script>

<p>
  For most <a>Shape</a>s other than the "Rectangle" figure we do not want to have the "main" shape be the same size as the "content" element.
  Ellipses, for example, need to be significantly larger than the content to avoid having the content corners spill over the edge of the shape.
  This can be controlled by setting the <a>Shape.spot1</a> and <a>Shape.spot2</a> properties, which determine the area where the content should go.
  Many of the predefined figures have their own default values for spot1 and spot2.
</p>
<pre class="lang-js" id="spotAreaAutoPanels"><code>
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(0, 0), background: "lightgray"
      })
      .add(
        new go.Shape("RoundedRectangle", {
            fill: "lightgreen",
            spot1: new go.Spot(0, 0), spot2: new go.Spot(1, 1)
          }),
        new go.TextBlock("some text", { background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(100, 0), background: "lightgray"
      })
      .add(
        new go.Shape("RoundedRectangle", {
            fill: "lightgreen",
            spot1: new go.Spot(0, 0, 10, 0), spot2: new go.Spot(1, 1, -10, -10)
          }),
        new go.TextBlock("some text", { background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        position: new go.Point(200, 0), background: "lightgray"
      })
      .add(
        new go.Shape("RoundedRectangle", {
            fill: "lightgreen",
            spot1: new go.Spot(0, 0, 0, 20), spot2: new go.Spot(1, 1, 0, -20)
          }),
        new go.TextBlock("some text", { background: "yellow" })
      ));
</code></pre>
<script>
  goCode('spotAreaAutoPanels', 600, 100);
</script>
<p>
  The spot1 and spot2 properties on the main <a>Shape</a> are more general and more flexible than specifying the <a>GraphObject.margin</a> on the content
  element(s).
</p>

<h2 id="ConstrainedSizeAutoPanels">Constrained Size Auto Panels</h2>
<p>
  If you constrain the size of the whole panel, there may be less or more space available to fit all of the "content" elements inside the "main" element. If an
  element has a size that won't fit in the available area, it will be clipped. That also applies to the main element -- you normally should not set its
  <a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> or <a>GraphObject.height</a>, because that will cause it too to be clipped (if too big) or to not
  act as a proper border around the other elements of the panel (if too small).
</p>
<p>
  In the following example each Part has a total size of 60x60, causing the "content" <a>TextBlock</a>s to be limited in width and height, less than the natural
  width, which results in wrapping of the text. However there may not be enough height available to show the whole content element(s), causing them to be
  clipped. You can see that in the third Part the text is clipped, because there is less available area within an ellipse than within a rectangle.
</p>
<pre class="lang-js" id="autoPanelsConstrained"><code>
  diagram.add(
    new go.Part("Auto", {
        width: 60, height: 60,  // set the size of the whole panel
        position: new go.Point(0, 0), background: "lightgray"
      })
      .add(
        new go.Shape("Rectangle", { fill: "lightgreen" }),
        new go.TextBlock("Some Wrapping Text", { background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        width: 60, height: 60,  // set the size of the whole panel
        position: new go.Point(100, 0), background: "lightgray"
      })
      .add(
        new go.Shape("RoundedRectangle", { fill: "lightgreen" }),
        new go.TextBlock("Some Wrapping Text", { background: "yellow" })
      ));
  diagram.add(
    new go.Part("Auto", {
        width: 60, height: 60,  // set the size of the whole panel
        position: new go.Point(200, 0), background: "lightgray"
      })
      .add(
        new go.Shape("Ellipse", { fill: "lightgreen" }),
        new go.TextBlock("Some Wrapping Text", { background: "yellow" })
      ));
</code></pre>
<script>
  goCode('autoPanelsConstrained', 600, 100);
</script>
<p>
  You should not set the size (<a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> or <a>GraphObject.height</a>) of the "main" element of an Auto Panel.
</p>
<p>Auto Panels should have two or more elements in them.</p>

<h2 id="SpotPanels">Spot Panels</h2>
<p>
  Spot <a>Panel</a>s are like Auto Panels in that there is a "main" element and there are "other" elements that are not resized.
  The "other" elements are positioned about the "main" element based on the <a>GraphObject.alignment</a> property that has a <a>Spot</a> value.
  The main feature of Spot Panels, unlike Auto Panels, is that those elements may extend beyond the bounds of the "main" element.
</p>
<p>
  This is useful for having the main element be a specific size and then positioning smaller elements at particular places relative to the main shape.
  Note in this example that the TextBlocks are centered at the four corners, causing the panel to be larger than the main shape,
  as can be seen with the light gray background.
</p>
<p>Spot Panels should have two or more elements in them.</p>
<pre class="lang-js" id="spotPanels"><code>
  diagram.add(
    new go.Part("Spot", { background: "lightgray" })
      .add(
        new go.Shape("Rectangle",  // the main element
          { fill: "lightgreen", width: 100, height: 50 }),
        new go.TextBlock("TL", { background: "yellow", alignment: go.Spot.TopLeft }),
        new go.TextBlock("TR", { background: "yellow", alignment: go.Spot.TopRight }),
        new go.TextBlock("BL", { background: "yellow", alignment: go.Spot.BottomLeft }),
        new go.TextBlock("BR", { background: "yellow", alignment: go.Spot.BottomRight })
      ));
</code></pre>
<script>
  goCode('spotPanels', 600, 100);
</script>

<p>
  A Spot Panel aligns its content elements in the general location given by its <a>GraphObject.alignment</a>.
  The precise point in the content element that is positioned defaults to <a>Spot.Center</a>, as seen above.
  But you can set the element's <a>GraphObject.alignmentFocus</a> to use a different spot.
  For example, if you use the same alignmentFocus as the alignment, the elements will be just inside the main element's bounds:
</p>
<pre class="lang-js" id="focusInsideSpotPanels"><code>
  diagram.add(
    new go.Part("Spot", { background: "lightgray" })
      .add(
        new go.Shape("Rectangle", 
          { fill: "lightgreen", width: 100, height: 50 }),
        new go.TextBlock("TL", { background: "yellow",
                alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.TopLeft }),
        new go.TextBlock("TR", { background: "yellow",
                alignment: go.Spot.TopRight, alignmentFocus: go.Spot.TopRight }),
        new go.TextBlock("BL", { background: "yellow",
                alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.BottomLeft }),
        new go.TextBlock("BR", { background: "yellow",
                alignment: go.Spot.BottomRight, alignmentFocus: go.Spot.BottomRight })
      ));
</code></pre>
<script>
  goCode('focusInsideSpotPanels', 600, 100);
</script>

<p>If you use the opposite alignmentFocus as the alignment, the elements will be just outside the main element's bounds:</p>
<pre class="lang-js" id="focusOutsideSpotPanels"><code>
  diagram.add(
    new go.Part("Spot", { background: "lightgray" })
      .add(
        new go.Shape("Rectangle",
          { fill: "lightgreen", width: 100, height: 50 }),
        new go.TextBlock("TL", { background: "yellow",
                alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.BottomRight }),
        new go.TextBlock("TR", { background: "yellow",
                alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomLeft }),
        new go.TextBlock("BL", { background: "yellow",
                alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.TopRight }),
        new go.TextBlock("BR", { background: "yellow",
                alignment: go.Spot.BottomRight, alignmentFocus: go.Spot.TopLeft })
      ));
</code></pre>
<script>
  goCode('focusOutsideSpotPanels', 600, 100);
</script>

<p>alignmentFocus offsetX/Y will also work to offset the alignmentFocus point, the same way it works for Link labels:</p>
<pre class="lang-js" id="focusSpotPanels2"><code>
  diagram.layout = new go.GridLayout({
      wrappingColumn: 10, wrappingWidth: 900, isViewportSized: false
    });
  const blue = "rgba(0,0,255,.2)";
  diagram.add(
    new go.Part("Vertical", { locationObjectName: 'main' })
      .add(
        new go.Panel("Spot")
          .add(
            new go.Shape("Rectangle", {
                name: 'main', stroke: null, width: 100, height: 100, fill: "lightgreen"
              }),
            new go.Shape("Rectangle", {
                fill: "lightcoral", stroke: null, width: 30, height: 30,
                alignment: go.Spot.TopRight, alignmentFocus: go.Spot.TopRight
              })
          ),
        new go.TextBlock("alignment: TopRight,\n alignmentFocus: TopRight",
            { font: '11px sans-serif' })
      ));

  diagram.add(
    new go.Part("Vertical", { locationObjectName: 'main' })
      .add(
        new go.Panel("Spot")
          .add(
            new go.Shape("Rectangle", {
                name: 'main', stroke: null, width: 100, height: 100, fill: "lightgreen"
              }),
            new go.Shape("Rectangle", {
                fill: "lightcoral", stroke: null, width: 30, height: 30,
                alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomRight
              })
          ),
        new go.TextBlock("alignment: TopRight,\n alignmentFocus: BottomRight",
            { font: '11px sans-serif' })
      ));

  diagram.add(
    new go.Part("Vertical", { locationObjectName: 'main' })
      .add(
        new go.Panel("Spot")
          .add(
            new go.Shape("Rectangle", {
                name: 'main', stroke: null, width: 100, height: 100, fill: "lightgreen"
              }),
            new go.Shape("Rectangle", {
                fill: "lightcoral", stroke: null, width: 30, height: 30,
                // BottomRight with offsetX = 15
                alignment: go.Spot.TopRight, alignmentFocus: new go.Spot(1, 1, 15, 0)
              })
          ),
        new go.TextBlock("alignment: TopRight,\n alignmentFocus: BottomRight with offsetX = 15",
          { font: '11px sans-serif' })
      ));
  </code></pre>
<script>
  goCode('focusSpotPanels2', 700, 250);
</script>

<h3 id="AligningSubElementsSpotPanels">Aligning to sub-elements with Spot Panels</h3>
<p>
  You may find it necessary to align an object nested inside a Spot panel with that panel's main element.
  This is often the case when you want an element of a Spot panel to appear to have its own text label or other decorator.
</p>
<p>
  To do this, you can use <a>Panel.alignmentFocusName</a>.
  In the example below, a Spot panel contains a main element and another Panel.
  We want to align the corners of the main element the shape within this panel,
  so we give it a name and set <a>Panel.alignmentFocusName</a> on the panel.
</p>

<pre class="lang-js" id="alignmentFocusName"><code>
  diagram.add(
    new go.Node("Spot")
      .add(
        // Main shape
        new go.Shape({ strokeWidth: 4, fill: 'lime' }),
        // Instead of aligning this Panel, we want to align the shape inside of it, to the corner of the main shape
        new go.Panel("Horizontal", {
              background: 'rgba(255,0,0,0.2)',
              alignment: go.Spot.TopRight,
              alignmentFocus: go.Spot.BottomLeft,
              alignmentFocusName: 'SHAPE'
            })
          .add(
            new go.TextBlock("some\nlong\nlabel", { margin: 8 }),
            new go.Shape({ name: 'SHAPE', width: 20, height: 20, strokeWidth: 0, fill: 'red' })
              .bind("fill", "color")
          )
      ));
  </code></pre>
<script>
  goCode('alignmentFocusName', 600, 300);
</script>

<!-- begin new -->
<h3 id="StretchingWithSpotPanels">Stretching with Spot Panels</h3>
<p>
  When a non-main element in a Spot panel stretches, it takes on the width and/or height of the main element.
  This can be useful for aligning elements within the Panel.
</p>
<p>
  In the example below, the red main element has three elements around it which stretch to its side's length.
  The main element is the <a>Part.resizeObject</a>, and as it changes size the stretched elements will change size accordingly.
</p>
<pre class="lang-js" id="StretchSpotPanels"><code>
  diagram.add(
    new go.Part("Spot", { resizable: true, resizeObjectName: 'MAIN' })
      .add(
        new go.Shape({
          name: 'MAIN', strokeWidth: 0, width: 80, height: 60,
          fill: 'rgba(255,0,0,.8)'  // red
        }),

        new go.Shape({
          stretch: go.Stretch.Vertical, strokeWidth: 0, width: 20,
          fill: 'rgba(0,255,0,.3)', // green
          alignment: go.Spot.Left,
          alignmentFocus: go.Spot.Right
        }),
        new go.Shape({
          stretch: go.Stretch.Vertical, strokeWidth: 0, width: 20,
          fill: 'rgba(0,0,255,.3)' , // blue
          alignment: go.Spot.Right,
          alignmentFocus: go.Spot.Left
        }),
        new go.Shape({
          stretch: go.Stretch.Horizontal, strokeWidth: 0, height: 20,
          fill: 'rgba(255,0,255,.3)' , // pink
          alignment: go.Spot.Bottom,
          alignmentFocus: go.Spot.Top
        })
      ));
  diagram.select(diagram.parts.first());
  </code></pre>
<script>
  goCode('StretchSpotPanels', 600, 300);
</script>

<!-- end new -->

<h3 id="ConstrainingSizeWithSpotPanels">Constraining size with Spot Panels</h3>

<p>
  If you constrain the size of the whole panel, the panel may clip its elements.
  For example, when the whole panel must be 100x50, there is room horizontally
  but not vertically for the main element plus all of its other elements after arranging them.
</p>
<pre class="lang-js" id="clipping"><code>
  diagram.add(
    new go.Part("Spot", {
        background: "lightgray",
        width: 100, height: 50  // it is unusual to set the size!
      })
      .add(
        new go.Shape("Rectangle", { fill: "lightgreen", width: 40, height: 40 }),
        new go.TextBlock("TL", { background: "yellow",
                alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.BottomRight }),
        new go.TextBlock("TR", { background: "yellow",
                alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomLeft }),
        new go.TextBlock("BL", { background: "yellow",
                alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.TopRight }),
        new go.TextBlock("BR", { background: "yellow",
                alignment: go.Spot.BottomRight, alignmentFocus: go.Spot.TopLeft })
      ));
</code></pre>
<script>
  goCode('clipping', 600, 100);
</script>

<p>Spot Panels should have two or more elements in them.</p>

<p>
  Remember that the elements of every panel are drawn in order.
  Normally you want the main element to be behind all of the other elements, so the main element will come first.
  However if you want the main element to be in front of some or all of the other elements,
  you can move the main element not to be the first element of the panel,
  if you also set its <a>GraphObject.isPanelMain</a> property to true.
</p>
<pre class="lang-js" id="spotZorder"><code>
  diagram.add(
    new go.Part("Spot",
        { background: "lightgray" })
      .add(
      new go.TextBlock("TL", { background: "yellow", alignment: go.Spot.TopLeft }),
      new go.TextBlock("TR", { background: "yellow", alignment: go.Spot.TopRight }),
      new go.TextBlock("BL", { background: "yellow", alignment: go.Spot.BottomLeft }),
      new go.TextBlock("BR", { background: "yellow", alignment: go.Spot.BottomRight }),

      // NOTE: the main element isn't first, so it must be declared by setting isPanelMain to true
      new go.Shape("Rectangle", {
          isPanelMain: true,
          fill: "lightgreen", width: 100, height: 50
        })
    ));
</code></pre>
<script>
  goCode('spotZorder', 600, 150);
</script>
<p>
  Note how the opaque Shape, explicitly declared to be the main element, is now visually in front of the
  non-main elements of the Spot Panel because it has been moved to be the last element in the panel.
</p>
<p>
  Without setting <a>GraphObject.isPanelMain</a> to true on the desired main element,
  in this example <a>Panel.findMainElement</a> would return the first TextBlock.
  This would cause all of the other elements to be arranged around that TextBlock.
  Since the TextBlock is small and the rectangular Shape is big and opaque,
  the Shape would cover all of the other TextBlocks, so the user might not see any text,
  depending on the size and alignment of those other TextBlocks.
</p>

<h3 id="ClippingWithSpotPanels">Clipping with Spot Panels</h3>
<p>
  Spot Panels can set <a>Panel.isClipping</a> to true to use the main Panel element as a clipping area instead of a drawn Shape.
  If used, the main element must be a Shape and its stroke and fill will not be drawn.
  When <a>Panel.isClipping</a> is true, the Spot panel will size itself to be the <strong>intersection</strong> of the
  main element bounds and all other elements' bounds, rather than the union of these bounds.
</p>
<p>Example:</p>

<pre class="lang-js" id="clipPictures"><code>
  diagram.layout = new go.GridLayout();

  // Without Panel.isClipping
  diagram.add(
    new go.Part("Spot", { scale: 2 })
      .add(
        new go.Shape("Circle", { width: 55, height: 55, strokeWidth: 0 }),
        new go.Picture("../samples/images/55x55.png", { width: 55, height: 55 })
      ));

  // Using Panel.isClipping
  diagram.add(
    new go.Part("Spot", { isClipping: true, scale: 2 })
      .add(
        new go.Shape("Circle", { width: 55, height: 55, strokeWidth: 0 }),
        new go.Picture("../samples/images/55x55.png", { width: 55, height: 55 })
      ));

  // Using Panel.isClipping and also having a surrounding panel
  diagram.add(
    new go.Part("Spot", { scale: 2 })
      .add(
        new go.Shape("Circle", { width: 65, height: 65, strokeWidth: 0, fill: 'red' }),
        new go.Panel("Spot", { isClipping: true })
          .add(
            new go.Shape("Circle", { width: 55, height: 55, strokeWidth: 0 }),
            new go.Picture("../samples/images/55x55.png", { width: 55, height: 55 })
          )
      ));
</code></pre>
<script>
  goCode('clipPictures', 500, 200);
</script>

<h2 id="ViewboxPanels">Viewbox Panels</h2>
<p>
  Viewbox <a>Panel</a>s contain only a single element that is rescaled to fit the size of the Panel.
</p>
<p>
  This is useful for taking an arbitrary element, especially a <a>Panel</a>,
  and automatically squeezing it to fit in a small fixed-size area.
  The same can be achieved by setting the <a>GraphObject.scale</a> on that element,
  but with a Viewbox Panel that computation is performed automatically.
</p>
<p>
  In this diagram there are two copies of the same Auto <a>Panel</a>, each consisting of a <a>Picture</a> and
  a caption <a>TextBlock</a> surrounded by an Ellipse <a>Shape</a>.
  The one on the left is inside a Viewbox <a>Panel</a> forced to fit in an 80x80 area; the one on the right is its natural size.
  Note that you can still see all of the elements of the panel at a reduced scale so that it can fit inside the Viewbox panel.
  But because the nested panel is taller than it is wider, there is empty space on the sides of the 80x80 Viewbox.
</p>
<pre class="lang-js" id="viewboxPanel"><code>
  diagram.add(
    new go.Part(go.Panel.Viewbox, { // or "Viewbox"
        position: new go.Point(0, 0), background: "lightgray",
        width: 80, height: 80
      })
      .add(  // just a single element
        new go.Panel("Auto")
          .add(
            new go.Shape("Ellipse", { fill: "lightgreen" }),
            new go.Panel("Vertical")
              .add(
                new go.Picture({ source: "images/120x160.png" }),
                new go.TextBlock("a 120x160 kitten")
              )
          )
      ));

  diagram.add(
    new go.Part("Auto", {  // no Viewbox Panel
        position: new go.Point(100, 0), background: "lightgray"
        // no width or height
      })
      .add(
        new go.Shape("Ellipse", { fill: "lightgreen" }),
        new go.Panel("Vertical")
          .add(
            new go.Picture({ source: "images/120x160.png" }),
            new go.TextBlock("a 120x160 kitten")
          )
      ));
</code></pre>
<script>
  goCode('viewboxPanel', 600, 270);
</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>