Make Bands/Swim Lanes Static for BandedLDLayout

Hello, I have a gojs diagram (more like an organizational chart), in which each layer is separated with Bands or Swim lanes.
Like this:-

Each swim lane have headers defined, like B1, B2 and B3. Now when I am sliding my diagram let’s say to right, the swim lanes is also getting moved and I am not able to see my band/swim lane headers.
Like this:

This bands are made from BandedLDLayout which is extending my go.LayeredDiagraphLayout.

Any Suggestions, how can I keep my swim lane headers intact even if I am sliding my diagram to right ?

Any help would be helpful.
Thanks!

Do your row/layer header Parts have their own category/template name that is not also used for any other purpose?

If so I think all you need to do is implement a “ViewportBoundsChanged” DiagramEvent listener that repositions each of those kinds of Parts. Something like:

new go.Diagram(. . ., {
    // Whenever the Diagram.position or Diagram.scale changes,
    // update the position of all simple Parts that have category "RowHeader"
    "ViewportBoundsChanged": e => {
      e.diagram.commit(dia => {
        // only iterates through simple Parts in the diagram, not Nodes or Links
        dia.parts.each(part => {
          // and only on those that have the "RowHeader" category
          if (part.category === "RowHeader") {
            part.position = new go.Point(dia.viewportBounds.x, part.actualBounds.y);
          }
        })
      }, null);  // set skipsUndoManager to true, to avoid recording these changes
    },
    . . .
  })

Thanks for your response @walter . Above code works fine, I had one category for that Band template, and it works fine.

Just added one more thing, to keep my Bands above my other nodes so it doesn’t get overlapped when I slide my nodes to left/right, I added part.layerName: ‘Foreground’ so it remains on top.

Hi @walter , for this same solution which you provided, now there is an infiinte horizontal scrolling and the horizontal scroll keeps on happening if my all nodes are slided to left/right and my diagram view does not contain any nodes.

How to prevent the infinte scrolling at some point.?

Have you set Diagram.scrollMode to go.ScrollMode.Infinite? Diagram | GoJS API

If so, don’t.

No, I haven’t set this. Previously it was not there but after re-positioning my bands on ever ViewPortChanged event, it is happening.

This is my code:

this.diagram.addDiagramListener('ViewportBoundsChanged', (e) => {
      e.diagram.commit(diagram => {
        diagram.parts.each(part => {
          if (part.category == 'BANDS') {
            part.position = new go.Point(diagram.viewportBounds.x, part.actualBounds.y);

            part.layerName = foreground
          }
        });
      }, null);
    });

Oh, you are also putting those row/layer header Parts in the “Foreground” Layer? That means those Parts will be included by Diagram.computeBounds, which determines the size of the area being scrolled. So you are continually widening the Diagram.documentBounds, hence the behavior you are seeing.

Make sure those header Parts are either in a Layer that has Layer.isInDocumentBounds set to false, or have Part.isInDocumentBounds set to false. Since you seem to care about those Parts being in front, the latter is the easiest solution. Part | GoJS API So you don’t need to set Part.layerName while scrolling.

If you also print or make an image of the diagram you may want to include the header Parts. Since they don’t have a fixed position relative to everything else in your diagram, you will want to temporarily set Part.isInDocumentBounds to true as well as their Part.position.

I tried. I removed part.LayerName: ‘foreground’ from my ViewPortBoundsChanged Event handler, and added isInDocumentsBounds: false in my Band template (the part which contains my Bands)

Like this:

private setupBandNameTemplate(): go.Part {
    return $(go.Part, 'Position',
      new go.Binding('itemArray'),
      {
        isLayoutPositioned: false,
        locationSpot: new go.Spot(0, 0, 146, 0),
        layerName: 'Background',
        isInDocumentBounds: false,
        pickable: true,
        selectable: false,
        itemTemplate:
          $(go.Panel, 'Horizontal',
            new go.Binding('position', 'bounds', b => b.position),
            this.addBandName(),
            this.addLineSeparator()
          )
      }
    );
  }

After adding this, the Bands is itself not visible. I also tried setting layerName to Foreground, here in my band template and removed setting layerName from my Diagram Listener function, still the issue persists.

That’s odd. Is your layout still setting the Part.position of those header Parts to real Points? What are those values?

I logged the part.position.x and part.position.y after this line:
part.position = new go.Point(diagram.viewportBounds.x, part.actualBounds.y);
in my diagram listener function and the values are:

-151 -10.000000000000021
-123.22222025992926 -10.000000000000021
-65.44443840058216 -10.000000000000021
… similar are the other values as more I scroll to left

When those header positions are (…, -10.000…), is the Diagram.viewport including those positions? If so, those Parts ought to be visible.

I suspect the layout code is actually setting the Part.location, because Part.locationSpot is set to a value making room for the header. Still, if you set the Part.position to a Point that is within (or at the edge) of the viewport, you should see it.

Are the Part’s actualBounds.width and .height reasonable?

Sorry @walter didn’t understand completely, what you want me to cross check.

So you actually have a single Part that has all of the row header labels, right? At any time, is that part’s Part.actualBounds in the Diagram.viewportBounds? I would think that your “ViewportBoundsChanged” DiagramEvent listener would ensure that.

Thus as long as that Part is visible (does Part.isVisible return true?) and as long as that Part isn’t hidden by other Parts that are in front of it, it should be seen.

Yeah these bands part are always visible. I guess there is some misunderstanding. Let me clear my doubt once again. So when I am scrolling my diagram to left, it keeps on scrolling even when my last right most node goes out of the view port.

I want that before even my last right-most node goes out of my viewport, I want scrolling to left should stop and not continue.

Something like a defined width of scrollable area.

I’ll create a simple sample for you.

Thanks @walter . I am attaching some screenshots for a better understanding of this issue.

Step1: Intiial loading of page:

Step 2: Scrolled some to left

Step 3: Again scrolled some to left:

My requirement is the scrolling should stop here. But again I am able to scroll more and my view becomes:

Step 4:

Here, no nodes is in my viewport but I am still able to scroll. This should not happpen.

I want to only scroll if my complete diagram nodes are not fitted in my diagram viewport. But once each node is inside my diagram viewport, the scrolling should not be there.

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv", {
      layout: new go.GridLayout({ wrappingWidth: Infinity, spacing: new go.Size(50, 50) }),
      // reposition myHeaders in the viewport horizontally, but with the document vertically
      "ViewportBoundsChanged": e => {
        myHeaders.position = new go.Point(e.diagram.viewportBounds.x, myHeaders.position.y);
      },
      // make room for myHeaders when scrolled all the way towards the right
      scrollMargin: new go.Margin(0, 0, 0, 100),
      "undoManager.isEnabled": true
    });

// This could have been created via the model too.
const myHeaders =
  new go.Part({
      position: new go.Point(0, 0),
      width: 100, height: 800,
      background: "lavender",
      isInDocumentBounds: false
    });
myDiagram.add(myHeaders);

myDiagram.nodeTemplate =
  new go.Node("Auto", { width: 100, height: 100 })
    .add(
      new go.Shape({ fill: "white" })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })
        .bind("text")
    );

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange" },
  { key: 3, text: "Gamma", color: "lightgreen" },
  { key: 4, text: "Delta", color: "pink" },
  { key: 5, text: "Epsilon", color: "yellow" },
  { key: 6, text: "Zeta", color: "yellow" },
  { key: 7, text: "Eta", color: "yellow" },
  { key: 8, text: "Theta", color: "yellow" },
]);
  </script>
</body>
</html>

Thanks @walter for the sample code. I got the idea, I was actually setting isInDocumentBounds for Band/header part wrong. After setting isInDocumentBounds: false for my bands/headers part, the infinite scrolling issue is fixed.

But I am not encountering a new issue. I have a expander button for some nodes in my diagram, which expands some node and show some options, but on expanding/collapsing some nodes, these headers are getting invisible or might be they are shifting to more left, but those are not visible in my view port.

I also used another diagram event - DocumentBoundsChanged and using the same logic over there what I have in my ViewportBoundsChanged event. But it doesn’t work.

Any ideas how to go ahead with this.

First when nodes are not expanded, headers are visible.

Second when nodes were expanded.

I suspect you do not need to implement a “DocumentBoundsChanged” DiagramEvent listener.

I cannot explain why the header(s) would become invisible when you modify a node. You will need to look at the definition of the headers and any code that modifies them to make sure that their visibility is properly controlled. You can observe your “ViewportBoundsChanged” listener code to make sure that when it is called it is doing what you want. I cannot tell from your screenshots whether or not the viewport had been moved and thus your listener code would have been called.

Thanks @walter for your patience. Actually I debugged some scenarios, and it seems like the band/headers are not getting invisible. These are just moving out of my current viewport and moving somewhere to left.

In the below code:

this.diagram.addDiagramListener('ViewportBoundsChanged', (e) => {
      e.diagram.commit(diagram => {
        diagram.parts.each(part => {
          if (part.category == 'BANDS') {
            part.position = new go.Point(diagram.viewportBounds.x, part.actualBounds.y);
          }
        });
      }, null);
    });

Here we are always updating the position of my headers based on my viewportchanged event, but in some scenario, these positions are not correct.

How to make the position of headers always to leftmost of current viewport?

Please correct me if I am wrong in understanding this code?