How to add an empty band to the layeredDiGraph

hello,
while reviewing your product I encountered this sample code:
http://gojs.net/latest/samples/swimBands.html

I saw a previous question regarding adding more parents to each node, stating I should replace the
TreeLayout with the LayeredDiGraphLayout.

More on that I want the diagram to be able to handle an empty band such as follows:

Any idea how can i achieve that?
thanks in advance

Which layout will you be using?

I’ll be using the LayeredDiGraphLayout

I already had a solution for you when using TreeLayout, but I need to figure out if there is a reasonable solution when using LayeredDigraphLayout.

This is a modified samples/swimBands.html:

  // this controls whether the layout is horizontal and the layer bands are vertical, or vice-versa:
  var HORIZONTAL = true;  // this constant parameter can only be set here, not dynamically

  // Perform a LayeredDigraphLayout where commitLayers is overridden to modify the background Part whose key is "_BANDS".
  function BandedLDLayout() {
    go.LayeredDigraphLayout.call(this);
  }
  go.Diagram.inherit(BandedLDLayout, go.LayeredDigraphLayout);

  /** @override */
  BandedLDLayout.prototype.assignLayers = function() {
    go.LayeredDigraphLayout.prototype.assignLayers.call(this);
    var maxlayer = this.maxLayer;
    // now assign specific layers
    var it = this.network.vertexes.iterator;
    while (it.next()) {
      var v = it.value;
      if (v.node !== null) {
        var lay = v.node.data.layer;
        if (typeof lay === "number" && lay >= 0 && lay <= maxlayer) {
          v.layer = lay;
        }
      }
    }
  };

  /** @override */
  BandedLDLayout.prototype.commitLayers = function(layerRects, offset) {
    // update the background object holding the visual "bands"
    var bands = this.diagram.findPartForKey("_BANDS");
    if (bands) {
      var model = this.diagram.model;
      bands.location = this.arrangementOrigin.copy().add(offset);

      // make each band visible or not, depending on whether there is a layer for it
      for (var it = bands.elements; it.next(); ) {
        var idx = it.key;
        var elt = it.value;  // the item panel representing a band
        elt.visible = idx < layerRects.length;
      }

      // set the bounds of each band via data binding of the "bounds" property
      var arr = bands.data.itemArray;
      for (var i = 0; i < layerRects.length; i++) {
        var itemdata = arr[i];
        if (itemdata) {
          model.setDataProperty(itemdata, "bounds", layerRects[i]);
        }
      }
    }
  };
  // end BandedLDLayout


  function init() {
    if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
    var $ = go.GraphObject.make;

    myDiagram = $(go.Diagram, "myDiagramDiv",
                  {
                    initialContentAlignment: go.Spot.Center,
                    layout: $(BandedLDLayout,
                              {
                                direction: HORIZONTAL ? 0 : 90
                              }),  // custom layout is defined above
                    "undoManager.isEnabled": true
                  });

    myDiagram.nodeTemplate =
      $(go.Node, go.Panel.Auto,
        $(go.Shape, "Rectangle",
          { fill: "white" }),
        $(go.TextBlock, { margin: 5 },
          new go.Binding("text", "key")));

    // There should be at most a single object of this category.
    // This Part will be modified by BandedLDLayout.commitLayers to display visual "bands"
    // where each "layer" is a layer of the tree.
    // This template is parameterized at load time by the HORIZONTAL parameter.
    // You also have the option of showing rectangles for the layer bands or
    // of showing separator lines between the layers, but not both at the same time,
    // by commenting in/out the indicated code.
    myDiagram.nodeTemplateMap.add("Bands",
      $(go.Part, "Position",
        new go.Binding("itemArray"),
        {
          isLayoutPositioned: false,  // but still in document bounds
          locationSpot: new go.Spot(0, 0, HORIZONTAL ? 0 : 16, HORIZONTAL ? 16 : 0),  // account for header height
          layerName: "Background",
          pickable: false,
          selectable: false,
          itemTemplate:
            $(go.Panel, HORIZONTAL ? "Vertical" : "Horizontal",
              new go.Binding("position", "bounds", function(b) { return b.position; }),
              $(go.TextBlock,
                {
                  angle: HORIZONTAL ? 0 : 270,
                  textAlign: "center",
                  wrap: go.TextBlock.None,
                  font: "bold 11pt sans-serif",
                  background: $(go.Brush, "Linear", { 0: "aqua", 1: go.Brush.darken("aqua") })
                },
                new go.Binding("text"),
                // always bind "width" because the angle does the rotation
                new go.Binding("width", "bounds", function(r) { return HORIZONTAL ? r.width : r.height; })
              ),
              // option 1: rectangular bands:
              $(go.Shape,
                { stroke: null, strokeWidth: 0 },
                new go.Binding("desiredSize", "bounds", function(r) { return r.size; }),
                new go.Binding("fill", "itemIndex", function(i) { return i % 2 == 0 ? "whitesmoke" : go.Brush.darken("whitesmoke"); }).ofObject())
              // option 2: separator lines:
              //(HORIZONTAL
              //  ? $(go.Shape, "LineV",
              //      { stroke: "gray", alignment: go.Spot.TopLeft, width: 1 },
              //      new go.Binding("height", "bounds", function(r) { return r.height; }),
              //      new go.Binding("visible", "itemIndex", function(i) { return i > 0; }).ofObject())
              //  : $(go.Shape, "LineH",
              //      { stroke: "gray", alignment: go.Spot.TopLeft, height: 1 },
              //      new go.Binding("width", "bounds", function(r) { return r.width; }),
              //      new go.Binding("visible", "itemIndex", function(i) { return i > 0; }).ofObject())
              //)
            )
        }
      ));

    myDiagram.linkTemplate =
      $(go.Link,
        $(go.Shape));  // simple black line, no arrowhead needed

    // define the tree node data
    var nodearray = [
      { // this is the information needed for the headers of the bands
        key: "_BANDS",
        category: "Bands",
        itemArray: [
          { text: "Zero" },
          { text: "One" },
          { text: "Two" },
          { text: "Three" },
          { text: "Four" },
          { text: "Five" }
        ]
      },
      // these are the regular nodes in the TreeModel
      { key: "root" },
      { key: "oneB", parent: "root" },
      { key: "twoA", parent: "oneB", layer: 0 },
      { key: "twoC", parent: "root" },
      { key: "threeC", parent: "twoC" },
      { key: "threeD", parent: "twoC" },
      { key: "fourB", parent: "threeD", layer: 2 },
      { key: "fourC", parent: "twoC", layer: 0 },
      { key: "fourD", parent: "fourB" },
      { key: "twoD", parent: "root", layer: 1 }
    ];

    myDiagram.model = new go.TreeModel(nodearray);
  }

Can you please explain how the layer property should be set and when it should be left out? It makes absolutely no sense how this solution works. Some of the nodes set the layer but others don’t. fourD skips stage Three through magic it seems as it does not define layer. What if fourC wanted to appear in stage Five?

I am totally confused.

Ken
P.S. My company has a current license for the software so if you want to help me offline that is fine.

First, the layout assigns a layer to each node based on its relationships. Then, if a node has a data.layer assignment, it adjusts which layer it is actually placed in. Finally the layout is performed with those layers.

Did you have layer assignments for all of your nodes? I don’t think that is the normal scenario for this layout.

By the way, I notice now that the model is a TreeModel. That is also unlikely if you are using a LayeredDigraphLayout. But layouts are independent of models, so it’s OK for this example.