Hi, I’m a new GoJS user in my project with angular 6, and I’d just like to know if it was possible to interact with many diagrams with different nodes or the same nodes. Because when i try, they are not a link between the diagrams
GoJS does not support rendering Links, or really any GraphObjects, across multiple Diagrams (i.e., multiple HTML DIV elements).
But the user can drag-and-drop from one Diagram to another. That is clearly demonstrated in any sample that includes a Palette.
Perhaps you want to show multiple subgraphs within one Diagram? Could you describe more thoroughly what you want?
I would like to represent nodes on different layers. I have about 5 layers and each layer contains nodes and each node can interact with another node whatever its layer. So my idea was to have for each layer a div (diagram) that will contain the nodes of its layer. The problem is at the level of the diagrams since the nodes do not belong to the same diagram it does not manage to make the connection between another diagram. But if it is possible to have a diagram and subdiagrams that interact with each other that can solve my problem.
Is there a reason you could not use Groups arranged as in Swim Lanes ? Note how in that sample the user can draw new links between nodes that are in different lanes.
PoolLayout, defined in that sample, is responsible for arranging its groups to be positioned above/below each other, touching, and aligned and with the same lengths.
i didn’t see it, i think that i can use.
thank
Consider also an alternative arrangement:
Also take a look at SwimLaneLayout extension: Beat Paths with Lanes for Divisions Using SwimLaneLayout
It is implemented at: https://gojs.net/latest/extensions/SwimLaneLayout.js
It is documented at: SwimLaneLayout | GoJS API
Note in the “Swim Bands” diagram the layers cut across the general direction or flow. This is unlike the “Swim Lanes” diagram, where as in a real life swimming pool, the direction or flow is within a lane – you are not supposed to cross between lanes.
The “Swimlane Layout” sample demonstrates how nodes can be assigned to different groups and the SwimlaneLayout arranges all of the nodes and links to put them into layers.
The advantage of these two diagram types is that the layout applies to all of the nodes and links in the diagram, excluding groups. So all of the relationships are considered in doing the layout.
The disadvantage of the “Swim Lanes” diagram type is that each group has its own layout (Group.layout), which means that there is no coordination between groups/lanes when doing the layout.
Thank, walter.
I think that i will use swimlanes, but when i implement PoolLyaout in typescript, the method comparer is never called, and i haven’t the same resultat my pools and my lanes overlap also the lanes. Can you help me.
import * as go from 'gojs';
export class PoolLayout extends go.GridLayout {
private static MINLENGTH = 200; // this controls the minimum length of any swimlane
private static MINBREADTH = 20; // this controls the minimum breadth of any non-collapsed swimlane
constructor() {
super();
this.cellSize = new go.Size(1, 1);
this.wrappingColumn = 1;
this.wrappingWidth = Infinity;
this.isRealtime = false; // don't continuously layout while dragging
this.alignment = go.GridLayout.Position;
// This sorts based on the location of each Group.
// This is useful when Groups can be moved up and down in order to change their order.
this.comparer = function(a, b) {
const ay = a.location.y;
const by = b.location.y;
if (isNaN(ay) || isNaN(by)) { return 0; }
if (ay < by) { return -1; }
if (ay > by) { return 1; }
return 0;
};
}
private computeMinLaneSize(lane) {
if (!lane.isSubGraphExpanded) {return new go.Size(PoolLayout.MINLENGTH, 1); }
return new go.Size(PoolLayout.MINLENGTH, PoolLayout.MINBREADTH);
}
private computeMinPoolSize(pool) {
// assert(pool instanceof go.Group && pool.category === 'Pool');
let len = PoolLayout.MINLENGTH;
pool.memberParts.each(function(lane) {
// pools ought to only contain lanes, not plain Nodes
if (!(lane instanceof go.Group)) {return; }
const holder = lane.placeholder;
if (holder !== null) {
const sz = holder.actualBounds;
len = Math.max(len, sz.width);
}
});
return new go.Size(len, NaN);
}
private computeLaneSize(lane) {
// assert(lane instanceof go.Group && lane.category !== 'Pool');
const sz = this.computeMinLaneSize(lane);
if (lane.isSubGraphExpanded) {
const holder = lane.placeholder;
if (holder !== null) {
const hsz = holder.actualBounds;
sz.height = Math.max(sz.height, hsz.height);
}
}
// minimum breadth needs to be big enough to hold the header
const hdr = lane.findObject('HEADER');
if (hdr !== null) {sz.height = Math.max(sz.height, hdr.actualBounds.height); }
return sz;
}
doLayout = function(coll) {
const poolLayout = this;
const diagram = poolLayout.diagram;
if (diagram === null) { return; }
diagram.startTransaction('PoolLayout');
const pool = poolLayout.group;
if (pool !== null && pool.category === 'Pool') {
// make sure all of the Group Shapes are big enough
const minsize = poolLayout.computeMinPoolSize.bind(poolLayout, pool)();
pool.memberParts.each(function(lane) {
if (!(lane instanceof go.Group)) { return; }
if (lane.category !== 'Pool') {
const shape = lane.resizeObject;
if (shape !== null) { // change the desiredSize to be big enough in both directions
const sz = poolLayout.computeLaneSize.bind(poolLayout, lane)();
shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width));
shape.height = (!isNaN(shape.height)) ? Math.max(shape.height, sz.height) : sz.height;
const cell = lane.resizeCellSize;
if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) {
shape.width = Math.ceil(shape.width / cell.width) * cell.width; }
if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) {
shape.height = Math.ceil(shape.height / cell.height) * cell.height; }
}
}
});
}
diagram.commitTransaction('PoolLayout');
};
}
import * as go from 'gojs';
export class DetailsObjet {
private $ = go.GraphObject.make;
constructor() {}
public partContextMenu =
this.$(go.Adornment, 'Vertical',
this.makeButton('Properties',
(e, obj) => { // the OBJ is this Button
const contextmenu = <go.Adornment>obj.part; // the Button is in the context menu Adornment
const part = contextmenu.adornedPart; // the adornedPart is the Part that the context menu adorns
// now can do something with PART, or with its data, or with the Adornment (the context menu)
if (part instanceof go.Link) { alert(this.linkInfo(part.data));
} else if (part instanceof go.Group) { alert(this.groupInfo(contextmenu));
} else { alert(this.nodeInfo(part.data)); }
}),
this.makeButton('Cut',
(e, obj) => e.diagram.commandHandler.cutSelection(),
o => o.diagram.commandHandler.canCutSelection()),
this.makeButton('Copy',
(e, obj) => e.diagram.commandHandler.copySelection(),
o => o.diagram.commandHandler.canCopySelection()),
this.makeButton('Paste',
(e, obj) => e.diagram.commandHandler.pasteSelection(e.diagram.lastInput.documentPoint),
o => o.diagram.commandHandler.canPasteSelection()),
this.makeButton('Delete',
(e, obj) => e.diagram.commandHandler.deleteSelection(),
o => o.diagram.commandHandler.canDeleteSelection()),
this.makeButton('Undo',
(e, obj) => e.diagram.commandHandler.undo(),
o => o.diagram.commandHandler.canUndo()),
this.makeButton('Redo',
(e, obj) => e.diagram.commandHandler.redo(),
o => o.diagram.commandHandler.canRedo()),
this.makeButton('Group',
(e, obj) => e.diagram.commandHandler.groupSelection(),
o => o.diagram.commandHandler.canGroupSelection()),
this.makeButton('Ungroup',
(e, obj) => e.diagram.commandHandler.ungroupSelection(),
o => o.diagram.commandHandler.canUngroupSelection())
);
nodeInfo(d) { // Tooltip info for a node data object
let str = 'Node ' + d.key + ': ' + d.text + '\n';
if (d.group) {
str += 'member of ' + d.group;
} else {
str += 'top-level node'; }
return str;
}
linkInfo(d) { // Tooltip info for a link data object
return 'Link:\nfrom ' + d.from + ' to ' + d.to;
}
groupInfo(adornment: go.Adornment) { // takes the tooltip, not a group node data object
const g = <go.Group>adornment.adornedPart; // get the Group that the tooltip adorns
const mems = g.memberParts.count;
const links = g.memberParts.filter(p => p instanceof go.Link).count;
return 'Group ' + g.data.key + ': ' + g.data.text + '\n' + mems + ' members including ' + links + ' links';
}
makeButton(text: string, action: (e: go.InputEvent, obj: go.GraphObject) => void, visiblePredicate?: (obj: go.GraphObject) => boolean) {
if (visiblePredicate === undefined) {
visiblePredicate = o => true;
}
return this.$('ContextMenuButton',
this.$(go.TextBlock, text),
{ click: action },
// don't bother with binding GraphObject.visible if there's no predicate
visiblePredicate ? new go.Binding('visible', '', visiblePredicate).ofObject() : {});
}
diagramInfo(model: go.GraphLinksModel) { // Tooltip info for the diagram's model
return 'Model:\n' + model.nodeDataArray.length + ' nodes, ' + model.linkDataArray.length + ' links';
}
stayInGroup(part, pt, gridpt) {
// don't constrain top-level nodes
const grp = part.containingGroup;
if (grp === null) {return pt; }
// try to stay within the background Shape of the Group
const back = grp.resizeObject;
if (back === null) {return pt; }
// allow dragging a Node out of a Group if the Shift key is down
if (part.diagram.lastInput.shift) {return pt; }
const p1 = back.getDocumentPoint(go.Spot.TopLeft);
const p2 = back.getDocumentPoint(go.Spot.BottomRight);
const b = part.actualBounds;
const loc = part.location;
// find the padding inside the group's placeholder that is around the member parts
const m = grp.placeholder.padding;
// now limit the location appropriately
const x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1)) + (loc.x - b.x);
const y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1)) + (loc.y - b.y);
return new go.Point(x, y);
}
updateCrossLaneLinks(group) {
group.findExternalLinksConnected().each(function(l) {
l.visible = (l.fromNode.isVisible() && l.toNode.isVisible());
});
}
groupStyle() { // common settings for both Lane and Pool Groups
return [
{
layerName: 'Background', // all pools and lanes are always behind all nodes and links
background: 'transparent', // can grab anywhere in bounds
movable: true, // allows users to re-order by dragging
copyable: false, // can't copy lanes or pools
avoidable: false, // don't impede AvoidsNodes routed Links
minLocation: new go.Point(NaN, -Infinity), // only allow vertical movement
maxLocation: new go.Point(NaN, Infinity),
resizeCellSize: new go.Size(60 , 60)
},
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify)
];
}
}
test5() {
const $ = go.GraphObject.make;
this.diagram = new go.Diagram();
const laneResizingtool = new LaneResizingtool();
console.log(laneResizingtool);
this.diagram.toolManager.resizingTool = laneResizingtool;
console.log(this.diagram.toolManager);
this.diagram.toolManager.isEnabled = true;
this.diagram.mouseDragOver = function(e) {
if (!e.diagram.selection.all(function(n) { return n instanceof go.Group; })) {
e.diagram.currentCursor = 'not-allowed';
}
};
this.diagram.mouseDrop = function(e) {
if (!e.diagram.selection.all(function(n) { return n instanceof go.Group; })) {
e.diagram.currentTool.doCancel();
}
};
this.diagram.commandHandler.copiesGroupKey = true;
// this.diagram.SelectionMoved = laneResizingtool.relayoutDiagram;
this.diagram.animationManager.isEnabled = true;
this.diagram.undoManager.isEnabled = true;
this.diagram.nodeTemplate =
$(go.Node, 'Table',
{
locationObjectName: 'BODY',
locationSpot: go.Spot.Center,
selectionObjectName: 'BODY',
},
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, 'Rectangle',
{ fill: 'white', portId: '', cursor: 'pointer', fromLinkable: true, toLinkable: true }),
$(go.TextBlock, { margin: 5 },
new go.Binding('text', 'key')),
{ dragComputation: this.detail.stayInGroup } // limit dragging of Nodes to stay within the containing Group, defined above
);
this.diagram.linkTemplate = GoLinkTemplate.getLinkTemplateTest();
this.diagram.toolManager.clickCreatingTool.archetypeNodeData = {
leftArray: [],
rightArray: [],
topArray: [],
bottomArray: []
};
this.diagram.groupTemplate =
$(go.Group, 'Horizontal', this.detail.groupStyle(),
{
selectionObjectName: 'SHAPE', // selecting a lane causes the body of the lane to be highlit, not the label
resizable: true, resizeObjectName: 'SHAPE', // the custom resizeAdornmentTemplate only permits two kinds of resizing
layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{
// isInitial: false, // don't even do initial layout
// isOngoing: false, // don't invalidate layout when nodes or links are added or removed
direction: 0,
columnSpacing: 10,
layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource
}),
computesBoundsAfterDrag: true, // needed to prevent recomputing Group.placeholder bounds too soon
computesBoundsIncludingLinks: false, // to reduce occurrences of links going briefly outside the lane
computesBoundsIncludingLocation: true, // to support empty space at top-left corner of lane
handlesDragDropForMembers: true, // don't need to define handlers on member Nodes and Links
mouseDrop: function(e, grp: go.Group ) { // dropping a copy of some Nodes and Links onto this Group adds them to this Group
if (!e.shift) {return; } // cannot change groups with an unmodified drag-and-drop
// don't allow drag-and-dropping a mix of regular Nodes and Groups
if (!e.diagram.selection.any(function(n) { return n instanceof go.Group; })) {
const ok = grp.addMembers(grp.diagram.selection, true);
if (ok) {
this.detail.updateCrossLaneLinks(grp);
} else {
grp.diagram.currentTool.doCancel();
}
} else {
e.diagram.currentTool.doCancel();
}
},
subGraphExpandedChanged: function(grp) {
const shp = grp.resizeObject;
if (grp.diagram.undoManager.isUndoingRedoing) {return; }
if (grp.isSubGraphExpanded) {
shp.height = grp._savedBreadth;
} else {
grp._savedBreadth = shp.height;
shp.height = NaN;
}
this.detail.updateCrossLaneLinks(grp);
}
},
new go.Binding('isSubGraphExpanded', 'expanded').makeTwoWay(),
// the lane header consisting of a Shape and a TextBlock
$(go.Panel, 'Horizontal',
{
name: 'HEADER',
angle: 270, // maybe rotate the header to read sideways going up
alignment: go.Spot.Left
},
$(go.Panel, 'Horizontal', // this is hidden when the swimlane is collapsed
new go.Binding('visible', 'isSubGraphExpanded').ofObject(),
$(go.Shape, 'Diamond',
{ width: 8, height: 8, fill: 'white' },
new go.Binding('fill', 'color')),
$(go.TextBlock, // the lane label
{ font: 'bold 13pt sans-serif', editable: false, margin: new go.Margin(2, 0, 0, 0) },
new go.Binding('text', 'text').makeTwoWay())
),
$('SubGraphExpanderButton', { margin: 10 }) // but this remains always visible!
), // end Horizontal Panel
$(go.Panel, 'Auto', // the lane consisting of a background Shape and a Placeholder representing the subgraph
$(go.Shape, 'Rectangle', // this is the resized object
{ name: 'SHAPE', fill: 'white' },
new go.Binding('fill', 'color'),
new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify)),
$(go.Placeholder,
{ padding: 12, alignment: go.Spot.TopLeft }),
$(go.TextBlock, // this TextBlock is only seen when the swimlane is collapsed
{
name: 'LABEL',
font: 'bold 13pt sans-serif', editable: false,
angle: 0, alignment: go.Spot.TopLeft, margin: new go.Margin(2, 0, 0, 4)
},
new go.Binding('visible', 'isSubGraphExpanded', function(e) { return !e; }).ofObject(),
new go.Binding('text', 'text').makeTwoWay())
) // end Auto Panel
); // end Group
this.diagram.groupTemplate.resizeAdornmentTemplate =
$(go.Adornment, 'Spot',
$(go.Placeholder),
$(go.Shape, // for changing the length of a lane
{
alignment: go.Spot.Right,
desiredSize: new go.Size(7, 50),
fill: 'lightblue', stroke: 'dodgerblue',
cursor: 'col-resize'
},
new go.Binding('visible', '', function(ad) {
if (ad.adornedPart === null) { return false; }
return ad.adornedPart.isSubGraphExpanded;
}).ofObject()),
$(go.Shape, // for changing the breadth of a lane
{
alignment: go.Spot.Bottom,
desiredSize: new go.Size(50, 7),
fill: 'lightblue', stroke: 'dodgerblue',
cursor: 'row-resize'
},
new go.Binding('visible', '', function(ad) {
if (ad.adornedPart === null) { return false; }
return ad.adornedPart.isSubGraphExpanded;
}).ofObject())
);
this.diagram.groupTemplateMap.add('Pool',
$(go.Group, 'Auto',
this.detail.groupStyle(),
{ // use a simple layout that ignores links to stack the 'lane' Groups on top of each other
layout: $(PoolLayout, { spacing: new go.Size(0, 0) }) // no space between lanes
},
$(go.Shape,
{ fill: 'white' },
new go.Binding('fill', 'color')),
$(go.Panel, 'Table',
{ defaultColumnSeparatorStroke: 'black' },
$(go.Panel, 'Horizontal',
{ column: 0, angle: 270 },
$(go.TextBlock,
{ font: 'bold 16pt sans-serif', editable: false, margin: new go.Margin(2, 0, 0, 0) },
new go.Binding('text').makeTwoWay())
),
$('SubGraphExpanderButton', { margin: 20 }), // but this remains always visible!
$(go.Placeholder,
{ column: 1 })
)
));
this.diagram.linkTemplate = GoLinkTemplate.getLinkTemplateTest();
const test = new go.GraphLinksModel([ // node data
{ key: 'Pool1', text: 'Pool', isGroup: true, category: 'Pool' },
{ key: 'Pool2', text: 'Pool2', isGroup: true, category: 'Pool' },
{ key: 'Lane1', text: 'Lane1', isGroup: true, group: 'Pool1', color: 'lightblue' },
{ key: 'Lane2', text: 'Lane2', isGroup: true, group: 'Pool1', color: 'lightgreen' },
{ key: 'Lane3', text: 'Lane3', isGroup: true, group: 'Pool1', color: 'lightyellow' },
{ key: 'Lane4', text: 'Lane4', isGroup: true, group: 'Pool1', color: 'orange' },
{ key: 'oneA', group: 'Lane1' },
{ key: 'oneB', group: 'Lane1' },
{ key: 'oneC', group: 'Lane1' },
{ key: 'oneD', group: 'Lane1' },
{ key: 'twoA', group: 'Lane2' },
{ key: 'twoB', group: 'Lane2' },
{ key: 'twoC', group: 'Lane2' },
{ key: 'twoD', group: 'Lane2' },
{ key: 'twoE', group: 'Lane2' },
{ key: 'twoF', group: 'Lane2' },
{ key: 'twoG', group: 'Lane2' },
{ key: 'fourA', group: 'Lane4' },
{ key: 'fourB', group: 'Lane4' },
{ key: 'fourC', group: 'Lane4' },
{ key: 'fourD', group: 'Lane4' }, { key: 'fourE', loc: '100 200' , group: 'Lane4' },
{ key: 'fourF', loc: '100 230' , group: 'Lane4' }, { key: 'four', loc: '100 240' , group: 'Lane4' },
{ key: 'Lane5', text: 'Lane5', isGroup: true, group: 'Pool2', color: 'lightyellow' },
{ key: 'Lane6', text: 'Lane6', isGroup: true, group: 'Pool2', color: 'lightgreen' },
{ key: 'fiveA', group: 'Lane5' },
{ key: 'sixA', group: 'Lane6' }
],
[ // link data
{ from: 'oneA', to: 'oneB' },
{ from: 'oneA', to: 'oneC' },
{ from: 'oneB', to: 'oneD' },
{ from: 'oneC', to: 'oneD' },
{ from: 'twoA', to: 'twoB' },
{ from: 'twoA', to: 'twoC' },
{ from: 'twoA', to: 'twoF' },
{ from: 'twoB', to: 'twoD' },
{ from: 'twoC', to: 'twoD' },
{ from: 'twoD', to: 'twoG' },
{ from: 'twoE', to: 'twoG' },
{ from: 'twoF', to: 'twoG' },
{ from: 'fourA', to: 'fourB' },
{ from: 'fourB', to: 'fourC' }, { from: 'four', to: 'fourF' },
{ from: 'fourC', to: 'fourD' }
]);
this.diagram.model = test;
}
import { DetailsObjet } from './detail-objet';
import * as go from 'gojs';
import { CustomLink } from './go.custom';
export class GoLinkTemplate {
private static detail: DetailsObjet = new DetailsObjet();
public static getLinkTemplate(): go.Link {
const $ = go.GraphObject.make;
// go.Diagram.inherit(CustomLink, go.Link);
const link: go.Link =
$(CustomLink,
new go.Binding('routing', 'routing'),
{
routing: go.Link.AvoidsNodes,
corner: 5,
curve: go.Link.JumpGap,
reshapable: true,
resegmentable: true,
relinkableFrom: true,
relinkableTo: true
},
new go.Binding('points').makeTwoWay(),
$(go.Shape, { stroke: '#2F4F4F', strokeWidth: 2, toArrow: 'OpenTriangle' }),
{ // this tooltip Adornment is shared by all links
toolTip:
$(go.Adornment, 'Auto',
$(go.Shape, { fill: '#FFFFCC' }),
$(go.TextBlock, { margin: 4 }, // the tooltip shows the result of calling linkInfo(data)
new go.Binding('text', '', this.detail.linkInfo))
),
// the same context menu Adornment is shared by all links
contextMenu: this.detail.partContextMenu
}
);
return link;
}
public static getLinkTemplateTest(): go.Link {
const $ = go.GraphObject.make;
// go.Diagram.inherit(CustomLink, go.Link);
const link: go.Link =
$(go.Link,
{ routing: go.Link.AvoidsNodes, corner: 5 },
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape)
);
return link;
}
}
result:
I haven’t translated the whole sample, but I did try translating PoolLayout.ts, starting from your code. I noticed that your override of doLayout wasn’t calling the super method. Also some calls to bind were unnecessary, some type declarations were missing, and I changed some function(...) { ... }
to lambdas: (...) => { ... }
.
import * as go from '../release/go.js';
export class PoolLayout extends go.GridLayout {
private static MINLENGTH = 200; // this controls the minimum length of any swimlane
private static MINBREADTH = 20; // this controls the minimum breadth of any non-collapsed swimlane
constructor() {
super();
this.cellSize = new go.Size(1, 1);
this.wrappingColumn = 1;
this.wrappingWidth = Infinity;
this.isRealtime = false; // don't continuously layout while dragging
this.alignment = go.GridLayout.Position;
// This sorts based on the location of each Group.
// This is useful when Groups can be moved up and down in order to change their order.
this.comparer = function(a, b) {
const ay = a.location.y;
const by = b.location.y;
if (isNaN(ay) || isNaN(by)) { return 0; }
if (ay < by) { return -1; }
if (ay > by) { return 1; }
return 0;
};
}
private computeMinLaneSize(lane: go.Group) {
if (!lane.isSubGraphExpanded) {return new go.Size(PoolLayout.MINLENGTH, 1); }
return new go.Size(PoolLayout.MINLENGTH, PoolLayout.MINBREADTH);
}
private computeMinPoolSize(pool: go.Group) {
// assert(pool instanceof go.Group && pool.category === 'Pool');
let len = PoolLayout.MINLENGTH;
pool.memberParts.each((lane: go.Part) => {
// pools ought to only contain lanes, not plain Nodes
if (!(lane instanceof go.Group)) return;
const holder = lane.placeholder;
if (holder !== null) {
const sz = holder.actualBounds;
len = Math.max(len, sz.width);
}
});
return new go.Size(len, NaN);
}
private computeLaneSize(lane: go.Group) {
// assert(lane instanceof go.Group && lane.category !== 'Pool');
const sz = this.computeMinLaneSize(lane);
if (lane.isSubGraphExpanded) {
const holder = lane.placeholder;
if (holder !== null) {
const hsz = holder.actualBounds;
sz.height = Math.max(sz.height, hsz.height);
}
}
// minimum breadth needs to be big enough to hold the header
const hdr = lane.findObject('HEADER');
if (hdr !== null) {sz.height = Math.max(sz.height, hdr.actualBounds.height); }
return sz;
}
public doLayout(coll: go.Diagram | go.Group | go.Iterable<go.Part>) {
const diagram = this.diagram;
if (diagram === null) return;
diagram.startTransaction('PoolLayout');
const pool = this.group;
if (pool !== null && pool.category === 'Pool') {
// make sure all of the Group Shapes are big enough
const minsize = this.computeMinPoolSize(pool);
pool.memberParts.each((lane: go.Part) => {
if (!(lane instanceof go.Group)) return;
if (lane.category !== 'Pool') {
const shape = lane.resizeObject;
if (shape !== null) { // change the desiredSize to be big enough in both directions
const sz = this.computeLaneSize(lane);
shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width));
shape.height = (!isNaN(shape.height)) ? Math.max(shape.height, sz.height) : sz.height;
const cell = lane.resizeCellSize;
if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) {
shape.width = Math.ceil(shape.width / cell.width) * cell.width; }
if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) {
shape.height = Math.ceil(shape.height / cell.height) * cell.height; }
}
}
});
}
// now do all of the usual stuff, according to whatever properties have been set on this GridLayout
super.doLayout(coll);
diagram.commitTransaction('PoolLayout');
};
}