Bands issue after commit transaction

I have a diagram that uses LayeredDigraphLayout with bands. The initial state of my diagram looks like this.

If I add a node and commit the transaction, the nodes layout properly but my bands aren’t laying out correctly.

If I call diagram.layoutDiagram(true) after committing my transaction, it looks good.

Is there a way to get my bands to re-layout during the diagram.commitTransaction() call? Or do I have to call diagram.layoutDiagram(true) after committing my transaction? Or is there some other way to do this?

Also, if I undo the above transaction using Ctrl-Z, my bands are not updated. I expect undo to revert back to my initial state diagram above (only 1 band showing).

Is there a way for me to get bands updated properly for undo/redo? If I can detect when undo/redo happens (I don’t see a way to detect this looking at the API), then I suppose I can call diagram.layoutDiagram(true) to re-layout my bands as well…

Kendal

The reason that the layout is not updating the positions of the bands is that there is a mechanism in place to avoid making unexpected changes to Diagram objects (such as Nodes or their GraphObjects) while the model is being updated. We’ll consider re-writing the sample so that it doesn’t run into this limitation.

I’m not sure what’s going on with Undo not restoring the visibility of a band object. We’ll investigate that.

Here’s the updated code for the samples/swimbands.html sample:

[code] // 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 TreeLayout where commitLayers is overridden to modify the background Part whose key is “_BANDS”.
function LayeredTreeLayout() {
go.TreeLayout.call(this);
this.layerStyle = go.TreeLayout.LayerUniform; // needed for straight layers
}
go.Diagram.inherit(LayeredTreeLayout, go.TreeLayout);

LayeredTreeLayout.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;
// if visible, set the sizes of particular objects in each band
for (var it = bands.elements; it.next(); ) {
var idx = it.key;
var elt = it.value; // the item panel representing a band
if (idx < layerRects.length) {
elt.visible = true;
var r = layerRects[idx];
elt.position = r.position;
// limit the header text width
var tb = elt.findObject(“TEXTBLOCK”);
if (tb) tb.width = HORIZONTAL ? r.width : r.height;
// option 1: rectangular bands:
var sh = elt.findObject(“SHAPE”);
if (sh) sh.desiredSize = r.size;
// option 2: separator lines:
var ln = elt.findObject(“LINE”);
if (ln) {
if (HORIZONTAL) {
ln.height = r.height;
} else {
ln.width = r.width;
}
}
} else {
elt.visible = false;
}
}
}
};
// end LayeredTreeLayout

function init() {
if (window.goSamples) goSamples(); // init for these samples – you don’t need to call this
var $ = go.GraphObject.make;
myDiagram = $(go.Diagram, “myDiagram”,
{
initialContentAlignment: go.Spot.Center,
layout: $(LayeredTreeLayout, // custom layout is defined above
{
angle: HORIZONTAL ? 0 : 90,
arrangement: HORIZONTAL ? go.TreeLayout.ArrangementVertical : go.TreeLayout.ArrangementHorizontal
}),
“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 LayeredTreeLayout.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",
          $(go.TextBlock,
            {
              name: "TEXTBLOCK",
              angle: HORIZONTAL ? 0 : 270,
              textAlign: "center",
              wrap: go.TextBlock.None,
              font: "bold 11pt sans-serif",
              background: $(go.Brush, go.Brush.Linear, { 0: "lightgray", 1: "whitesmoke" })
            },
            new go.Binding("text")
          ),
          // option 1: rectangular bands:
          $(go.Shape,
            { name: "SHAPE", stroke: null, strokeWidth: 0 },
            new go.Binding("fill", "itemIndex", function(i) { return i % 2 == 0 ? "white" : "lightgray"; }).ofObject())
          // option 2: separator lines:
          //(HORIZONTAL
          //  ? $(go.Shape, "LineV",
          //      { name: "LINE", stroke: "gray", alignment: go.Spot.TopLeft, width: 1 },
          //      new go.Binding("visible", "itemIndex", function(i) { return i > 0; }).ofObject())
          //  : $(go.Shape, "LineH",
          //      { name: "LINE", stroke: "gray", alignment: go.Spot.TopLeft, height: 1 },
          //      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" },
  { key: "twoC", parent: "root" },
  { key: "threeC", parent: "twoC" },
  { key: "threeD", parent: "twoC" },
  { key: "fourB", parent: "threeD" },
  { key: "fourC", parent: "twoC" },
  { key: "fourD", parent: "fourB" },
  { key: "twoD", parent: "root" }
];
myDiagram.model = new go.TreeModel(nodearray);

}[/code]

The problem with the non-undoability of the “Bands” is because that Part is in the “Grid” Layer, which is considered a temporary layer, and the UndoManager by default does not record any Changed events for objects in temporary layers.

So I have modified the code above to put that “Bands” object in the “Background” Layer instead. But I have also set pickable: false so that no mouse events will effect it, and I have set selectable: false so that that Part cannot be selected.

Ran into various issues applying your changes to my project. It did work in some cases like removing a node or undo/redo. But when adding a node with a new band being needed, I had issues (sometimes, the band didn’t show, sometimes showed incorrectly and sometimes it worked).

Unfortunately, these issues aren’t high enough priority for me to investigate further (need to move on to other things). I left my code as is with diagram.layoutDiagram(true) after diagram.commitTransaction() and no undo/redo for now since everything seems good like this. If undo/redo becomes a priority, I will investigate further and can provide more details as to what is going on.

Thanks for your help!
Kendal

We have now released version 1.4.8 which should cause the calls to Model.setDataProperty in the custom Layout to work the way that I think you would expect.

I have hidden my earlier post with the alternative layout implementation that avoided the (now obsolete) problem.

But what I said about the UndoManager not recording changes to Parts that are in Layers that are Layer.isTemporary is still true and will remain true.

With 1.4.8, I verified that I no longer have to re-layout the diagram after committing a transaction to get my bands to update properly. Thanks for the info!

Can you post another sample for 1.4.8 with just the undo/redo fix (using non temporary layer) and I’ll give it a try again?

I also updated the sample, Bands sample, so that the “BANDS” Part is in the “Background” layer instead of the “Grid” layer, and I enabled the UndoManager too.

I now have undo/redo working like a charm with 1.4.8! Thanks!!!