Here’s one such extension of TreeLayout that assumes the bands are implemented using a single background Part holding the visual “bands”:
// Perform a TreeLayout where the node's actual tree-layer is specified by the "band" property on the node data.
// This implementation only works when angle == 0, but could be easily modified to support other angles.
function LayeredTreeLayout() {
go.TreeLayout.call(this);
this.treeStyle = go.TreeLayout.StyleLayered; // required
// new in GoJS v1.4
this.layerStyle = go.TreeLayout.LayerUniform;
// don't move subtrees closer together, to maintain possible empty spaces between layers
this.compaction = go.TreeLayout.CompactionNone;
// move the parent node towards the top of its subtree area
this.alignment = go.TreeLayout.AlignmentStart;
// sort a parent's child vertexes by the value of the index property
function compareIndexes(v, w) {
var vidx = v.index;
if (vidx === undefined) vidx = 0;
var widx = w.index;
if (widx === undefined) widx = 0;
return vidx-widx;
}
this.sorting = go.TreeLayout.SortingAscending;
this.comparer = compareIndexes;
//this.setsPortSpot = false;
this.setsChildPortSpot = false;
}
go.Diagram.inherit(LayeredTreeLayout, go.TreeLayout);
// Modify the standard LayoutNetwork by making children with the same "band" value as their
// parents actually be children of the grandparent.
LayeredTreeLayout.prototype.makeNetwork = function(coll) {
var net = go.TreeLayout.prototype.makeNetwork.call(this, coll);
// annotate every child with an index, used for sorting
for (var it = net.vertexes.iterator; it.next();) {
var parent = it.value;
var idx = 0;
for (var cit = parent.destinationVertexes; cit.next();) {
var child = cit.value;
child.index = idx;
idx += 10000;
}
}
// now look for children with the same band value as their parent
for (var it = net.vertexes.iterator; it.next();) {
var parent = it.value;
// Should this be recursively looking for grandchildren/greatgrandchildren that
// have the same band as this parent node?? Assume that is NOT required.
var parentband = parent.node.data.band;
var edges = [];
for (var eit = parent.destinationEdges; eit.next();) {
var edge = eit.value;
var child = edge.toVertex;
var childband = child.node.data.band;
if (childband <= parentband) edges.push(edge);
}
// for each LayoutEdge that connects the parent vertex with a child vertex
// whose node has the same band #, reconnect the edge with the parent's parent vertex
var grandparent = parent.sourceVertexes.first();
if (grandparent !== null) {
var cidx = 1;
for (var i = 0; i < edges.length; i++) {
var e = edges[i];
parent.deleteDestinationEdge(e);
e.fromVertex = grandparent;
grandparent.addDestinationEdge(e);
var child = e.toVertex;
child.index = parent.index + cidx;
cidx++;
}
}
}
return net;
};
LayeredTreeLayout.prototype.assignTreeVertexValues = function(v) {
if (v.node && v.node.data && v.node.data.band) {
v.originalLevel = v.level; // remember tree assignment
v.level = Math.max(v.level, v.node.data.band); // shift down to meet band requirement
}
};
LayeredTreeLayout.prototype.commitLayers = function(layerRects, offset) {
for (var it = this.network.vertexes.iterator; it.next(); ) {
var v = it.value;
var n = v.node;
if (n && v.originalLevel) {
// the band specifies the horizontal position
var diff = n.data.band - v.originalLevel;
if (diff > 0) {
var pos = v.bounds.position;
// this assumes that the angle is zero: rightward growth
pos.x = layerRects[v.level].x;
n.move(pos);
}
}
}
// update the background object holding the visual "bands"
var bands = this.diagram.findPartForKey("_BANDS");
if (bands) {
bands.layerRects = layerRects; // remember the Array of Rect
var model = this.diagram.model;
for (var it = this.network.vertexes.iterator; it.next(); ) {
var v = it.value;
model.setDataProperty(v.node.data, "level", v.level);
}
bands.location = this.arrangementOrigin.copy().add(offset);
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]);
}
}
}
};
For example, this is the background “bands” part:
// there should be a single object of this category;
// it will be modified by LayeredTreeLayout to display visual "bands"
myDiagram.nodeTemplateMap.add("VerticalBands",
$(go.Part, "Position",
{
isLayoutPositioned: false, // but still in document bounds
locationSpot: new go.Spot(0, 0, 0, 16), // account for header height
layerName: "Grid", // not pickable, not selectable
itemTemplate:
$(go.Panel, "Vertical",
new go.Binding("opacity", "visible", function(v) { return v ? 1 : 0; }),
new go.Binding("position", "bounds", function(b) { return b.position; }),
$(go.TextBlock,
{
stretch: go.GraphObject.Horizontal,
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"),
new go.Binding("width", "bounds", function(r) { return r.width; })),
// for separator lines:
//$(go.Shape, "LineV",
// { stroke: "gray", alignment: go.Spot.Left, width: 1 },
// new go.Binding("height", "bounds", function(r) { return r.height; }),
// new go.Binding("visible", "itemIndex", function(i) { return i > 0; }).ofObject()),
// for 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 ? "white" : "lightgray"; }).ofObject())
)
},
new go.Binding("itemArray")
));
which could be set up in the model via:
// define the tree node data
var nodearray = [
{
key: "_BANDS",
category: "VerticalBands",
itemArray: [
{ text: "Zero" },
{ text: "One" /*, visible: false*/ },
{ text: "Two" },
{ text: "Three" },
{ text: "Four" }
]
},
{ key: "root", band: 0 },
{ key: "oneB", band: 3, parent: "root" },
{ key: "twoA", band: 4, parent: "oneB" },
{ key: "twoC", band: 2, parent: "root" },
{ key: "threeC", band: 4, parent: "twoC" },
{ key: "threeD", band: 2, parent: "twoC" },
{ key: "fourB", band: 2, parent: "threeD" },
{ key: "fourC", band: 3, parent: "twoC" },
{ key: "fourD", band: 4, parent: "fourB" },
{ key: "twoD", band: 1, parent: "root" }
];
myDiagram.model = new go.TreeModel(nodearray);