This is what I got: 
Here is my code
- Pool template
protected getPart(): Group {
const group: Group = makeGraphObject(Group, 'Auto');
// group.layerName = 'Background'; // all pools and lanes are always behind all nodes and links
group.background = new Brush('null'); // can grab anywhere in bounds
group.movable = true; // allows users to re-order by dragging
group.copyable = false; // can't copy lanes or pools
group.avoidable = false; // don't impede AvoidsNodes routed Links
group.locationSpot = Spot.Center;
group.bind(new Binding('location', 'loc', Point.parse).makeTwoWay(Point.stringify));
group.computesBoundsIncludingLinks = false;
// use a simple layout that ignores links to stack the 'lane' Groups on top of each other
group.layout = makeGraphObject(PoolLayout);
(group.layout as any).spacing = new Size(0, 0); // no space between lanes
group.reshapable = true;
group.bind(new Binding('zOrder'));
group.bind(new Binding('location'));
// add shape
const shape: Shape = makeGraphObject(Shape);
shape.fill = 'null';
// shape.bind(new Binding('fill', 'color'));
group.add(shape);
const table = makeGraphObject(Panel, 'Table');
table.defaultColumnSeparatorStroke = 'black';
const panel = makeGraphObject(Panel, 'Horizontal');
panel.column = 0;
panel.angle = 270;
// add text block
const textBlock: TextBlock = makeGraphObject(TextBlock);
textBlock.editable = true;
textBlock.margin = new Margin(5, 0, 5, 0);
textBlock.font = 'bold 12pt Helvetica, Arial, sans-serif';
textBlock.bind(new Binding('text').makeTwoWay());
panel.add(textBlock);
table.add(panel);
const placeholder = makeGraphObject(Placeholder);
placeholder.background = 'darkgray';
placeholder.column = 1;
table.add(placeholder);
group.add(table);
// add ports
group.add(this.makePort('T1', new Spot(0, 0.25), true, true));
group.add(this.makePort('T2', new Spot(0, 0.5), true, true));
group.add(this.makePort('T3', new Spot(0, 0.75), true, true));
group.add(this.makePort('L1', new Spot(0.25, 0), true, true));
group.add(this.makePort('L2', new Spot(0.5, 0), true, true));
group.add(this.makePort('L3', new Spot(0.75, 0), true, true));
group.add(this.makePort('R1', new Spot(0.25, 1), true, true));
group.add(this.makePort('R2', new Spot(0.5, 1), true, true));
group.add(this.makePort('R3', new Spot(0.75, 1), true, true));
group.add(this.makePort('B1', new Spot(1, 0.25), true, true));
group.add(this.makePort('B2', new Spot(1, 0.5), true, true));
group.add(this.makePort('B3', new Spot(1, 0.75), true, true));
group.mouseEnter = ((e, obj) => { this.showPorts(obj.part as Group, true); });
group.mouseLeave = ((e, obj) => { this.showPorts(obj.part as Group, false); });
return group;
}
- Lane template
protected getPart(): Group {
const group: Group = makeGraphObject(Group, 'Spot');
group.name = 'Lane';
group.layerName = 'Background';
group.minLocation = new Point(NaN, -Infinity); // only allow vertical movement
group.maxLocation = new Point(NaN, Infinity);
group.bind(new Binding('location', 'loc', Point.parse).makeTwoWay(Point.stringify));
group.selectionObjectName = 'SHAPE'; // selecting a lane causes the body of the lane to be highlit, not the label
group.resizable = true;
// group.opacity = 0;
group.resizeObjectName = 'SHAPE'; // the custom resizeAdornmentTemplate only permits two kinds of resizing
const layout = makeGraphObject(LayeredDigraphLayout);
layout.isInitial = false; // don't even do initial layout
layout.isOngoing = false; // don't invalidate layout when nodes or links are added or removed
layout.direction = 0;
layout.columnSpacing = 10;
layout.layeringOption = LayeredDigraphLayout.LayerLongestPathSource;
group.layout = layout;
group.computesBoundsAfterDrag = true; // needed to prevent recomputing Group.placeholder bounds too soon
group.computesBoundsIncludingLinks = false; // to reduce occurrences of links going briefly outside the lane
group.computesBoundsIncludingLocation = true; // to support empty space at top-left corner of lane
group.handlesDragDropForMembers = true; // don't need to define handlers on member Nodes and Links
group.mouseDrop = ((e: InputEvent, grp: Group) => { // dropping a copy of some Nodes and Links onto this Group adds them to this Group
if (!e.diagram.selection.any((n) => {
return (n instanceof Group && n.category !== 'subprocess') || n.category === 'privateProcess'; })) {
const ok = grp.addMembers(grp.diagram.selection, true);
if (ok) {
PoolHelper.updateCrossLaneLinks(grp);
PoolHelper.relayoutDiagram(e);
} else {
grp.diagram.currentTool.doCancel();
}
}
});
group.subGraphExpandedChanged = ((grp: Group) => {
const shp = grp.resizeObject;
if (grp.diagram.undoManager.isUndoingRedoing) {
return;
}
if (grp.isSubGraphExpanded) {
shp.height = (grp as any)._savedBreadth;
} else {
(grp as any)._savedBreadth = shp.height;
shp.height = NaN;
}
PoolHelper.updateCrossLaneLinks(grp);
});
const shape = makeGraphObject(Shape, 'Rectangle'); // this is the resized object
shape.name = 'SHAPE';
shape.fill = 'white';
shape.stroke = null; // need stroke null here or you gray out some of pool border.
shape.bind(new Binding('fill', 'color'));
shape.bind(new Binding('desiredSize', 'size', Size.parse).makeTwoWay(Size.stringify));
group.add(shape);
// the lane header consisting of a Shape and a TextBlock
const panel = makeGraphObject(Panel, 'Horizontal');
panel.name = 'HEADER';
panel.angle = 270; // maybe rotate the header to read sideways going up
panel.alignment = Spot.LeftCenter;
panel.alignmentFocus = Spot.LeftCenter;
group.add(panel);
const textBox = makeGraphObject(TextBlock); // the lane label
textBox.editable = true;
textBox.margin = new Margin(2, 0, 0, 8);
textBox.bind( new Binding('visible', 'isSubGraphExpanded').ofObject());
panel.add(textBox);
textBox.bind(new Binding('text', 'text').makeTwoWay());
const button = GraphObject.make('SubGraphExpanderButton');
button.margin = 4;
button.angle = -270; // but this remains always visible!
panel.add(button);
const placeholder = makeGraphObject(Placeholder);
placeholder.padding = 12;
placeholder.alignment = Spot.TopLeft;
placeholder.alignmentFocus = Spot.TopLeft;
group.add(placeholder);
const collapsedPanel = makeGraphObject(Panel, 'Horizontal');
collapsedPanel.alignment = Spot.TopLeft;
collapsedPanel.alignmentFocus = Spot.TopLeft;
group.add(collapsedPanel);
const collapsedTextBlock = makeGraphObject(TextBlock); // this TextBlock is only seen when the swimlane is collapsed
collapsedTextBlock.name = 'LABEL';
collapsedTextBlock.editable = true;
collapsedTextBlock.visible = false;
collapsedTextBlock.angle = 0;
collapsedTextBlock.margin = new Margin(6, 0, 0, 20);
collapsedTextBlock.bind(new Binding('visible', 'isSubGraphExpanded', (e) => !e).ofObject());
collapsedTextBlock.bind(new Binding('text', 'text').makeTwoWay());
collapsedPanel.add(collapsedTextBlock);
group.resizeAdornmentTemplate = this.getResizeAdornmentTemplate();
return group;
}
// define a custom resize adornment that has two resize handles if the group is expanded
private getResizeAdornmentTemplate(): Adornment {
const adornment = makeGraphObject(Adornment, 'Spot');
const placeholder = makeGraphObject(Placeholder);
adornment.add(placeholder);
const shapeL = makeGraphObject(Shape); // for changing the length of a lane
shapeL.desiredSize = new Size(7, 50);
shapeL.alignment = Spot.Right;
shapeL.fill = 'lightblue';
shapeL.stroke = 'dodgerblue';
shapeL.cursor = 'col-resize';
shapeL.bind(new Binding('visible', '', (ad) => {
if (ad.adornedPart === null) {
return false;
}
return ad.adornedPart.isSubGraphExpanded;
}).ofObject());
adornment.add(shapeL);
const shapeW = makeGraphObject(Shape); // for changing the length of a lane
shapeW.desiredSize = new Size(50, 7);
shapeW.alignment = Spot.Bottom;
shapeW.fill = 'lightblue';
shapeW.stroke = 'dodgerblue';
shapeW.cursor = 'col-resize';
shapeW.bind(new Binding('visible', '', (ad) => {
if (ad.adornedPart === null) {
return false;
}
return ad.adornedPart.isSubGraphExpanded;
}).ofObject());
adornment.add(shapeW);
return adornment;
}