@neo4j-nvl/layout-workers 1.2.1 → 2.0.0-55ef4c54
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/lib/__mocks__/index.d.ts +10 -9
- package/lib/__mocks__/index.js +2 -2
- package/lib/hierarchical-layout/HierarchicalLayout.fallback.js +2 -2
- package/lib/hierarchical-layout/HierarchicalLayout.worker.js +2 -2
- package/lib/hierarchical-layout/dagre-layout-impl.d.ts +1 -1
- package/lib/hierarchical-layout/dagre-layout-impl.js +42 -15
- package/lib/hierarchical-layout/dagre-layout-impl.test.js +142 -26
- package/lib/index.d.ts +1 -3
- package/lib/index.js +1 -3
- package/package.json +7 -8
- package/lib/cosebilkent-layout/CoseBilkentLayout.fallback.d.ts +0 -10
- package/lib/cosebilkent-layout/CoseBilkentLayout.fallback.js +0 -16
- package/lib/cosebilkent-layout/CoseBilkentLayout.worker.d.ts +0 -1
- package/lib/cosebilkent-layout/CoseBilkentLayout.worker.js +0 -9
- package/lib/cosebilkent-layout/cose-bilkent-layout-impl.d.ts +0 -3
- package/lib/cosebilkent-layout/cose-bilkent-layout-impl.js +0 -29
- package/lib/cosebilkent-layout/workerFactory.d.mts +0 -1
- package/lib/cosebilkent-layout/workerFactory.mjs +0 -8
package/lib/__mocks__/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
port
|
|
3
|
-
start: jest.Mock<any, any, any>;
|
|
4
|
-
onmessage: jest.Mock<any, any, any>;
|
|
5
|
-
postMessage: jest.Mock<any, any, any>;
|
|
6
|
-
close: jest.Mock<any, any, any>;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
export function createHierarchicalLayoutWorker(): {
|
|
1
|
+
declare namespace sharedWorkerMock {
|
|
2
|
+
namespace port {
|
|
3
|
+
let start: jest.Mock<any, any, any>;
|
|
4
|
+
let onmessage: jest.Mock<any, any, any>;
|
|
5
|
+
let postMessage: jest.Mock<any, any, any>;
|
|
6
|
+
let close: jest.Mock<any, any, any>;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare function createHierarchicalLayoutWorker(): {
|
|
10
10
|
port: {
|
|
11
11
|
start: jest.Mock<any, any, any>;
|
|
12
12
|
onmessage: jest.Mock<any, any, any>;
|
|
@@ -14,3 +14,4 @@ export function createHierarchicalLayoutWorker(): {
|
|
|
14
14
|
close: jest.Mock<any, any, any>;
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
|
+
export { sharedWorkerMock as hierarchicalLayoutFallbackWorker };
|
package/lib/__mocks__/index.js
CHANGED
|
@@ -7,6 +7,6 @@ const sharedWorkerMock = {
|
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
9
|
module.exports = {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
createHierarchicalLayoutWorker: () => sharedWorkerMock,
|
|
11
|
+
hierarchicalLayoutFallbackWorker: sharedWorkerMock
|
|
12
12
|
};
|
|
@@ -4,8 +4,8 @@ import { layout } from './dagre-layout-impl.js';
|
|
|
4
4
|
class HierarchicalLayoutFallbackWorker {
|
|
5
5
|
start() { }
|
|
6
6
|
postMessage(data) {
|
|
7
|
-
const { nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, forcedDelay = 0 } = data;
|
|
8
|
-
const layoutData = layout(nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio);
|
|
7
|
+
const { nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, forcedDelay = 0, clusterAssignments } = data;
|
|
8
|
+
const layoutData = layout(nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, clusterAssignments);
|
|
9
9
|
if (forcedDelay) {
|
|
10
10
|
setTimeout(() => {
|
|
11
11
|
this.onmessage({ data: layoutData });
|
|
@@ -2,8 +2,8 @@ import { layout } from './dagre-layout-impl.js';
|
|
|
2
2
|
onconnect = ({ ports }) => {
|
|
3
3
|
const port = ports[0];
|
|
4
4
|
port.onmessage = ({ data }) => {
|
|
5
|
-
const { nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, forcedDelay = 0 } = data;
|
|
6
|
-
const layoutData = layout(nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio);
|
|
5
|
+
const { nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, forcedDelay = 0, clusterAssignments } = data;
|
|
6
|
+
const layoutData = layout(nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, clusterAssignments);
|
|
7
7
|
if (forcedDelay) {
|
|
8
8
|
setTimeout(() => {
|
|
9
9
|
port.postMessage(layoutData);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function layout(nodes: any, nodeIds: any, idToPosition: any, rels: any, direction: any, packing: any, pixelRatio
|
|
1
|
+
export function layout(nodes: any, nodeIds: any, idToPosition: any, rels: any, direction: any, packing: any, pixelRatio: any, clusterAssignments: any): {
|
|
2
2
|
positions: {};
|
|
3
3
|
parents: {};
|
|
4
4
|
waypoints: {};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { layout as dagreLayout, graphlib } from '@dagrejs/dagre';
|
|
2
2
|
import binPack from 'bin-pack';
|
|
3
|
-
import graphlib from 'graphlib';
|
|
4
3
|
import { DefaultNodeSize, DirectionDown, DirectionRight, DirectionUp, Directions, GlAdjust, PackingBin, Ranker, SubGraphSpacing } from './constants.js';
|
|
5
4
|
const isDirectionVertical = (direction) => direction === DirectionUp || direction === DirectionDown;
|
|
6
5
|
const isDirectionNatural = (direction) => direction === DirectionDown || direction === DirectionRight;
|
|
@@ -56,9 +55,11 @@ const getGraphDimensions = (g) => {
|
|
|
56
55
|
yOffset: minCenterY - minY
|
|
57
56
|
};
|
|
58
57
|
};
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
const SYNTHETIC_PARENT_PREFIX = 'syntheticParent:';
|
|
59
|
+
const toSyntheticParentNodeId = (clusterId) => `${SYNTHETIC_PARENT_PREFIX}${clusterId}`;
|
|
60
|
+
const createGraph = (pixelRatio, { compound = false } = {}) => {
|
|
61
|
+
// Create a new directed graph (compound when clusterBy assignments are provided)
|
|
62
|
+
const g = new graphlib.Graph(compound ? { compound: true } : undefined);
|
|
62
63
|
// Set an object for the graph label
|
|
63
64
|
g.setGraph({});
|
|
64
65
|
// Default to assigning a new object as a label for each new edge.
|
|
@@ -70,6 +71,24 @@ const createGraph = (pixelRatio) => {
|
|
|
70
71
|
g.graph().ranksep = 75 * pixelRatio;
|
|
71
72
|
return g;
|
|
72
73
|
};
|
|
74
|
+
const applySyntheticParents = (g, clusterAssignments) => {
|
|
75
|
+
if (!clusterAssignments) {
|
|
76
|
+
return new Set();
|
|
77
|
+
}
|
|
78
|
+
const createdSyntheticParents = new Set();
|
|
79
|
+
for (const [nodeId, clusterId] of Object.entries(clusterAssignments)) {
|
|
80
|
+
if (!g.hasNode(nodeId) || clusterId === undefined || clusterId === null) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const syntheticParentNodeId = toSyntheticParentNodeId(clusterId);
|
|
84
|
+
if (!createdSyntheticParents.has(syntheticParentNodeId)) {
|
|
85
|
+
g.setNode(syntheticParentNodeId, { width: 0, height: 0 });
|
|
86
|
+
createdSyntheticParents.add(syntheticParentNodeId);
|
|
87
|
+
}
|
|
88
|
+
g.setParent(nodeId, syntheticParentNodeId);
|
|
89
|
+
}
|
|
90
|
+
return createdSyntheticParents;
|
|
91
|
+
};
|
|
73
92
|
const findParentForEdges = (id, connectedNodes, layoutGraph) => {
|
|
74
93
|
const { rank: currentRank } = layoutGraph.node(id);
|
|
75
94
|
let pRank = null;
|
|
@@ -123,12 +142,15 @@ const getConnectedSubGraphs = (g, pixelRatio) => {
|
|
|
123
142
|
}
|
|
124
143
|
return subGraphs;
|
|
125
144
|
};
|
|
126
|
-
const layoutGraph = (g, direction, parents) => {
|
|
145
|
+
const layoutGraph = (g, direction, parents, syntheticParentNodeIds = new Set()) => {
|
|
127
146
|
g.graph().ranker = Ranker;
|
|
128
147
|
g.graph().rankdir = Directions[direction];
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
dagreLayout(g, { useDynamic: false });
|
|
149
|
+
syntheticParentNodeIds.forEach((id) => {
|
|
150
|
+
g.removeNode(id);
|
|
151
|
+
});
|
|
152
|
+
for (const id of g.nodes()) {
|
|
153
|
+
const pId = findParent(id, g);
|
|
132
154
|
if (pId !== null) {
|
|
133
155
|
parents[id] = pId;
|
|
134
156
|
}
|
|
@@ -158,8 +180,10 @@ const mergeStraightPoints = (points) => {
|
|
|
158
180
|
mergedPoints.push(points[points.length - 1]);
|
|
159
181
|
return mergedPoints;
|
|
160
182
|
};
|
|
161
|
-
export const layout = (nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio
|
|
162
|
-
const
|
|
183
|
+
export const layout = (nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, clusterAssignments) => {
|
|
184
|
+
const resolvedPixelRatio = pixelRatio ?? 1;
|
|
185
|
+
const useClusters = Boolean(clusterAssignments && Object.keys(clusterAssignments).length > 0);
|
|
186
|
+
const g = createGraph(resolvedPixelRatio, { compound: useClusters });
|
|
163
187
|
const parents = {};
|
|
164
188
|
const positionSum = { x: 0, y: 0 };
|
|
165
189
|
const numNodes = nodes.length;
|
|
@@ -169,9 +193,10 @@ export const layout = (nodes, nodeIds, idToPosition, rels, direction, packing, p
|
|
|
169
193
|
const position = idToPosition[n.id];
|
|
170
194
|
positionSum.x += position?.x || 0;
|
|
171
195
|
positionSum.y += position?.y || 0;
|
|
172
|
-
const size = (n.size || DefaultNodeSize) * GlAdjust *
|
|
196
|
+
const size = (n.size || DefaultNodeSize) * GlAdjust * resolvedPixelRatio;
|
|
173
197
|
g.setNode(n.id, { width: size, height: size });
|
|
174
198
|
}
|
|
199
|
+
const syntheticParentNodeIds = useClusters ? applySyntheticParents(g, clusterAssignments) : new Set();
|
|
175
200
|
const prevNodeCenterPoint = numNodes ? [positionSum.x / numNodes, positionSum.y / numNodes] : [0, 0];
|
|
176
201
|
// Add edges to the graph.
|
|
177
202
|
const addedRel = {};
|
|
@@ -184,7 +209,9 @@ export const layout = (nodes, nodeIds, idToPosition, rels, direction, packing, p
|
|
|
184
209
|
}
|
|
185
210
|
}
|
|
186
211
|
}
|
|
187
|
-
|
|
212
|
+
// With clusterBy, lay out one compound graph so same-cluster nodes stay together
|
|
213
|
+
// even across disconnected components. Otherwise keep component packing.
|
|
214
|
+
const subGraphs = useClusters ? [g] : getConnectedSubGraphs(g, resolvedPixelRatio);
|
|
188
215
|
if (subGraphs.length > 1) {
|
|
189
216
|
subGraphs.forEach((subGraph) => layoutGraph(subGraph, direction, parents));
|
|
190
217
|
const isVertical = isDirectionVertical(direction);
|
|
@@ -316,7 +343,7 @@ export const layout = (nodes, nodeIds, idToPosition, rels, direction, packing, p
|
|
|
316
343
|
}
|
|
317
344
|
}
|
|
318
345
|
else {
|
|
319
|
-
layoutGraph(g, direction, parents);
|
|
346
|
+
layoutGraph(g, direction, parents, syntheticParentNodeIds);
|
|
320
347
|
}
|
|
321
348
|
positionSum.x = 0;
|
|
322
349
|
positionSum.y = 0;
|
|
@@ -355,7 +382,7 @@ export const layout = (nodes, nodeIds, idToPosition, rels, direction, packing, p
|
|
|
355
382
|
}
|
|
356
383
|
};
|
|
357
384
|
waypoints[`${id.w}-${id.v}`] = {
|
|
358
|
-
points: mergedPoints.
|
|
385
|
+
points: mergedPoints.toReversed(),
|
|
359
386
|
from: {
|
|
360
387
|
x: positions[id.w].x,
|
|
361
388
|
y: positions[id.w].y
|
|
@@ -1,36 +1,152 @@
|
|
|
1
1
|
import { layout } from './dagre-layout-impl.js';
|
|
2
|
+
const createNode = (id) => ({
|
|
3
|
+
id,
|
|
4
|
+
color: '#000000',
|
|
5
|
+
caption: `Thing${id}`,
|
|
6
|
+
pinned: false,
|
|
7
|
+
activated: false,
|
|
8
|
+
selected: false
|
|
9
|
+
});
|
|
10
|
+
const createRelationship = (id, from, to) => ({
|
|
11
|
+
id,
|
|
12
|
+
from,
|
|
13
|
+
to,
|
|
14
|
+
type: 'CONNECTED_TO',
|
|
15
|
+
properties: {}
|
|
16
|
+
});
|
|
17
|
+
const runLayout = (nodeIds, relationships, direction = 'down', packing = 'bin') => {
|
|
18
|
+
const nodes = nodeIds.map(createNode);
|
|
19
|
+
const nodesById = Object.fromEntries(nodes.map((node) => [node.id, node]));
|
|
20
|
+
const positionsById = Object.fromEntries(nodeIds.map((id) => [id, { id }]));
|
|
21
|
+
return layout(nodes, nodesById, positionsById, relationships, direction, packing);
|
|
22
|
+
};
|
|
2
23
|
describe('Dagre-layout-impl', () => {
|
|
3
|
-
test('
|
|
4
|
-
|
|
24
|
+
test('assigns parents from preceding ranks', () => {
|
|
25
|
+
const result = runLayout(['1', '2', '3'], [createRelationship('1000', '1', '2'), createRelationship('1001', '2', '3')]);
|
|
26
|
+
expect(result.parents).toEqual({ 2: '1', 3: '2' });
|
|
27
|
+
for (const [childId, parentId] of Object.entries(result.parents)) {
|
|
28
|
+
expect(result.positions[parentId].y).toBeLessThan(result.positions[childId].y);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
test.each(['bin', 'stack'])('positions every disconnected component with %s packing', (packing) => {
|
|
32
|
+
const result = runLayout(['1', '2', '3', '4', '5'], [createRelationship('1000', '1', '2'), createRelationship('1001', '3', '4')], 'down', packing);
|
|
33
|
+
expect(Object.keys(result.positions)).toHaveLength(5);
|
|
34
|
+
for (const position of Object.values(result.positions)) {
|
|
35
|
+
expect(Number.isFinite(position.x)).toBe(true);
|
|
36
|
+
expect(Number.isFinite(position.y)).toBe(true);
|
|
37
|
+
}
|
|
5
38
|
});
|
|
6
|
-
test('
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
39
|
+
test('creates monotonic waypoints for a long edge', () => {
|
|
40
|
+
const result = runLayout(['1', '2', '3'], [
|
|
41
|
+
createRelationship('1000', '1', '2'),
|
|
42
|
+
createRelationship('1001', '2', '3'),
|
|
43
|
+
createRelationship('1002', '1', '3')
|
|
44
|
+
], 'right');
|
|
45
|
+
const waypoint = result.waypoints['1-3'];
|
|
46
|
+
expect(waypoint.points.length).toBeGreaterThan(1);
|
|
47
|
+
const xs = [waypoint.from.x, ...waypoint.points.map(({ x }) => x), waypoint.to.x];
|
|
48
|
+
for (let i = 1; i < xs.length; i++) {
|
|
49
|
+
expect(xs[i]).toBeGreaterThan(xs[i - 1]);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
test('synthetic parent nodes are omitted from positions and animation parents', () => {
|
|
53
|
+
const nodes = [{ id: 'a' }, { id: 'b' }, { id: 'c' }];
|
|
54
|
+
const nodeIds = { a: { id: 'a' }, b: { id: 'b' }, c: { id: 'c' } };
|
|
55
|
+
const idToPosition = {
|
|
56
|
+
a: { id: 'a', x: 0, y: 0 },
|
|
57
|
+
b: { id: 'b', x: 0, y: 0 },
|
|
58
|
+
c: { id: 'c', x: 0, y: 0 }
|
|
59
|
+
};
|
|
60
|
+
const rels = [
|
|
61
|
+
{ id: 'ab', from: 'a', to: 'b' },
|
|
62
|
+
{ id: 'bc', from: 'b', to: 'c' }
|
|
11
63
|
];
|
|
64
|
+
const clusterAssignments = { a: 'group-1', b: 'group-1', c: 'group-2' };
|
|
65
|
+
const { positions, parents } = layout(nodes, nodeIds, idToPosition, rels, 'down', 'bin', 1, clusterAssignments);
|
|
66
|
+
expect(Object.keys(positions).sort()).toEqual(nodes.map(({ id }) => id).sort());
|
|
67
|
+
expect(Object.values(parents).every((id) => id in nodeIds)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
test('clusterAssignments group same-cluster nodes', () => {
|
|
70
|
+
const categories = ['Person', 'Company', 'Product'];
|
|
71
|
+
const nodes = [];
|
|
72
|
+
const nodeIds = {};
|
|
73
|
+
const idToPosition = {};
|
|
74
|
+
const rels = [];
|
|
75
|
+
const clusterAssignments = {};
|
|
76
|
+
categories.forEach((category) => {
|
|
77
|
+
for (let i = 0; i < 4; i++) {
|
|
78
|
+
const id = `${category}-${i}`;
|
|
79
|
+
nodes.push({ id });
|
|
80
|
+
nodeIds[id] = { id };
|
|
81
|
+
idToPosition[id] = { id, x: 0, y: 0 };
|
|
82
|
+
clusterAssignments[id] = category;
|
|
83
|
+
if (i > 0) {
|
|
84
|
+
rels.push({ id: `${id}-prev`, from: `${category}-${i - 1}`, to: id });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
rels.push({ id: 'cross-1', from: 'Person-0', to: 'Company-0' });
|
|
89
|
+
rels.push({ id: 'cross-2', from: 'Company-3', to: 'Product-0' });
|
|
90
|
+
const { positions } = layout(nodes, nodeIds, idToPosition, rels, 'down', 'bin', 1, clusterAssignments);
|
|
91
|
+
expect(Object.keys(positions)).toHaveLength(nodes.length);
|
|
92
|
+
const centroid = (ids) => {
|
|
93
|
+
let x = 0;
|
|
94
|
+
let y = 0;
|
|
95
|
+
ids.forEach((id) => {
|
|
96
|
+
x += positions[id].x;
|
|
97
|
+
y += positions[id].y;
|
|
98
|
+
});
|
|
99
|
+
return { x: x / ids.length, y: y / ids.length };
|
|
100
|
+
};
|
|
101
|
+
const clusterIds = categories.map((category) => nodes.filter((node) => node.id.startsWith(`${category}-`)).map((node) => node.id));
|
|
102
|
+
const centroids = clusterIds.map(centroid);
|
|
103
|
+
// Same-cluster members should be closer to their own centroid than to other centroids on average
|
|
104
|
+
clusterIds.forEach((ids, clusterIndex) => {
|
|
105
|
+
const own = centroids[clusterIndex];
|
|
106
|
+
const avgOwn = ids.reduce((sum, id) => {
|
|
107
|
+
const dx = positions[id].x - own.x;
|
|
108
|
+
const dy = positions[id].y - own.y;
|
|
109
|
+
return sum + Math.hypot(dx, dy);
|
|
110
|
+
}, 0) / ids.length;
|
|
111
|
+
const otherDistances = centroids
|
|
112
|
+
.filter((_, index) => index !== clusterIndex)
|
|
113
|
+
.map((other) => Math.hypot(own.x - other.x, own.y - other.y));
|
|
114
|
+
const minOtherCentroid = Math.min(...otherDistances);
|
|
115
|
+
expect(avgOwn).toBeLessThan(minOtherCentroid);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
test('omitting clusterAssignments keeps the non-compound packing path', () => {
|
|
119
|
+
const nodes = [{ id: 'a' }, { id: 'b' }, { id: 'c' }, { id: 'd' }];
|
|
12
120
|
const nodeIds = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
121
|
+
a: { id: 'a' },
|
|
122
|
+
b: { id: 'b' },
|
|
123
|
+
c: { id: 'c' },
|
|
124
|
+
d: { id: 'd' }
|
|
125
|
+
};
|
|
126
|
+
const idToPosition = {
|
|
127
|
+
a: { id: 'a', x: 0, y: 0 },
|
|
128
|
+
b: { id: 'b', x: 0, y: 0 },
|
|
129
|
+
c: { id: 'c', x: 0, y: 0 },
|
|
130
|
+
d: { id: 'd', x: 0, y: 0 }
|
|
16
131
|
};
|
|
132
|
+
// Two disconnected components
|
|
17
133
|
const rels = [
|
|
18
|
-
{ id: '
|
|
19
|
-
{ id: '
|
|
20
|
-
{ id: '1004', from: '1', to: '3', type: 'CONNECTED_TO', properties: {} },
|
|
21
|
-
{ id: '1005', from: '3', to: '1', type: 'CONNECTED_TO', properties: {} }
|
|
134
|
+
{ id: 'ab', from: 'a', to: 'b' },
|
|
135
|
+
{ id: 'cd', from: 'c', to: 'd' }
|
|
22
136
|
];
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
expect(
|
|
26
|
-
expect(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
137
|
+
const { positions } = layout(nodes, nodeIds, idToPosition, rels, 'down', 'bin', 1);
|
|
138
|
+
expect(Object.keys(positions)).toHaveLength(4);
|
|
139
|
+
expect(positions.a).toBeDefined();
|
|
140
|
+
expect(positions.d).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
test('ignores self-loops and reverse duplicate pairs', () => {
|
|
143
|
+
const result = runLayout(['1', '2'], [
|
|
144
|
+
createRelationship('1000', '1', '1'),
|
|
145
|
+
createRelationship('1001', '1', '2'),
|
|
146
|
+
createRelationship('1002', '2', '1')
|
|
147
|
+
], 'right');
|
|
148
|
+
expect(result.parents).toEqual({ 2: '1' });
|
|
149
|
+
expect(result.positions['1'].x).toBeLessThan(result.positions['2'].x);
|
|
150
|
+
expect(result.waypoints['1-1']).toBeUndefined();
|
|
35
151
|
});
|
|
36
152
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createCoseBilkentLayoutWorker } from './cosebilkent-layout/workerFactory.mjs';
|
|
2
1
|
import { createHierarchicalLayoutWorker } from './hierarchical-layout/workerFactory.mjs';
|
|
3
2
|
import { hierarchicalLayoutFallbackWorker } from './hierarchical-layout/HierarchicalLayout.fallback.js';
|
|
4
|
-
|
|
5
|
-
export { createCoseBilkentLayoutWorker, createHierarchicalLayoutWorker, hierarchicalLayoutFallbackWorker, coseBilkentLayoutFallbackWorker };
|
|
3
|
+
export { createHierarchicalLayoutWorker, hierarchicalLayoutFallbackWorker };
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { coseBilkentLayoutFallbackWorker } from './cosebilkent-layout/CoseBilkentLayout.fallback.js';
|
|
2
|
-
import { createCoseBilkentLayoutWorker } from './cosebilkent-layout/workerFactory.mjs';
|
|
3
1
|
import { hierarchicalLayoutFallbackWorker } from './hierarchical-layout/HierarchicalLayout.fallback.js';
|
|
4
2
|
import { createHierarchicalLayoutWorker } from './hierarchical-layout/workerFactory.mjs';
|
|
5
|
-
export {
|
|
3
|
+
export { createHierarchicalLayoutWorker, hierarchicalLayoutFallbackWorker };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-nvl/layout-workers",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-55ef4c54",
|
|
4
4
|
"license": "SEE LICENSE IN 'LICENSE.txt'",
|
|
5
5
|
"description": "Layout workers for the Neo4j Visualization Library",
|
|
6
6
|
"keywords": [
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"watch": "yarn global:tsc -w",
|
|
23
23
|
"prepack": "cp ../../LICENSE.txt ./",
|
|
24
24
|
"postpack": "rm LICENSE.txt",
|
|
25
|
-
"eslint": "yarn global:eslint ./src/ --ext .js,.mjs"
|
|
25
|
+
"eslint": "yarn global:eslint ./src/ --ext .js,.mjs",
|
|
26
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules yarn global:jest"
|
|
26
27
|
},
|
|
27
28
|
"typedoc": {
|
|
28
29
|
"entryPoint": "./src/index.js",
|
|
@@ -31,13 +32,11 @@
|
|
|
31
32
|
"tsconfig": "./tsconfig.json"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"bin-pack": "1.0.2"
|
|
36
|
-
"cytoscape": "3.33.1",
|
|
37
|
-
"cytoscape-cose-bilkent": "4.1.0",
|
|
38
|
-
"graphlib": "2.1.8"
|
|
35
|
+
"@dagrejs/dagre": "3.1.1",
|
|
36
|
+
"bin-pack": "1.0.2"
|
|
39
37
|
},
|
|
40
38
|
"devDependencies": {
|
|
41
39
|
"@types/node": "18.19.130"
|
|
42
|
-
}
|
|
40
|
+
},
|
|
41
|
+
"stableVersion": "2.0.0"
|
|
43
42
|
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
2
|
-
/* eslint-disable no-empty-function */
|
|
3
|
-
import { layout } from './cose-bilkent-layout-impl.js';
|
|
4
|
-
class CoseBilkentLayoutFallbackWorker {
|
|
5
|
-
start() { }
|
|
6
|
-
postMessage(data) {
|
|
7
|
-
const { elements, spacingFactor } = data;
|
|
8
|
-
const layoutData = layout(elements, spacingFactor);
|
|
9
|
-
this.onmessage({ data: layoutData });
|
|
10
|
-
}
|
|
11
|
-
onmessage() { }
|
|
12
|
-
close() { }
|
|
13
|
-
}
|
|
14
|
-
export const coseBilkentLayoutFallbackWorker = {
|
|
15
|
-
port: new CoseBilkentLayoutFallbackWorker()
|
|
16
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { layout } from './cose-bilkent-layout-impl.js';
|
|
2
|
-
onconnect = ({ ports }) => {
|
|
3
|
-
const port = ports[0];
|
|
4
|
-
port.onmessage = ({ data }) => {
|
|
5
|
-
const { elements, spacingFactor } = data;
|
|
6
|
-
const layoutData = layout(elements, spacingFactor);
|
|
7
|
-
port.postMessage(layoutData);
|
|
8
|
-
};
|
|
9
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import cytoscape from 'cytoscape';
|
|
2
|
-
import coseBilkent from 'cytoscape-cose-bilkent';
|
|
3
|
-
cytoscape.use(coseBilkent);
|
|
4
|
-
const type = 'cose-bilkent';
|
|
5
|
-
export const layout = (elements, spacingFactor) => {
|
|
6
|
-
const c = cytoscape({
|
|
7
|
-
headless: true,
|
|
8
|
-
styleEnabled: false
|
|
9
|
-
});
|
|
10
|
-
c.add(elements);
|
|
11
|
-
const positions = {};
|
|
12
|
-
const cytoLayout = c.layout({
|
|
13
|
-
name: type,
|
|
14
|
-
animate: false,
|
|
15
|
-
spacingFactor,
|
|
16
|
-
quality: 'default',
|
|
17
|
-
tile: false,
|
|
18
|
-
randomize: true,
|
|
19
|
-
stop: () => {
|
|
20
|
-
c.nodes().forEach((n) => {
|
|
21
|
-
positions[n.id()] = { ...n.position() };
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
cytoLayout.run();
|
|
26
|
-
return {
|
|
27
|
-
positions
|
|
28
|
-
};
|
|
29
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export function createCoseBilkentLayoutWorker(): SharedWorker;
|