With SwimLaneLayout, I noticed that the node order in nodeDataArray decides the node order in the same layer. For example, the following diagram has the nodeDataArray and linkDataArray as
const nodeDataArray = [
{
key: "A",
isGroup: true,
},
{
key: "Box1",
group: "A",
},
{
key: "Box2",
group: "A",
},
{
key: "Box3",
group: "A",
},
{
key: "Box4",
group: "A",
},
{
key: "Box5",
group: "A",
},
];
const linkDataArray = [
{
from: "Box1",
to: "Box2",
},
{
from: "Box1",
to: "Box3",
},
{
from: "Box1",
to: "Box4",
},
{
from: "Box2",
to: "Box5",
},
];
If I reorder the nodeDataArray by moving Box2 below Box4 as
[
{
key: "Box1",
group: "A",
},
{
key: "Box3",
group: "A",
},
{
key: "Box4",
group: "A",
},
{
key: "Box2",
group: "A",
},
{
key: "Box5",
group: "A",
},
];
The figure changes to
I want to animate the reordering by clicking a button. If I assigned a new array to diagram.model.nodeDataArray
between diagram.startTransation
and diagram.commitTransaction
, the animation involved all the nodes as shown below because I updated nodeDataArray
as a whole.
myDiagram.startTransaction("reorder node");
myDiagram.model.nodeDataArray = [
{
key: "Box1",
group: "A",
},
{
key: "Box3",
group: "A",
},
{
key: "Box4",
group: "A",
},
{
key: "Box2",
group: "A",
},
{
key: "Box5",
group: "A",
},
];
myDiagram.commitTransaction("reorder node");
If I removed box2 and added it back between diagram.startTransation
and diagram.commitTransaction
, the animation is less aggressive as shown below, which I think looks better.
myDiagram.startTransaction("reorder node 2");
let removed;
const nodeId = myDiagram.nodes;
while (nodeId.next()) {
const node = nodeId.value;
if (node.data.key === "Box2") {
removed = node.data;
break;
}
}
if (removed) {
myDiagram.model.removeNodeData(removed);
myDiagram.model.addNodeData({
key: "Box2",
group: "A",
});
}
myDiagram.commitTransaction("reorder node 2");
So, my question is if removing and adding the same node is the correct approach to implement the node reordering in SwimLaneLayout? Is there any better approach?
I am looking for a better approach because removing and adding the same node does NOT work if I want to move Box2 between Box3 and Box4 since Box2 is always added to the end of the nodeDataArray. Besides addNodeData and removeNodeData, is there any model method to insert node to a specific index?
I am adding the complete HTML + JS code below so that you can play with.
- Clicking the Reorder Node button causes the aggressive animation as shown in the first animated gif,
- Clicking the Incremental Reorder Node button causes the less aggressive animation as shown in the second animated gif,
- Clicking the Reorder Box2 to middle button moves Box2 between Box3 and Box4 but I could only accomplish it by reassigning
diagram.model.nodeDataArray
. Does GoJS have any approach to only animated the affect nodes and links?
Thanks so much for your time!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div
id="myDiagramDiv"
style="border: solid 1px black; width: 100%; height: 700px"
></div>
<div class="toolbar">
<button id="reorder">Reorder Node</button>
<button id="reorder2">Incremental Reorder Node</button>
<button id="reorder3">Reorder Box2 to middle</button>
</div>
<script src="../../release/go-debug.js"></script>
<script src="../../extensions/SwimLaneLayout.js"></script>
<script>
let myDiagram;
function init() {
const $ = go.GraphObject.make;
myDiagram = $(go.Diagram, "myDiagramDiv", {
layout: $(SwimLaneLayout, {
laneProperty: "group",
layerSpacing: 20,
commitLayers: function (layerRects, offset) {
if (layerRects.length === 0) return;
var horiz = true;
var forwards = true;
var rect = layerRects[forwards ? layerRects.length - 1 : 0];
var totallength = horiz ? rect.right : rect.bottom;
for (var i = 0; i < this.laneNames.length; i++) {
var lane = this.laneNames[i];
// assume lane names do not conflict with node names
var group = this.diagram.findNodeForKey(lane);
if (group === null) {
this.diagram.model.addNodeData({ key: lane, isGroup: true });
group = this.diagram.findNodeForKey(lane);
}
if (horiz) {
group.location = new go.Point(
-this.layerSpacing / 2,
this.lanePositions.get(lane) * this.columnSpacing +
offset.y,
);
} else {
group.location = new go.Point(
this.lanePositions.get(lane) * this.columnSpacing +
offset.x,
-this.layerSpacing / 2,
);
}
var ph = group.findObject("PLACEHOLDER"); // won't be a go.Placeholder, but just a regular Shape
if (ph === null) ph = group;
if (horiz) {
ph.desiredSize = new go.Size(
totallength,
this.laneBreadths.get(lane) * this.columnSpacing,
);
} else {
ph.desiredSize = new go.Size(
this.laneBreadths.get(lane) * this.columnSpacing,
totallength,
);
}
}
},
}),
});
myDiagram.nodeTemplate = $(
go.Node,
"Spot",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", {
fill: "white",
width: 100,
height: 50,
portId: "",
fromLinkable: true,
toLinkable: true,
cursor: "pointer",
}),
$(
go.TextBlock, // the text label
new go.Binding("text", "key"),
{
verticalAlignment: go.Spot.Center,
textAlign: "center",
},
),
);
myDiagram.linkTemplate = $(
go.Link,
{
curve: go.Link.Bezier,
toShortLength: 8,
fromEndSegmentLength: 50,
toEndSegmentLength: 50,
},
$(go.Shape, { isPanelMain: true, strokeWidth: 2 }),
$(go.Shape, { toArrow: "Standard", stroke: null }),
);
myDiagram.groupTemplate = $(
go.Group,
"Horizontal",
{
layerName: "Background",
movable: false,
copyable: false,
locationObjectName: "PLACEHOLDER",
layout: null,
avoidable: false,
},
$(
go.TextBlock,
{
font: "bold 12pt sans-serif",
angle: 270,
},
new go.Binding("text", "key"),
),
$(
go.Panel,
"Auto",
$(go.Shape, { fill: "transparent", stroke: "orange" }),
$(go.Shape, {
name: "PLACEHOLDER",
fill: null,
stroke: null,
strokeWidth: 0,
}),
),
);
const nodeDataArray = [
{
key: "A",
isGroup: true,
},
{
key: "Box1",
group: "A",
},
{
key: "Box2",
group: "A",
},
{
key: "Box3",
group: "A",
},
{
key: "Box4",
group: "A",
},
{
key: "Box5",
group: "A",
},
];
const linkDataArray = [
{
from: "Box1",
to: "Box2",
},
{
from: "Box1",
to: "Box3",
},
{
from: "Box1",
to: "Box4",
},
{
from: "Box2",
to: "Box5",
},
];
const model = new go.GraphLinksModel();
model.nodeDataArray = nodeDataArray;
model.linkDataArray = linkDataArray;
myDiagram.model = model;
myDiagram.addDiagramListener("SelectionMoved", (event) => {
const node = event.subject.first();
console.log(node.data);
});
}
window.addEventListener("DOMContentLoaded", init);
document.getElementById("reorder").addEventListener("click", (e) => {
myDiagram.startTransaction("reorder node");
myDiagram.model.nodeDataArray = [
{
key: "Box1",
group: "A",
},
{
key: "Box3",
group: "A",
},
{
key: "Box4",
group: "A",
},
{
key: "Box2",
group: "A",
},
{
key: "Box5",
group: "A",
},
];
myDiagram.commitTransaction("reorder node");
});
document.getElementById("reorder2").addEventListener("click", (e) => {
myDiagram.startTransaction("reorder node 2");
let removed;
const nodeId = myDiagram.nodes;
while (nodeId.next()) {
const node = nodeId.value;
if (node.data.key === "Box2") {
removed = node.data;
break;
}
}
if (removed) {
myDiagram.model.removeNodeData(removed);
myDiagram.model.addNodeData({
key: "Box2",
group: "A",
});
}
myDiagram.commitTransaction("reorder node 2");
});
document.getElementById("reorder3").addEventListener("click", (e) => {
myDiagram.startTransaction("reorder node 3");
myDiagram.model.nodeDataArray = [
{
key: "Box1",
group: "A",
},
{
key: "Box3",
group: "A",
},
{
key: "Box2",
group: "A",
},
{
key: "Box4",
group: "A",
},
{
key: "Box5",
group: "A",
},
];
myDiagram.commitTransaction("reorder node 3");
});
</script>
</body>
</html>