Skip to content

Commit 4ccc5d5

Browse files
authored
Merge pull request #980 from projectstorm/features
Some Features and fixes
2 parents ddeea12 + fb7d646 commit 4ccc5d5

File tree

5 files changed

+217
-13
lines changed

5 files changed

+217
-13
lines changed

.changeset/nice-zoos-laugh.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@projectstorm/react-diagrams-defaults': minor
3+
'@projectstorm/react-diagrams-routing': minor
4+
'@projectstorm/react-diagrams-gallery': minor
5+
---
6+
7+
- [feature] new ability to refresh links in auto distribute system [PR 756](https://github.com/projectstorm/react-diagrams/pull/756)
8+
- [fix] Default link now uses the correct method for creating a point allowing this to be overridden [PR 939](https://github.com/projectstorm/react-diagrams/pull/939)
9+
10+
Big thanks to @ToTheHit and @h0111in for your help on these, even though its very delayed on my part :)

.prettierrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"semi": true,
33
"singleQuote": true,
4-
"jsxBracketSameLine": true,
54
"useTabs": true,
65
"printWidth": 120,
76
"trailingComma": "none"

diagrams-demo-gallery/demos/demo-dagre/index.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class DemoWidget extends React.Component<{ model: DiagramModel; engine: DiagramE
4545
marginx: 25,
4646
marginy: 25
4747
},
48-
includeLinks: true
48+
includeLinks: true,
49+
nodeMargin: 25
4950
});
5051
}
5152

@@ -57,6 +58,13 @@ class DemoWidget extends React.Component<{ model: DiagramModel; engine: DiagramE
5758
this.props.engine.repaintCanvas();
5859
};
5960

61+
autoRefreshLinks = () => {
62+
this.engine.refreshLinks(this.props.model);
63+
64+
// only happens if pathfing is enabled (check line 25)
65+
this.reroute();
66+
this.props.engine.repaintCanvas();
67+
};
6068
componentDidMount(): void {
6169
setTimeout(() => {
6270
this.autoDistribute();
@@ -72,7 +80,14 @@ class DemoWidget extends React.Component<{ model: DiagramModel; engine: DiagramE
7280

7381
render() {
7482
return (
75-
<DemoWorkspaceWidget buttons={<DemoButton onClick={this.autoDistribute}>Re-distribute</DemoButton>}>
83+
<DemoWorkspaceWidget
84+
buttons={
85+
<div>
86+
<DemoButton onClick={this.autoDistribute}>Re-distribute</DemoButton>
87+
<DemoButton onClick={this.autoRefreshLinks}>Refresh Links</DemoButton>
88+
</div>
89+
}
90+
>
7691
<DemoCanvasWidget>
7792
<CanvasWidget engine={this.props.engine} />
7893
</DemoCanvasWidget>
@@ -106,9 +121,11 @@ export default () => {
106121
});
107122

108123
// more links for more complicated diagram
109-
links.push(connectNodes(nodesFrom[0], nodesTo[1], engine));
110-
links.push(connectNodes(nodesTo[0], nodesFrom[1], engine));
111-
links.push(connectNodes(nodesFrom[1], nodesTo[2], engine));
124+
links.push(connectNodes(nodesTo[0], nodesTo[1], engine));
125+
links.push(connectNodes(nodesTo[1], nodesTo[2], engine));
126+
links.push(connectNodes(nodesTo[0], nodesTo[2], engine));
127+
links.push(connectNodes(nodesFrom[0], nodesFrom[2], engine));
128+
links.push(connectNodes(nodesFrom[0], nodesTo[2], engine));
112129

113130
// initial random position
114131
nodesFrom.forEach((node, index) => {

packages/react-diagrams-defaults/src/link/DefaultLinkWidget.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,8 @@ export class DefaultLinkWidget extends React.Component<DefaultLinkProps, Default
5858
!this.props.link.isLocked() &&
5959
this.props.link.getPoints().length - 1 <= this.props.diagramEngine.getMaxNumberPointsPerLink()
6060
) {
61-
const point = new PointModel({
62-
link: this.props.link,
63-
position: this.props.diagramEngine.getRelativeMousePoint(event)
64-
});
65-
this.props.link.addPoint(point, index);
61+
const position = this.props.diagramEngine.getRelativeMousePoint(event);
62+
const point = this.props.link.point(position.x, position.y, index);
6663
event.persist();
6764
event.stopPropagation();
6865
this.forceUpdate(() => {

packages/react-diagrams-routing/src/dagre/DagreEngine.ts

Lines changed: 183 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { DiagramModel, PointModel } from '@projectstorm/react-diagrams-core';
22
import * as dagre from 'dagre';
3-
import * as _ from 'lodash';
43
import { GraphLabel } from 'dagre';
4+
import * as _ from 'lodash';
55
import { Point } from '@projectstorm/geometry';
66

77
export interface DagreEngineOptions {
88
graph?: GraphLabel;
99
/**
10-
* Will also layout links
10+
* Will also re-layout links
1111
*/
1212
includeLinks?: boolean;
13+
nodeMargin?: number;
1314
}
1415

1516
export class DagreEngine {
@@ -68,4 +69,184 @@ export class DagreEngine {
6869
});
6970
}
7071
}
72+
73+
/**
74+
* TODO cleanup this method into smaller methods
75+
*/
76+
public refreshLinks(diagram: DiagramModel) {
77+
const { nodeMargin } = this.options;
78+
const nodes = diagram.getNodes();
79+
const links = diagram.getLinks();
80+
let maxChunkRowIndex = -1;
81+
// build the chunk matrix
82+
const chunks: { [id: number]: { [id: number]: boolean } } = {}; // true: occupied, false: blank
83+
const NodeXColumnIndexDictionary: { [id: number]: number } = {};
84+
let verticalLines: number[] = [];
85+
_.forEach(nodes, (node) => {
86+
// find vertical lines. vertical lines go through maximum number of nodes located under each other.
87+
const nodeColumnCenter = node.getX() + node.width / 2;
88+
if (
89+
_.every(verticalLines, (vLine) => {
90+
return Math.abs(nodeColumnCenter - vLine) > nodeMargin;
91+
})
92+
) {
93+
verticalLines.push(nodeColumnCenter);
94+
}
95+
});
96+
97+
// sort chunk columns
98+
verticalLines = verticalLines.sort((a, b) => a - b);
99+
_.forEach(verticalLines, (line, index) => {
100+
chunks[index] = {};
101+
chunks[index + 0.5] = {};
102+
});
103+
104+
// set occupied chunks
105+
_.forEach(nodes, (node) => {
106+
const nodeColumnCenter = node.getX() + node.width / 2;
107+
const startChunkIndex = Math.floor(node.getY() / nodeMargin);
108+
const endChunkIndex = Math.floor((node.getY() + node.height) / nodeMargin);
109+
// find max ChunkRowIndex
110+
if (endChunkIndex > maxChunkRowIndex) maxChunkRowIndex = endChunkIndex;
111+
const nodeColumnIndex = _.findIndex(verticalLines, (vLine) => {
112+
return Math.abs(nodeColumnCenter - vLine) <= nodeMargin;
113+
});
114+
_.forEach(_.range(startChunkIndex, endChunkIndex + 1), (chunkIndex) => {
115+
chunks[nodeColumnIndex][chunkIndex] = true;
116+
});
117+
NodeXColumnIndexDictionary[node.getX()] = nodeColumnIndex;
118+
});
119+
120+
// sort links based on their distances
121+
const edges = _.map(links, (link) => {
122+
if (link.getSourcePort() && link.getTargetPort()) {
123+
const source = link.getSourcePort().getNode();
124+
const target = link.getTargetPort().getNode();
125+
const sourceIndex = NodeXColumnIndexDictionary[source.getX()];
126+
const targetIndex = NodeXColumnIndexDictionary[target.getX()];
127+
128+
return sourceIndex > targetIndex
129+
? {
130+
link,
131+
sourceIndex,
132+
sourceY: source.getY() + source.height / 2,
133+
source,
134+
targetIndex,
135+
targetY: target.getY() + source.height / 2,
136+
target
137+
}
138+
: {
139+
link,
140+
sourceIndex: targetIndex,
141+
sourceY: target.getY() + target.height / 2,
142+
source: target,
143+
targetIndex: sourceIndex,
144+
targetY: source.getY() + source.height / 2,
145+
target: source
146+
};
147+
}
148+
});
149+
const sortedEdges = _.sortBy(edges, (link) => {
150+
return Math.abs(link.targetIndex - link.sourceIndex);
151+
});
152+
153+
// set link points
154+
if (this.options.includeLinks) {
155+
_.forEach(sortedEdges, (edge) => {
156+
const link = diagram.getLink(edge.link.getID());
157+
// re-draw
158+
if (Math.abs(edge.sourceIndex - edge.targetIndex) > 1) {
159+
// get the length of link in column
160+
const columns = _.range(edge.sourceIndex - 1, edge.targetIndex);
161+
162+
const chunkIndex = Math.floor(edge.sourceY / nodeMargin);
163+
const targetChunkIndex = Math.floor(edge.targetY / nodeMargin);
164+
165+
// check upper paths
166+
let northCost = 1;
167+
let aboveRowIndex = chunkIndex;
168+
for (; aboveRowIndex >= 0; aboveRowIndex--, northCost++) {
169+
if (
170+
_.every(columns, (columnIndex) => {
171+
return !(
172+
chunks[columnIndex][aboveRowIndex] ||
173+
chunks[columnIndex + 0.5][aboveRowIndex] ||
174+
chunks[columnIndex - 0.5][aboveRowIndex]
175+
);
176+
})
177+
) {
178+
break;
179+
}
180+
}
181+
182+
// check lower paths
183+
let southCost = 0;
184+
let belowRowIndex = chunkIndex;
185+
for (; belowRowIndex <= maxChunkRowIndex; belowRowIndex++, southCost++) {
186+
if (
187+
_.every(columns, (columnIndex) => {
188+
return !(
189+
chunks[columnIndex][belowRowIndex] ||
190+
chunks[columnIndex + 0.5][belowRowIndex] ||
191+
chunks[columnIndex - 0.5][belowRowIndex]
192+
);
193+
})
194+
) {
195+
break;
196+
}
197+
}
198+
// pick the cheapest path
199+
const pathRowIndex =
200+
southCost + (belowRowIndex - targetChunkIndex) < northCost + (targetChunkIndex - aboveRowIndex)
201+
? belowRowIndex + 1
202+
: aboveRowIndex - 1;
203+
204+
// Finally update the link points
205+
const points = [link.getFirstPoint()];
206+
points.push(
207+
new PointModel({
208+
link: link,
209+
position: new Point(
210+
(verticalLines[columns[0]] + verticalLines[columns[0] + 1]) / 2,
211+
(pathRowIndex + 0.5) * nodeMargin
212+
)
213+
})
214+
);
215+
216+
_.forEach(columns, (column) => {
217+
points.push(
218+
new PointModel({
219+
link: link,
220+
position: new Point(verticalLines[column], (pathRowIndex + 0.5) * nodeMargin)
221+
})
222+
);
223+
points.push(
224+
new PointModel({
225+
link: link,
226+
position: new Point(
227+
(verticalLines[column] + verticalLines[column - 1]) / 2,
228+
(pathRowIndex + 0.5) * nodeMargin
229+
)
230+
})
231+
);
232+
chunks[column][pathRowIndex] = true;
233+
chunks[column][pathRowIndex + 1] = true;
234+
chunks[column + 0.5][pathRowIndex] = true;
235+
chunks[column + 0.5][pathRowIndex + 1] = true;
236+
});
237+
link.setPoints(points.concat(link.getLastPoint()));
238+
} else {
239+
// refresh
240+
link.setPoints([link.getFirstPoint(), link.getLastPoint()]);
241+
const columnIndex = (edge.sourceIndex + edge.targetIndex) / 2;
242+
if (!chunks[columnIndex]) {
243+
chunks[columnIndex] = {};
244+
}
245+
const rowIndex = Math.floor((edge.sourceY + edge.targetY) / 2 / nodeMargin);
246+
chunks[columnIndex][rowIndex] = true;
247+
chunks[columnIndex][rowIndex + 1] = true;
248+
}
249+
});
250+
}
251+
}
71252
}

0 commit comments

Comments
 (0)