@neo4j-nvl/layout-workers 0.1.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/README.md ADDED
@@ -0,0 +1 @@
1
+ # Layout Workers
@@ -0,0 +1,9 @@
1
+ import { layout } from './cose-bilkent-layout-impl.js';
2
+ const onMessage = ({ data, target }) => {
3
+ if (target.name === 'CoseBilkentLayout') {
4
+ const { elements, boundingBox, spacingFactor, nodeCenterPoint } = data;
5
+ const layoutData = layout(elements, boundingBox, spacingFactor, nodeCenterPoint);
6
+ postMessage(layoutData);
7
+ }
8
+ };
9
+ self.addEventListener('message', onMessage);
@@ -0,0 +1,3 @@
1
+ export function layout(elements: any, boundingBox: any, spacingFactor: any, nodeCenterPoint: any): {
2
+ positions: {};
3
+ };
@@ -0,0 +1,32 @@
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, boundingBox, spacingFactor, nodeCenterPoint) => {
6
+ const c = cytoscape({
7
+ headless: true,
8
+ styleEnabled: false,
9
+ boundingBox
10
+ });
11
+ c.add(elements);
12
+ const positions = {};
13
+ const cytoLayout = c.layout({
14
+ name: type,
15
+ animate: false,
16
+ spacingFactor,
17
+ quality: 'default',
18
+ tile: false,
19
+ randomize: true,
20
+ stop: () => {
21
+ c.nodes().forEach(n => {
22
+ positions[n.id()] = { ...n.position() };
23
+ positions[n.id()].x += nodeCenterPoint[0];
24
+ positions[n.id()].y += nodeCenterPoint[1];
25
+ });
26
+ }
27
+ });
28
+ cytoLayout.run();
29
+ return {
30
+ positions
31
+ };
32
+ };
@@ -0,0 +1 @@
1
+ export function createCoseBilkentLayoutWorker(): Worker;
@@ -0,0 +1 @@
1
+ export const createCoseBilkentLayoutWorker = () => new Worker(new URL('./CoseBilkentLayout.worker.js', import.meta.url), { type: 'module', name: 'CoseBilkentLayout' });
@@ -0,0 +1,16 @@
1
+ import { layout } from './dagre-layout-impl.js';
2
+ const onMessage = ({ data, target }) => {
3
+ if (target.name === 'HierarchicalLayout') {
4
+ const { nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio, forcedDelay = 0 } = data;
5
+ const layoutData = layout(nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio);
6
+ if (forcedDelay) {
7
+ setTimeout(() => {
8
+ postMessage(layoutData);
9
+ }, forcedDelay);
10
+ }
11
+ else {
12
+ postMessage(layoutData);
13
+ }
14
+ }
15
+ };
16
+ self.addEventListener('message', onMessage);
@@ -0,0 +1,27 @@
1
+ export const Ranker: "tight-tree";
2
+ export const SubGraphSpacing: 100;
3
+ export const DirectionUp: "up";
4
+ export const DirectionDown: "down";
5
+ export const DirectionLeft: "left";
6
+ export const DirectionRight: "right";
7
+ export namespace Directions {
8
+ const up: string;
9
+ const down: string;
10
+ const left: string;
11
+ const right: string;
12
+ }
13
+ export const PackingBin: "bin";
14
+ export const PackingStack: "stack";
15
+ export const Packings: string[];
16
+ export const PhysTextureSize: 256;
17
+ export const SpringTextureSize: 4096;
18
+ export const MaxIndexBuffer: 65536;
19
+ export const MinRelLength: 100;
20
+ export const Gravity: 100;
21
+ export const SimulationStopVelocity: 100;
22
+ export const MappingEnd: 999999;
23
+ export const ActiveAnimDuration: 1000;
24
+ export const DefaultNodeSize: 25;
25
+ export const DefaultRelWidth: 1;
26
+ export const DisabledRelColor: "#ededed";
27
+ export const GlAdjust: number;
@@ -0,0 +1,28 @@
1
+ export const Ranker = 'tight-tree';
2
+ export const SubGraphSpacing = 100;
3
+ export const DirectionUp = 'up';
4
+ export const DirectionDown = 'down';
5
+ export const DirectionLeft = 'left';
6
+ export const DirectionRight = 'right';
7
+ export const Directions = {
8
+ [DirectionUp]: 'BT',
9
+ [DirectionDown]: 'TB',
10
+ [DirectionLeft]: 'RL',
11
+ [DirectionRight]: 'LR'
12
+ };
13
+ export const PackingBin = 'bin';
14
+ export const PackingStack = 'stack';
15
+ export const Packings = [PackingBin, PackingStack];
16
+ export const PhysTextureSize = 256;
17
+ export const SpringTextureSize = 4096;
18
+ export const MaxIndexBuffer = 65536;
19
+ export const MinRelLength = 100;
20
+ export const Gravity = 100;
21
+ export const SimulationStopVelocity = 100;
22
+ export const MappingEnd = 999999;
23
+ export const ActiveAnimDuration = 1000;
24
+ export const DefaultNodeSize = 25;
25
+ export const DefaultRelWidth = 1;
26
+ export const DisabledRelColor = '#ededed';
27
+ // Factor to make nodes same size in WebGL renderer and Canvas
28
+ export const GlAdjust = 1 / 0.38;
@@ -0,0 +1,5 @@
1
+ export function layout(nodes: any, nodeIds: any, idToPosition: any, rels: any, direction: any, packing: any, pixelRatio?: number): {
2
+ positions: {};
3
+ parents: {};
4
+ waypoints: {};
5
+ };
@@ -0,0 +1,361 @@
1
+ import dagre from '@neo4j-bloom/dagre';
2
+ import graphlib from 'graphlib';
3
+ import binPack from 'bin-pack';
4
+ import { Ranker, SubGraphSpacing, DirectionUp, DirectionDown, DirectionRight, Directions, PackingBin, DefaultNodeSize, GlAdjust } from './constants.js';
5
+ const isDirectionVertical = (direction) => direction === DirectionUp || direction === DirectionDown;
6
+ const isDirectionNatural = (direction) => direction === DirectionDown || direction === DirectionRight;
7
+ const getGraphDimensions = (g) => {
8
+ let minX = null;
9
+ let minY = null;
10
+ let maxX = null;
11
+ let maxY = null;
12
+ let minCenterX = null;
13
+ let minCenterY = null;
14
+ let maxCenterX = null;
15
+ let maxCenterY = null;
16
+ for (const id of g.nodes()) {
17
+ const sn = g.node(id);
18
+ if (minCenterX === null || sn.x < minCenterX) {
19
+ minCenterX = sn.x;
20
+ }
21
+ if (minCenterY === null || sn.y < minCenterY) {
22
+ minCenterY = sn.y;
23
+ }
24
+ if (maxCenterX === null || sn.x > maxCenterX) {
25
+ maxCenterX = sn.x;
26
+ }
27
+ if (maxCenterY === null || sn.y > maxCenterY) {
28
+ maxCenterY = sn.y;
29
+ }
30
+ const halfSize = Math.ceil(sn.width / 2.0);
31
+ if (minX === null || (sn.x - halfSize) < minX) {
32
+ minX = sn.x - halfSize;
33
+ }
34
+ if (minY === null || (sn.y - halfSize) < minY) {
35
+ minY = sn.y - halfSize;
36
+ }
37
+ if (maxX === null || (sn.x + halfSize) > maxX) {
38
+ maxX = sn.x + halfSize;
39
+ }
40
+ if (maxY === null || (sn.y + halfSize) > maxY) {
41
+ maxY = sn.y + halfSize;
42
+ }
43
+ }
44
+ return {
45
+ minX,
46
+ minY,
47
+ maxX,
48
+ maxY,
49
+ minCenterX,
50
+ minCenterY,
51
+ maxCenterX,
52
+ maxCenterY,
53
+ width: maxX - minX,
54
+ height: maxY - minY,
55
+ xOffset: minCenterX - minX,
56
+ yOffset: minCenterY - minY
57
+ };
58
+ };
59
+ const createGraph = (pixelRatio) => {
60
+ // Create a new directed graph
61
+ const g = new dagre.graphlib.Graph();
62
+ // Set an object for the graph label
63
+ g.setGraph({});
64
+ // Default to assigning a new object as a label for each new edge.
65
+ g.setDefaultEdgeLabel(() => ({}));
66
+ // Configuration https://github.com/dagrejs/dagre/wiki#configuring-the-layout
67
+ // Number of pixels that separate nodes horizontally in the layout.
68
+ g.graph().nodesep = 75 * pixelRatio;
69
+ // Number of pixels between each rank in the layout.
70
+ g.graph().ranksep = 75 * pixelRatio;
71
+ return g;
72
+ };
73
+ const findParent = (id, layoutGraph) => {
74
+ // predecessors uses inEdges, successors uses outEdges
75
+ let pId = findParentForEdges(id, layoutGraph.predecessors(id), layoutGraph);
76
+ if (pId === null) {
77
+ pId = findParentForEdges(id, layoutGraph.successors(id), layoutGraph);
78
+ }
79
+ return pId;
80
+ };
81
+ const findParentForEdges = (id, connectedNodes, layoutGraph) => {
82
+ const { rank: currentRank } = layoutGraph.node(id);
83
+ let pRank = null;
84
+ let pId = null;
85
+ for (const otherId of connectedNodes) {
86
+ const { rank } = layoutGraph.node(otherId);
87
+ if (otherId === id || rank >= currentRank) {
88
+ continue;
89
+ }
90
+ else if (rank === currentRank - 1) {
91
+ pRank = rank;
92
+ pId = otherId;
93
+ break;
94
+ }
95
+ else if ((pRank === null && pId === null) || rank > pRank) {
96
+ pRank = rank;
97
+ pId = otherId;
98
+ }
99
+ }
100
+ return pId;
101
+ };
102
+ const getConnectedSubGraphs = (g, pixelRatio) => {
103
+ const subGraphs = [];
104
+ const components = graphlib.alg.components(g);
105
+ if (components.length > 1) {
106
+ for (const component of components) {
107
+ const subGraph = createGraph(pixelRatio);
108
+ for (const id of component) {
109
+ const n = g.node(id);
110
+ subGraph.setNode(id, { width: n.width, height: n.height });
111
+ const outEdges = g.outEdges(id);
112
+ if (outEdges) {
113
+ for (const e of outEdges) {
114
+ subGraph.setEdge(e.v, e.w);
115
+ }
116
+ }
117
+ }
118
+ subGraphs.push(subGraph);
119
+ }
120
+ }
121
+ else {
122
+ subGraphs.push(g);
123
+ }
124
+ return subGraphs;
125
+ };
126
+ const layoutGraph = (g, direction, parents) => {
127
+ g.graph().ranker = Ranker;
128
+ g.graph().rankdir = Directions[direction];
129
+ const layoutGraph = dagre.layout(g);
130
+ for (const id of layoutGraph.nodes()) {
131
+ const pId = findParent(id, layoutGraph);
132
+ if (pId !== null) {
133
+ parents[id] = pId;
134
+ }
135
+ }
136
+ };
137
+ const getDistance = (p1, p2) => Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
138
+ const mergeStraightPoints = (points) => {
139
+ const mergedPoints = [points[0]];
140
+ let prevSegment = { p1: points[0], p2: points[1] };
141
+ let prevLength = getDistance(prevSegment.p1, prevSegment.p2);
142
+ for (let i = 2; i < points.length; i++) {
143
+ let currentSegment = { p1: points[i - 1], p2: points[i] };
144
+ let currentLength = getDistance(currentSegment.p1, currentSegment.p2);
145
+ const compositeSegment = { p1: prevSegment.p1, p2: currentSegment.p2 };
146
+ const compositeLength = getDistance(compositeSegment.p1, compositeSegment.p2);
147
+ // if 2 consecutive segments are parallel then join them
148
+ // Use the triangular inequality for checking the parallelism
149
+ if (currentLength + prevLength - compositeLength < 0.1) {
150
+ mergedPoints.pop();
151
+ currentSegment = compositeSegment;
152
+ currentLength = compositeLength;
153
+ }
154
+ mergedPoints.push(currentSegment.p1);
155
+ prevSegment = currentSegment;
156
+ prevLength = currentLength;
157
+ }
158
+ mergedPoints.push(points[points.length - 1]);
159
+ return mergedPoints;
160
+ };
161
+ export const layout = (nodes, nodeIds, idToPosition, rels, direction, packing, pixelRatio = 1) => {
162
+ const g = createGraph(pixelRatio);
163
+ const parents = {};
164
+ const positionSum = { x: 0, y: 0 };
165
+ const numNodes = nodes.length;
166
+ // Add nodes to the graph. The first argument is the node id. The second is
167
+ // metadata about the node.
168
+ for (const n of nodes) {
169
+ const position = idToPosition[n.id];
170
+ positionSum.x += position?.x || 0;
171
+ positionSum.y += position?.y || 0;
172
+ const size = (n.size || DefaultNodeSize) * GlAdjust * pixelRatio;
173
+ g.setNode(n.id, { width: size, height: size });
174
+ }
175
+ const prevNodeCenterPoint = numNodes ? [positionSum.x / numNodes, positionSum.y / numNodes] : [0, 0];
176
+ // Add edges to the graph.
177
+ const addedRel = {};
178
+ for (const r of rels) {
179
+ if (nodeIds[r.from] && nodeIds[r.to] && r.from !== r.to) {
180
+ const relKey = r.from < r.to ? `${r.from}-${r.to}` : `${r.to}-${r.from}`;
181
+ if (!addedRel[relKey]) {
182
+ addedRel[relKey] = 1;
183
+ g.setEdge(r.from, r.to);
184
+ }
185
+ }
186
+ }
187
+ const subGraphs = getConnectedSubGraphs(g, pixelRatio);
188
+ if (subGraphs.length > 1) {
189
+ subGraphs.forEach(subGraph => layoutGraph(subGraph, direction, parents));
190
+ const isVertical = isDirectionVertical(direction);
191
+ const isNatural = isDirectionNatural(direction);
192
+ const singleNodeGraphs = subGraphs.filter(g => g.nodeCount() === 1);
193
+ const multiNodeGraphs = subGraphs.filter(g => g.nodeCount() !== 1);
194
+ if (packing === PackingBin) {
195
+ multiNodeGraphs.sort((a, b) => b.nodeCount() - a.nodeCount());
196
+ const adjustDimensionPaddingNormal = ({ width, height, ...rest }) => ({ ...rest, width: width + SubGraphSpacing, height: height + SubGraphSpacing });
197
+ const adjustDimensionPaddingFlip = ({ width, height, ...rest }) => ({ ...rest, width: height + SubGraphSpacing, height: width + SubGraphSpacing });
198
+ const adjustDimensionPadding = isVertical ? adjustDimensionPaddingNormal : adjustDimensionPaddingFlip;
199
+ const multiGraphDimensions = multiNodeGraphs.map(getGraphDimensions).map(adjustDimensionPadding);
200
+ const singleGraphDimensions = singleNodeGraphs.map(getGraphDimensions).map(adjustDimensionPadding);
201
+ const bins = multiGraphDimensions.concat(singleGraphDimensions);
202
+ binPack(bins, { inPlace: true });
203
+ const halfSpacing = Math.floor(SubGraphSpacing / 2);
204
+ const xProp = isVertical ? 'x' : 'y';
205
+ const yProp = isVertical ? 'y' : 'x';
206
+ if (!isNatural) {
207
+ const positionProp = isVertical ? 'y' : 'x';
208
+ const extentProp = isVertical ? 'height' : 'width';
209
+ const min = bins.reduce((min, d) => min === null ? d[positionProp] : Math.min(d[positionProp], min[extentProp] || 0), null);
210
+ const max = bins.reduce((max, d) => max === null ? (d[positionProp] + d[extentProp]) : Math.max((d[positionProp] + d[extentProp]), max[extentProp] || 0), null);
211
+ bins.forEach(d => {
212
+ d[positionProp] = min + (max - (d[positionProp] + d[extentProp]));
213
+ });
214
+ }
215
+ const assignPositions = (subGraph, dimensions) => {
216
+ for (const id of subGraph.nodes()) {
217
+ const sn = subGraph.node(id);
218
+ const n = g.node(id);
219
+ n.x = sn.x - dimensions.xOffset + dimensions[xProp] + halfSpacing;
220
+ n.y = sn.y - dimensions.yOffset + dimensions[yProp] + halfSpacing;
221
+ }
222
+ };
223
+ for (let i = 0; i < multiNodeGraphs.length; i++) {
224
+ const subGraph = multiNodeGraphs[i];
225
+ const dimensions = multiGraphDimensions[i];
226
+ assignPositions(subGraph, dimensions);
227
+ }
228
+ for (let i = 0; i < singleNodeGraphs.length; i++) {
229
+ const subGraph = singleNodeGraphs[i];
230
+ const dimensions = singleGraphDimensions[i];
231
+ assignPositions(subGraph, dimensions);
232
+ }
233
+ }
234
+ else {
235
+ multiNodeGraphs.sort(isNatural ? (a, b) => b.nodeCount() - a.nodeCount() : (a, b) => a.nodeCount() - b.nodeCount());
236
+ const multiGraphDimensions = multiNodeGraphs.map(getGraphDimensions);
237
+ const singleNodesAcc = singleNodeGraphs.reduce((acc, subGraph) => acc + g.node(subGraph.nodes()[0]).width, 0);
238
+ const singleNodesMaxSize = singleNodeGraphs.reduce((maxSize, subGraph) => Math.max(maxSize, g.node(subGraph.nodes()[0]).width), 0);
239
+ const singleNodesSize = singleNodeGraphs.length > 0 ? singleNodesAcc + (singleNodeGraphs.length - 1) * SubGraphSpacing : 0;
240
+ const maxSubGraphWidth = multiGraphDimensions.reduce((maxWidth, { width }) => Math.max(maxWidth, width), 0);
241
+ const graphWidth = Math.max(maxSubGraphWidth, singleNodesSize);
242
+ const maxSubGraphHeight = multiGraphDimensions.reduce((maxHeight, { height }) => Math.max(maxHeight, height), 0);
243
+ const graphHeight = Math.max(maxSubGraphHeight, singleNodesSize);
244
+ let position = 0;
245
+ const positionMultiNodeGraphs = () => {
246
+ for (let i = 0; i < multiNodeGraphs.length; i++) {
247
+ const subGraph = multiNodeGraphs[i];
248
+ const dimensions = multiGraphDimensions[i];
249
+ const centerOffset = isVertical ? Math.floor((graphWidth - dimensions.width) / 2.0) : Math.floor((graphHeight - dimensions.height) / 2.0);
250
+ for (const id of subGraph.nodes()) {
251
+ const sn = subGraph.node(id);
252
+ const n = g.node(id);
253
+ if (isVertical) {
254
+ n.x = sn.x - dimensions.minX + centerOffset;
255
+ n.y = sn.y - dimensions.minY + position;
256
+ }
257
+ else {
258
+ n.x = sn.x - dimensions.minX + position;
259
+ n.y = sn.y - dimensions.minY + centerOffset;
260
+ }
261
+ }
262
+ for (const id of subGraph.edges()) {
263
+ const sedge = subGraph.edge(id);
264
+ const edge = g.edge(id);
265
+ if (sedge.points && sedge.points.length > 3) {
266
+ edge.points = sedge.points.map(({ x, y }) => ({
267
+ x: x - dimensions.minX + (isVertical ? centerOffset : position),
268
+ y: y - dimensions.minY + (isVertical ? position : centerOffset)
269
+ }));
270
+ }
271
+ }
272
+ position += (isVertical ? dimensions.height : dimensions.width) + SubGraphSpacing;
273
+ }
274
+ };
275
+ const positionSingleNodeGraphs = () => {
276
+ const singleCenterOffset = Math.floor(((isVertical ? graphWidth : graphHeight) - singleNodesSize) / 2.0);
277
+ position += Math.floor(singleNodesMaxSize / 2.0);
278
+ let singlePosition = singleCenterOffset;
279
+ for (const subGraph of singleNodeGraphs) {
280
+ const id = subGraph.nodes()[0];
281
+ const n = g.node(id);
282
+ if (isVertical) {
283
+ n.x = singlePosition + Math.floor(n.width / 2.0);
284
+ n.y = position;
285
+ }
286
+ else {
287
+ n.x = position;
288
+ n.y = singlePosition + Math.floor(n.width / 2.0);
289
+ }
290
+ singlePosition += SubGraphSpacing + n.width;
291
+ }
292
+ position = singleNodesMaxSize + SubGraphSpacing;
293
+ };
294
+ if (isNatural) {
295
+ positionMultiNodeGraphs();
296
+ positionSingleNodeGraphs();
297
+ }
298
+ else {
299
+ positionSingleNodeGraphs();
300
+ positionMultiNodeGraphs();
301
+ }
302
+ }
303
+ }
304
+ else {
305
+ layoutGraph(g, direction, parents);
306
+ }
307
+ positionSum.x = 0;
308
+ positionSum.y = 0;
309
+ const positions = {};
310
+ for (const id of g.nodes()) {
311
+ const n = g.node(id);
312
+ positionSum.x += n.x || 0;
313
+ positionSum.y += n.y || 0;
314
+ positions[id] = { x: n.x, y: n.y };
315
+ }
316
+ const newNodeCenterPoint = numNodes ? [positionSum.x / numNodes, positionSum.y / numNodes] : [0, 0];
317
+ const translateX = prevNodeCenterPoint[0] - newNodeCenterPoint[0];
318
+ const translateY = prevNodeCenterPoint[1] - newNodeCenterPoint[1];
319
+ for (const key in positions) {
320
+ positions[key].x += translateX;
321
+ positions[key].y += translateY;
322
+ }
323
+ const waypoints = {};
324
+ for (const id of g.edges()) {
325
+ const e = g.edge(id);
326
+ if (e.points && e.points.length > 3) {
327
+ const mergedPoints = mergeStraightPoints(e.points);
328
+ for (const p of mergedPoints) {
329
+ p.x += translateX;
330
+ p.y += translateY;
331
+ }
332
+ waypoints[`${id.v}-${id.w}`] = {
333
+ points: [...mergedPoints],
334
+ from: {
335
+ x: positions[id.v].x,
336
+ y: positions[id.v].y
337
+ },
338
+ to: {
339
+ x: positions[id.w].x,
340
+ y: positions[id.w].y
341
+ }
342
+ };
343
+ waypoints[`${id.w}-${id.v}`] = {
344
+ points: mergedPoints.reverse(),
345
+ from: {
346
+ x: positions[id.w].x,
347
+ y: positions[id.w].y
348
+ },
349
+ to: {
350
+ x: positions[id.v].x,
351
+ y: positions[id.v].y
352
+ }
353
+ };
354
+ }
355
+ }
356
+ return {
357
+ positions,
358
+ parents,
359
+ waypoints
360
+ };
361
+ };
@@ -0,0 +1 @@
1
+ export function createHierarchicalLayoutWorker(): Worker;
@@ -0,0 +1 @@
1
+ export const createHierarchicalLayoutWorker = () => new Worker(new URL('./HierarchicalLayout.worker.js', import.meta.url), { type: 'module', name: 'HierarchicalLayout' });
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { createCoseBilkentLayoutWorker } from './cosebilkent-layout/workerFactory.js';
2
+ import { createHierarchicalLayoutWorker } from './hierarchical-layout/workerFactory.js';
3
+ export { createCoseBilkentLayoutWorker, createHierarchicalLayoutWorker };
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createCoseBilkentLayoutWorker } from './cosebilkent-layout/workerFactory.js';
2
+ import { createHierarchicalLayoutWorker } from './hierarchical-layout/workerFactory.js';
3
+ export { createCoseBilkentLayoutWorker, createHierarchicalLayoutWorker };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@neo4j-nvl/layout-workers",
3
+ "version": "0.1.0",
4
+ "main": "lib/index.js",
5
+ "type": "module",
6
+ "files": [
7
+ "lib"
8
+ ],
9
+ "scripts": {
10
+ "prebuild": "rm -rf lib/",
11
+ "build": "tsc",
12
+ "test": "echo 'not implemented'"
13
+ },
14
+ "typedoc": {
15
+ "entryPoint": "./src/index.js",
16
+ "readmeFile": "./README.md",
17
+ "displayName": "Layout Workers",
18
+ "tsconfig": "./tsconfig.json"
19
+ },
20
+ "engines": {
21
+ "yarn": "^1.10.1"
22
+ },
23
+ "dependencies": {
24
+ "@neo4j-bloom/dagre": "^0.8.14",
25
+ "bin-pack": "^1.0.2",
26
+ "cytoscape": "^3.23.0",
27
+ "cytoscape-cose-bilkent": "^4.1.0",
28
+ "graphlib": "^2.1.8"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^18.11.11",
32
+ "typedoc": "^0.23.26"
33
+ }
34
+ }