@dxos/diagram 0.11.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/chunk-parse.mjs +9406 -0
- package/dist/lib/chunk-parse.mjs.map +1 -0
- package/dist/lib/extension.mjs +204 -0
- package/dist/lib/extension.mjs.map +1 -0
- package/dist/lib/index.mjs +497 -2846
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/aesthetics.d.ts +12 -0
- package/dist/types/src/aesthetics.d.ts.map +1 -0
- package/dist/types/src/aesthetics.test.d.ts +2 -0
- package/dist/types/src/aesthetics.test.d.ts.map +1 -0
- package/dist/types/src/appeal.d.ts +55 -0
- package/dist/types/src/appeal.d.ts.map +1 -0
- package/dist/types/src/appeal.test.d.ts +2 -0
- package/dist/types/src/appeal.test.d.ts.map +1 -0
- package/dist/types/src/architecture.d.ts +86 -0
- package/dist/types/src/architecture.d.ts.map +1 -0
- package/dist/types/src/architecture.test.d.ts +2 -0
- package/dist/types/src/architecture.test.d.ts.map +1 -0
- package/dist/types/src/compact.d.ts +39 -0
- package/dist/types/src/compact.d.ts.map +1 -0
- package/dist/types/src/compact.test.d.ts +2 -0
- package/dist/types/src/compact.test.d.ts.map +1 -0
- package/dist/types/src/diagnostics.d.ts +70 -3
- package/dist/types/src/diagnostics.d.ts.map +1 -1
- package/dist/types/src/dsl/cli.d.ts +2 -0
- package/dist/types/src/dsl/cli.d.ts.map +1 -0
- package/dist/types/src/dsl/compile.d.ts +12 -0
- package/dist/types/src/dsl/compile.d.ts.map +1 -0
- package/dist/types/src/dsl/convert.d.ts +45 -0
- package/dist/types/src/dsl/convert.d.ts.map +1 -0
- package/dist/types/src/dsl/convert.test.d.ts +2 -0
- package/dist/types/src/dsl/convert.test.d.ts.map +1 -0
- package/dist/types/src/dsl/dsl.test.d.ts +2 -0
- package/dist/types/src/dsl/dsl.test.d.ts.map +1 -0
- package/dist/types/src/dsl/gen/diagram.d.ts +3 -0
- package/dist/types/src/dsl/gen/diagram.d.ts.map +1 -0
- package/dist/types/src/dsl/index.d.ts +7 -0
- package/dist/types/src/dsl/index.d.ts.map +1 -0
- package/dist/types/src/dsl/parse.d.ts +49 -0
- package/dist/types/src/dsl/parse.d.ts.map +1 -0
- package/dist/types/src/dsl/print.d.ts +19 -0
- package/dist/types/src/dsl/print.d.ts.map +1 -0
- package/dist/types/src/dsl/semantic.test.d.ts +2 -0
- package/dist/types/src/dsl/semantic.test.d.ts.map +1 -0
- package/dist/types/src/dsl/vocabulary.d.ts +61 -0
- package/dist/types/src/dsl/vocabulary.d.ts.map +1 -0
- package/dist/types/src/dx-svg.d.ts +12 -0
- package/dist/types/src/dx-svg.d.ts.map +1 -0
- package/dist/types/src/dx-svg.test.d.ts +2 -0
- package/dist/types/src/dx-svg.test.d.ts.map +1 -0
- package/dist/types/src/extension/complete.d.ts +3 -0
- package/dist/types/src/extension/complete.d.ts.map +1 -0
- package/dist/types/src/extension/extension.test.d.ts +2 -0
- package/dist/types/src/extension/extension.test.d.ts.map +1 -0
- package/dist/types/src/extension/highlight.d.ts +14 -0
- package/dist/types/src/extension/highlight.d.ts.map +1 -0
- package/dist/types/src/extension/index.d.ts +6 -0
- package/dist/types/src/extension/index.d.ts.map +1 -0
- package/dist/types/src/extension/language.d.ts +13 -0
- package/dist/types/src/extension/language.d.ts.map +1 -0
- package/dist/types/src/extension/lint.d.ts +21 -0
- package/dist/types/src/extension/lint.d.ts.map +1 -0
- package/dist/types/src/extension/syntax.d.ts +3 -0
- package/dist/types/src/extension/syntax.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +14 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/mermaid-engine.d.ts +40 -1
- package/dist/types/src/mermaid-engine.d.ts.map +1 -1
- package/dist/types/src/mermaid.d.ts +3 -3
- package/dist/types/src/mermaid.d.ts.map +1 -1
- package/dist/types/src/nudge.d.ts +30 -0
- package/dist/types/src/nudge.d.ts.map +1 -0
- package/dist/types/src/nudge.test.d.ts +2 -0
- package/dist/types/src/nudge.test.d.ts.map +1 -0
- package/dist/types/src/objective.d.ts +4 -0
- package/dist/types/src/objective.d.ts.map +1 -1
- package/dist/types/src/ortho-router.d.ts +13 -1
- package/dist/types/src/ortho-router.d.ts.map +1 -1
- package/dist/types/src/ports.d.ts +40 -0
- package/dist/types/src/ports.d.ts.map +1 -0
- package/dist/types/src/ports.test.d.ts +2 -0
- package/dist/types/src/ports.test.d.ts.map +1 -0
- package/dist/types/src/relations.test.d.ts +2 -0
- package/dist/types/src/relations.test.d.ts.map +1 -0
- package/dist/types/src/rules.d.ts +30 -0
- package/dist/types/src/rules.d.ts.map +1 -0
- package/dist/types/src/scene.d.ts +108 -74
- package/dist/types/src/scene.d.ts.map +1 -1
- package/dist/types/src/score.d.ts +54 -0
- package/dist/types/src/score.d.ts.map +1 -0
- package/dist/types/src/score.test.d.ts +2 -0
- package/dist/types/src/score.test.d.ts.map +1 -0
- package/dist/types/src/semantic-engine.d.ts +41 -0
- package/dist/types/src/semantic-engine.d.ts.map +1 -0
- package/dist/types/src/semantic-engine.test.d.ts +2 -0
- package/dist/types/src/semantic-engine.test.d.ts.map +1 -0
- package/dist/types/src/semantic-place.d.ts +81 -0
- package/dist/types/src/semantic-place.d.ts.map +1 -0
- package/dist/types/src/semantic-route.d.ts +133 -0
- package/dist/types/src/semantic-route.d.ts.map +1 -0
- package/dist/types/src/semantic.d.ts +164 -0
- package/dist/types/src/semantic.d.ts.map +1 -0
- package/dist/types/src/uml-grid.d.ts.map +1 -1
- package/dist/types/src/uml.d.ts +3 -5
- package/dist/types/src/uml.d.ts.map +1 -1
- package/dist/types/src/view.d.ts +53 -0
- package/dist/types/src/view.d.ts.map +1 -0
- package/dist/types/src/view.test.d.ts +2 -0
- package/dist/types/src/view.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -5
- package/rules/DIAGRAM.mdl +388 -0
- package/src/aesthetics.test.ts +89 -0
- package/src/aesthetics.ts +148 -0
- package/src/appeal.test.ts +144 -0
- package/src/appeal.ts +586 -0
- package/src/architecture.test.ts +94 -0
- package/src/architecture.ts +215 -0
- package/src/compact.test.ts +67 -0
- package/src/compact.ts +219 -0
- package/src/diagnostics.test.ts +207 -1
- package/src/diagnostics.ts +481 -37
- package/src/dsl/cli.ts +46 -0
- package/src/dsl/compile.ts +28 -0
- package/src/dsl/convert.test.ts +51 -0
- package/src/dsl/convert.ts +98 -0
- package/src/dsl/diagram.grammar +195 -0
- package/src/dsl/dsl.test.ts +286 -0
- package/src/dsl/gen/diagram.ts +22 -0
- package/src/dsl/index.ts +44 -0
- package/src/dsl/parse.ts +1054 -0
- package/src/dsl/print.ts +198 -0
- package/src/dsl/semantic.test.ts +216 -0
- package/src/dsl/vocabulary.ts +150 -0
- package/src/dx-svg.test.ts +44 -0
- package/src/dx-svg.ts +77 -0
- package/src/extension/complete.ts +105 -0
- package/src/extension/extension.test.ts +135 -0
- package/src/extension/highlight.ts +23 -0
- package/src/extension/index.ts +9 -0
- package/src/extension/language.ts +31 -0
- package/src/extension/lint.ts +60 -0
- package/src/extension/syntax.ts +62 -0
- package/src/index.ts +14 -0
- package/src/mermaid-engine.test.ts +6 -6
- package/src/mermaid-engine.ts +569 -119
- package/src/mermaid.test.ts +17 -1
- package/src/mermaid.ts +30 -18
- package/src/nudge.test.ts +186 -0
- package/src/nudge.ts +387 -0
- package/src/objective.test.ts +2 -0
- package/src/objective.ts +17 -1
- package/src/ortho-router.ts +240 -80
- package/src/ports.test.ts +157 -0
- package/src/ports.ts +186 -0
- package/src/relations.test.ts +98 -0
- package/src/rules.ts +117 -0
- package/src/scene.ts +66 -5
- package/src/score.test.ts +64 -0
- package/src/score.ts +130 -0
- package/src/semantic-engine.test.ts +635 -0
- package/src/semantic-engine.ts +891 -0
- package/src/semantic-place.ts +1508 -0
- package/src/semantic-route.ts +1136 -0
- package/src/semantic.ts +278 -0
- package/src/typings.d.ts +8 -0
- package/src/uml-grid.ts +6 -1
- package/src/uml.test.ts +5 -1
- package/src/uml.ts +24 -11
- package/src/view.test.ts +139 -0
- package/src/view.ts +376 -0
package/src/mermaid.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import { compile, parse } from './mermaid.ts';
|
|
7
|
+
import { compile, markers, parse } from './mermaid.ts';
|
|
8
8
|
import type * as Scene from './scene.ts';
|
|
9
9
|
|
|
10
10
|
/** Exercises node declarations, a labelled-blank subgraph, fan-out, and a `C <-> Y` cycle. */
|
|
@@ -55,6 +55,22 @@ describe('mermaid', () => {
|
|
|
55
55
|
]);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
test('reads the relation tokens as kinds with their UML markers', ({ expect }) => {
|
|
59
|
+
const graph = parse(
|
|
60
|
+
['flowchart TB', ' B ..|> A', ' C -.-> D', ' E o--> F', ' G --{ H', ' I --|> J', ' K --> L'].join('\n'),
|
|
61
|
+
);
|
|
62
|
+
expect(graph.edges.map(({ kind }) => kind)).toEqual([
|
|
63
|
+
'implements',
|
|
64
|
+
'creates',
|
|
65
|
+
'contains',
|
|
66
|
+
'hasMany',
|
|
67
|
+
'inheritance',
|
|
68
|
+
'reference',
|
|
69
|
+
]);
|
|
70
|
+
expect(markers('implements')).toEqual({ relation: 'implementation', stroke: 'dashed' });
|
|
71
|
+
expect(markers('creates')).toEqual({ relation: 'dependency', stroke: 'dashed' });
|
|
72
|
+
});
|
|
73
|
+
|
|
58
74
|
test('compiles to one object per node plus a frame and an edge object', ({ expect }) => {
|
|
59
75
|
const objects = objectsOf(compile(FLOWCHART));
|
|
60
76
|
|
package/src/mermaid.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type MermaidGroup = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
/** Relationship kind, from the edge token; drawn with the UML end markers in `markers`. */
|
|
32
|
-
export type RelationKind = 'reference' | 'inheritance' | 'hasMany' | 'contains';
|
|
32
|
+
export type RelationKind = 'reference' | 'inheritance' | 'implements' | 'hasMany' | 'contains' | 'creates';
|
|
33
33
|
|
|
34
34
|
export type MermaidEdge = {
|
|
35
35
|
from: string;
|
|
@@ -39,23 +39,30 @@ export type MermaidEdge = {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Edge tokens: mermaid's own arrows read as references
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* Edge tokens: mermaid's own arrows read as references, except the dotted `-.->`, which reads as
|
|
43
|
+
* creation (UML's dashed «create» dependency). The classDiagram-style `--|>` (hollow triangle) and
|
|
44
|
+
* `..|>` (dashed hollow triangle) and ER-style `--{` (crow's foot) and `o-->` (circle at the owner)
|
|
45
|
+
* extend the flowchart grammar with the UML kinds, at the cost of mermaid.js rejecting those lines.
|
|
45
46
|
*/
|
|
46
47
|
const EDGE_KINDS: Record<string, RelationKind> = {
|
|
47
48
|
'-->': 'reference',
|
|
48
49
|
'---': 'reference',
|
|
49
|
-
'-.->': 'reference',
|
|
50
50
|
'==>': 'reference',
|
|
51
|
+
'-.->': 'creates',
|
|
51
52
|
'--|>': 'inheritance',
|
|
53
|
+
'..|>': 'implements',
|
|
52
54
|
'--{': 'hasMany',
|
|
53
55
|
'o-->': 'contains',
|
|
54
56
|
};
|
|
55
57
|
|
|
56
58
|
/** Reference-render labels for the UML kinds, used when the edge carries none of its own. */
|
|
57
|
-
const UML_LABELS: Record<string, string> = {
|
|
58
|
-
|
|
59
|
+
const UML_LABELS: Record<string, string> = {
|
|
60
|
+
'--|>': 'extends',
|
|
61
|
+
'..|>': 'implements',
|
|
62
|
+
'--{': 'has many',
|
|
63
|
+
'o-->': 'contains',
|
|
64
|
+
};
|
|
65
|
+
const UML_EDGE = /^(\s*\S+)\s*(o-->|--\|>|\.\.\|>|--\{)\s*(?:\|(.*?)\|\s*)?(\S+)\s*$/;
|
|
59
66
|
|
|
60
67
|
/**
|
|
61
68
|
* Rewrite the UML edge tokens into mermaid-legal labelled arrows, for rendering the source with
|
|
@@ -72,15 +79,19 @@ export const toStandard = (source: string): string =>
|
|
|
72
79
|
})
|
|
73
80
|
.join('\n');
|
|
74
81
|
|
|
75
|
-
/**
|
|
76
|
-
export const markers = (kind: RelationKind): Pick<Scene.Arrow, '
|
|
82
|
+
/** The scene relation a relationship kind draws as, with the dash its line needs; a plain reference stays a plain arrow. */
|
|
83
|
+
export const markers = (kind: RelationKind): Pick<Scene.Arrow, 'relation' | 'stroke'> => {
|
|
77
84
|
switch (kind) {
|
|
78
85
|
case 'inheritance':
|
|
79
|
-
return {
|
|
86
|
+
return { relation: 'inheritance' };
|
|
87
|
+
case 'implements':
|
|
88
|
+
return { relation: 'implementation', stroke: 'dashed' };
|
|
80
89
|
case 'hasMany':
|
|
81
|
-
return {
|
|
90
|
+
return { relation: 'one-to-many' };
|
|
82
91
|
case 'contains':
|
|
83
|
-
return {
|
|
92
|
+
return { relation: 'aggregation' };
|
|
93
|
+
case 'creates':
|
|
94
|
+
return { relation: 'dependency', stroke: 'dashed' };
|
|
84
95
|
default:
|
|
85
96
|
return {};
|
|
86
97
|
}
|
|
@@ -97,8 +108,9 @@ const DIRECTIONS: Direction[] = ['TB', 'BT', 'LR', 'RL'];
|
|
|
97
108
|
|
|
98
109
|
// `A[Label]`, `A(Label)`, `A{Label}` or a bare `A`.
|
|
99
110
|
const NODE = /^([A-Za-z0-9_-]+)(?:\[(.*?)\]|\((.*?)\)|\{(.*?)\})?$/;
|
|
100
|
-
// `A --> B`, `A-->|label|B`, `A --- B`, plus the UML kinds `B --|> A`, `X --{ Y
|
|
101
|
-
|
|
111
|
+
// `A --> B`, `A-->|label|B`, `A --- B`, `A -.-> B`, plus the UML kinds `B --|> A`, `B ..|> A`, `X --{ Y`
|
|
112
|
+
// and `A o--> B`.
|
|
113
|
+
const EDGE = /^(.+?)\s*(o-->|--\|>|\.\.\|>|--\{|-->|---|-\.->|==>)\s*(?:\|(.*?)\|\s*)?(.+)$/;
|
|
102
114
|
const SUBGRAPH = /^subgraph\s+([A-Za-z0-9_-]+)(?:\s*\[(.*?)\])?\s*$/;
|
|
103
115
|
|
|
104
116
|
// `%% ref A packages/core/echo` — a comment to mermaid proper, so sources stay portable.
|
|
@@ -114,7 +126,7 @@ export const parse = (source: string): MermaidGraph => {
|
|
|
114
126
|
let direction: Direction = 'TB';
|
|
115
127
|
const stack: string[] = [];
|
|
116
128
|
|
|
117
|
-
const
|
|
129
|
+
const declareNode = (token: string): string | undefined => {
|
|
118
130
|
const match = NODE.exec(token.trim());
|
|
119
131
|
if (!match) {
|
|
120
132
|
return undefined;
|
|
@@ -172,8 +184,8 @@ export const parse = (source: string): MermaidGraph => {
|
|
|
172
184
|
const edge = EDGE.exec(line);
|
|
173
185
|
if (edge) {
|
|
174
186
|
const [, from, token, label, to] = edge;
|
|
175
|
-
const fromId =
|
|
176
|
-
const toId =
|
|
187
|
+
const fromId = declareNode(from);
|
|
188
|
+
const toId = declareNode(to);
|
|
177
189
|
if (fromId && toId) {
|
|
178
190
|
edges.push({
|
|
179
191
|
from: fromId,
|
|
@@ -185,7 +197,7 @@ export const parse = (source: string): MermaidGraph => {
|
|
|
185
197
|
continue;
|
|
186
198
|
}
|
|
187
199
|
|
|
188
|
-
|
|
200
|
+
declareNode(line);
|
|
189
201
|
}
|
|
190
202
|
|
|
191
203
|
// Directives may precede or follow the node they name.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { nudge } from './nudge.ts';
|
|
8
|
+
import type * as Scene from './scene.ts';
|
|
9
|
+
import { type Rect } from './uml-grid.ts';
|
|
10
|
+
|
|
11
|
+
const orthogonal = (points: readonly Scene.Point[]) =>
|
|
12
|
+
points.slice(1).every((point, index) => point.x === points[index].x || point.y === points[index].y);
|
|
13
|
+
|
|
14
|
+
/** Whether two orthogonal polylines share a collinear stretch of positive length. */
|
|
15
|
+
const overlapping = (first: readonly Scene.Point[], second: readonly Scene.Point[]) => {
|
|
16
|
+
const segments = (points: readonly Scene.Point[]) =>
|
|
17
|
+
points.slice(1).map((end, index) => [points[index], end] as const);
|
|
18
|
+
return segments(first).some(([a0, a1]) =>
|
|
19
|
+
segments(second).some(([b0, b1]) => {
|
|
20
|
+
if (a0.x === a1.x && b0.x === b1.x && a0.x === b0.x) {
|
|
21
|
+
const lo = Math.max(Math.min(a0.y, a1.y), Math.min(b0.y, b1.y));
|
|
22
|
+
const hi = Math.min(Math.max(a0.y, a1.y), Math.max(b0.y, b1.y));
|
|
23
|
+
return hi - lo > 0;
|
|
24
|
+
}
|
|
25
|
+
if (a0.y === a1.y && b0.y === b1.y && a0.y === b0.y) {
|
|
26
|
+
const lo = Math.max(Math.min(a0.x, a1.x), Math.min(b0.x, b1.x));
|
|
27
|
+
const hi = Math.min(Math.max(a0.x, a1.x), Math.max(b0.x, b1.x));
|
|
28
|
+
return hi - lo > 0;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const onBorder = (point: Scene.Point, rect: Rect) =>
|
|
36
|
+
((point.y === rect.y || point.y === rect.y + rect.h) && point.x >= rect.x && point.x <= rect.x + rect.w) ||
|
|
37
|
+
((point.x === rect.x || point.x === rect.x + rect.w) && point.y >= rect.y && point.y <= rect.y + rect.h);
|
|
38
|
+
|
|
39
|
+
describe('nudge', () => {
|
|
40
|
+
const source: Rect = { x: 0, y: 0, w: 128, h: 64 };
|
|
41
|
+
const left: Rect = { x: 0, y: 200, w: 128, h: 64 };
|
|
42
|
+
const right: Rect = { x: 200, y: 200, w: 128, h: 64 };
|
|
43
|
+
// A fork: both leave the same port and run down x = 64 together before one turns right.
|
|
44
|
+
const straight = {
|
|
45
|
+
points: [
|
|
46
|
+
{ x: 64, y: 64 },
|
|
47
|
+
{ x: 64, y: 200 },
|
|
48
|
+
],
|
|
49
|
+
source,
|
|
50
|
+
target: left,
|
|
51
|
+
};
|
|
52
|
+
const turning = {
|
|
53
|
+
points: [
|
|
54
|
+
{ x: 64, y: 64 },
|
|
55
|
+
{ x: 64, y: 132 },
|
|
56
|
+
{ x: 264, y: 132 },
|
|
57
|
+
{ x: 264, y: 200 },
|
|
58
|
+
],
|
|
59
|
+
source,
|
|
60
|
+
target: right,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
test('two edges sharing a vertical run are separated and stay orthogonal', ({ expect }) => {
|
|
64
|
+
const [first, second] = nudge([straight, turning], { obstacles: [source, left, right] });
|
|
65
|
+
expect(orthogonal(first)).toBe(true);
|
|
66
|
+
expect(orthogonal(second)).toBe(true);
|
|
67
|
+
// The edge that turns right takes the right-hand lane, so its turn-off does not cut the other.
|
|
68
|
+
expect(second[0].x - first[0].x).toBeGreaterThanOrEqual(8);
|
|
69
|
+
expect(second[1].x).toBe(second[0].x);
|
|
70
|
+
expect(onBorder(first[0], source) && onBorder(second[0], source)).toBe(true);
|
|
71
|
+
expect(first[first.length - 1]).toEqual({ x: first[0].x, y: 200 });
|
|
72
|
+
expect(second[second.length - 1]).toEqual({ x: 264, y: 200 });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('a fork sharing a vertical run and then a horizontal run is fully separated', ({ expect }) => {
|
|
76
|
+
const far: Rect = { x: 400, y: 200, w: 128, h: 64 };
|
|
77
|
+
const near = {
|
|
78
|
+
points: [
|
|
79
|
+
{ x: 64, y: 64 },
|
|
80
|
+
{ x: 64, y: 132 },
|
|
81
|
+
{ x: 264, y: 132 },
|
|
82
|
+
{ x: 264, y: 200 },
|
|
83
|
+
],
|
|
84
|
+
source,
|
|
85
|
+
target: right,
|
|
86
|
+
};
|
|
87
|
+
const away = {
|
|
88
|
+
points: [
|
|
89
|
+
{ x: 64, y: 64 },
|
|
90
|
+
{ x: 64, y: 132 },
|
|
91
|
+
{ x: 464, y: 132 },
|
|
92
|
+
{ x: 464, y: 200 },
|
|
93
|
+
],
|
|
94
|
+
source,
|
|
95
|
+
target: far,
|
|
96
|
+
};
|
|
97
|
+
const [first, second] = nudge([near, away], { obstacles: [source, right, far] });
|
|
98
|
+
expect(orthogonal(first) && orthogonal(second)).toBe(true);
|
|
99
|
+
expect(overlapping(first, second)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('a fixed bus never moves; the edge lying on it is pushed off', ({ expect }) => {
|
|
103
|
+
const bus = [
|
|
104
|
+
{ x: 100, y: 100 },
|
|
105
|
+
{ x: 200, y: 100 },
|
|
106
|
+
];
|
|
107
|
+
const edge = {
|
|
108
|
+
points: [
|
|
109
|
+
{ x: 64, y: 64 },
|
|
110
|
+
{ x: 64, y: 100 },
|
|
111
|
+
{ x: 264, y: 100 },
|
|
112
|
+
{ x: 264, y: 200 },
|
|
113
|
+
],
|
|
114
|
+
source,
|
|
115
|
+
target: right,
|
|
116
|
+
};
|
|
117
|
+
const fixed = [bus];
|
|
118
|
+
const [moved] = nudge([edge], { obstacles: [source, right], fixed });
|
|
119
|
+
expect(fixed[0]).toEqual(bus);
|
|
120
|
+
expect(Math.abs(moved[1].y - 100)).toBeGreaterThanOrEqual(8);
|
|
121
|
+
expect(moved[0]).toEqual({ x: 64, y: 64 });
|
|
122
|
+
expect(orthogonal(moved)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('ports stay put when separation would leave the box side', ({ expect }) => {
|
|
126
|
+
const narrow: Rect = { x: 0, y: 0, w: 16, h: 64 };
|
|
127
|
+
const below: Rect = { x: 0, y: 200, w: 16, h: 64 };
|
|
128
|
+
const path = {
|
|
129
|
+
points: [
|
|
130
|
+
{ x: 8, y: 64 },
|
|
131
|
+
{ x: 8, y: 200 },
|
|
132
|
+
],
|
|
133
|
+
source: narrow,
|
|
134
|
+
target: below,
|
|
135
|
+
};
|
|
136
|
+
const result = nudge([path, path], { obstacles: [narrow, below] });
|
|
137
|
+
expect(result).toEqual([path.points, path.points]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('a run is never pushed through a box', ({ expect }) => {
|
|
141
|
+
// Both lanes beside the shared run are blocked by boxes hugging it.
|
|
142
|
+
const wall = (x: number): Rect => ({ x, y: 80, w: 40, h: 100 });
|
|
143
|
+
const edge = (y: number) => ({
|
|
144
|
+
points: [
|
|
145
|
+
{ x: 64, y: 64 },
|
|
146
|
+
{ x: 64, y: 200 },
|
|
147
|
+
],
|
|
148
|
+
source,
|
|
149
|
+
target: { x: 0, y, w: 128, h: 64 },
|
|
150
|
+
});
|
|
151
|
+
const obstacles = [source, wall(20), wall(68), { x: 0, y: 200, w: 128, h: 64 }];
|
|
152
|
+
const result = nudge([edge(200), edge(200)], { obstacles });
|
|
153
|
+
expect(result.map((points) => points[0].x)).toEqual([64, 64]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('separates a shared run even when that costs one crossing', ({ expect }) => {
|
|
157
|
+
// Both leave the left side of one box; the line from the upper port turns off first, so either order crosses once.
|
|
158
|
+
const box: Rect = { x: 100, y: 0, w: 100, h: 40 };
|
|
159
|
+
const near: Rect = { x: 300, y: 80, w: 100, h: 40 };
|
|
160
|
+
const far: Rect = { x: 300, y: 180, w: 100, h: 40 };
|
|
161
|
+
const result = nudge([
|
|
162
|
+
{
|
|
163
|
+
points: [
|
|
164
|
+
{ x: 100, y: 10 },
|
|
165
|
+
{ x: 50, y: 10 },
|
|
166
|
+
{ x: 50, y: 100 },
|
|
167
|
+
{ x: 300, y: 100 },
|
|
168
|
+
],
|
|
169
|
+
source: box,
|
|
170
|
+
target: near,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
points: [
|
|
174
|
+
{ x: 100, y: 30 },
|
|
175
|
+
{ x: 50, y: 30 },
|
|
176
|
+
{ x: 50, y: 200 },
|
|
177
|
+
{ x: 300, y: 200 },
|
|
178
|
+
],
|
|
179
|
+
source: box,
|
|
180
|
+
target: far,
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
expect(overlapping(result[0], result[1])).toBe(false);
|
|
184
|
+
expect(result.every(orthogonal)).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
});
|