@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.
@@ -1,12 +1,12 @@
1
- export function createCoseBilkentLayoutWorker(): {
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 };
@@ -7,6 +7,6 @@ const sharedWorkerMock = {
7
7
  }
8
8
  };
9
9
  module.exports = {
10
- createCoseBilkentLayoutWorker: () => sharedWorkerMock,
11
- createHierarchicalLayoutWorker: () => sharedWorkerMock
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?: number): {
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 dagre from '@neo4j-bloom/dagre';
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 createGraph = (pixelRatio) => {
60
- // Create a new directed graph
61
- const g = new dagre.graphlib.Graph();
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
- const dagreLayoutGraph = dagre.layout(g);
130
- for (const id of dagreLayoutGraph.nodes()) {
131
- const pId = findParent(id, dagreLayoutGraph);
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 = 1) => {
162
- const g = createGraph(pixelRatio);
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 * pixelRatio;
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
- const subGraphs = getConnectedSubGraphs(g, pixelRatio);
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.reverse(),
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('is defined', () => {
4
- expect(layout).toBeDefined();
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('the waypoints are defined by the first relationship', () => {
7
- const nodes = [
8
- { id: '1', color: '#000000', caption: 'Thing1', pinned: false, activated: false, selected: false },
9
- { id: '2', color: '#000000', caption: 'Thing2', pinned: false, activated: false, selected: false },
10
- { id: '3', color: '#000000', caption: 'Thing3', pinned: false, activated: false, selected: false }
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
- 1: { id: '1', color: '#000000', caption: 'Thing1', pinned: false, activated: false, selected: false },
14
- 2: { id: '2', color: '#000000', caption: 'Thing2', pinned: false, activated: false, selected: false },
15
- 3: { id: '3', color: '#000000', caption: 'Thing3', pinned: false, activated: false, selected: false }
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: '1000', from: '1', to: '2', type: 'CONNECTED_TO', properties: {} },
19
- { id: '1002', from: '2', to: '3', type: 'CONNECTED_TO', properties: {} },
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 idToPosition = { 1: { id: '1' }, 2: { id: '2' }, 3: { id: '3' } };
24
- const result = layout(nodes, nodeIds, idToPosition, rels, 'right', 2);
25
- expect(result).toBeDefined();
26
- expect(result.positions).toBeDefined();
27
- expect(result.parents).toBeDefined();
28
- expect(result.waypoints).toBeDefined();
29
- expect(result.waypoints['1-3']).toBeDefined();
30
- const wpObject = result.waypoints['1-3'];
31
- const xs = [wpObject.from.x, ...wpObject.points.map((p) => p.x), wpObject.to.x];
32
- for (let i = 1; i < xs.length; i++) {
33
- expect(xs[i]).toBeGreaterThan(xs[i - 1]);
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
- import { coseBilkentLayoutFallbackWorker } from './cosebilkent-layout/CoseBilkentLayout.fallback.js';
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 { createCoseBilkentLayoutWorker, createHierarchicalLayoutWorker, hierarchicalLayoutFallbackWorker, coseBilkentLayoutFallbackWorker };
3
+ export { createHierarchicalLayoutWorker, hierarchicalLayoutFallbackWorker };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neo4j-nvl/layout-workers",
3
- "version": "1.2.1",
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
- "@neo4j-bloom/dagre": "0.8.14",
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,10 +0,0 @@
1
- export namespace coseBilkentLayoutFallbackWorker {
2
- let port: CoseBilkentLayoutFallbackWorker;
3
- }
4
- declare class CoseBilkentLayoutFallbackWorker {
5
- start(): void;
6
- postMessage(data: any): void;
7
- onmessage(): void;
8
- close(): void;
9
- }
10
- export {};
@@ -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,3 +0,0 @@
1
- export function layout(elements: any, spacingFactor: any): {
2
- positions: {};
3
- };
@@ -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;
@@ -1,8 +0,0 @@
1
- /**
2
- * @returns {SharedWorker}
3
- * @internal
4
- */
5
- export const createCoseBilkentLayoutWorker = () => new SharedWorker(new URL('./CoseBilkentLayout.worker.js', import.meta.url), {
6
- type: 'module',
7
- name: 'CoseBilkentLayout'
8
- });