@vunk/graph 0.0.1
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/api/index.d.ts +0 -0
- package/api/index.mjs +1 -0
- package/api/user/index.d.ts +3 -0
- package/api/user/index.mjs +18 -0
- package/api/user/types.d.ts +5 -0
- package/components/_plugin-vue_export-helper.mjs +9 -0
- package/components/context-menu/index.css +1 -0
- package/components/context-menu/index.d.ts +2 -0
- package/components/context-menu/index.mjs +1797 -0
- package/components/graph/index.css +5 -0
- package/components/graph/index.d.ts +6 -0
- package/components/graph/index.mjs +110 -0
- package/components/graph/src/ctx.d.ts +12 -0
- package/components/graph/src/index.vue.d.ts +74 -0
- package/components/graph/src/types.d.ts +7 -0
- package/components/graph/src/useGraph.d.ts +3 -0
- package/components/graph/src/useSigma.d.ts +3 -0
- package/components/graph-view-ui/index.css +8 -0
- package/components/graph-view-ui/index.d.ts +4 -0
- package/components/graph-view-ui/index.mjs +81 -0
- package/components/graph-view-ui/src/ctx.d.ts +11 -0
- package/components/graph-view-ui/src/index.vue.d.ts +17 -0
- package/components/graph-view-ui/src/types.d.ts +1 -0
- package/components/hover-edge/index.d.ts +4 -0
- package/components/hover-edge/index.mjs +179 -0
- package/components/hover-edge/src/ctx.d.ts +42 -0
- package/components/hover-edge/src/index.vue.d.ts +194 -0
- package/components/hover-edge/src/types.d.ts +4 -0
- package/components/hover-highlight/index.d.ts +4 -0
- package/components/hover-highlight/index.mjs +133 -0
- package/components/hover-highlight/src/ctx.d.ts +33 -0
- package/components/hover-highlight/src/index.vue.d.ts +45 -0
- package/components/hover-highlight/src/types.d.ts +1 -0
- package/components/hover-node/index.d.ts +4 -0
- package/components/hover-node/index.mjs +146 -0
- package/components/hover-node/src/ctx.d.ts +35 -0
- package/components/hover-node/src/index.vue.d.ts +148 -0
- package/components/hover-node/src/types.d.ts +4 -0
- package/components/label/index.css +30 -0
- package/components/label/index.d.ts +6 -0
- package/components/label/index.mjs +145 -0
- package/components/label/src/core.d.ts +5 -0
- package/components/label/src/ctx.d.ts +24 -0
- package/components/label/src/index.vue.d.ts +33 -0
- package/components/label/src/types.d.ts +1 -0
- package/components/label/src/view.vue.d.ts +28 -0
- package/components/layout-forceatlas2/index.d.ts +5 -0
- package/components/layout-forceatlas2/index.mjs +1617 -0
- package/components/layout-forceatlas2/src/ctx.d.ts +9 -0
- package/components/layout-forceatlas2/src/index.vue.d.ts +14 -0
- package/components/layout-forceatlas2/src/types.d.ts +1 -0
- package/components/layout-forceatlas2/src/use.d.ts +16 -0
- package/components/link-guide/index.css +9 -0
- package/components/link-guide/index.d.ts +4 -0
- package/components/link-guide/index.mjs +118 -0
- package/components/link-guide/src/ctx.d.ts +11 -0
- package/components/link-guide/src/index.vue.d.ts +27 -0
- package/components/link-guide/src/types.d.ts +1 -0
- package/components/popup/index.css +32 -0
- package/components/popup/index.d.ts +5 -0
- package/components/popup/index.mjs +136 -0
- package/components/popup/src/core.d.ts +24 -0
- package/components/popup/src/ctx.d.ts +16 -0
- package/components/popup/src/index.vue.d.ts +73 -0
- package/components/popup/src/provider.vue.d.ts +46 -0
- package/components/popup/src/types.d.ts +1 -0
- package/components/search/index.d.ts +4 -0
- package/components/search/index.mjs +218 -0
- package/components/search/src/ctx.d.ts +53 -0
- package/components/search/src/index.vue.d.ts +83 -0
- package/components/search/src/types.d.ts +16 -0
- package/composables/index.d.ts +1 -0
- package/composables/index.mjs +1 -0
- package/index.css +90 -0
- package/index.d.ts +10 -0
- package/index.esm.js +10 -0
- package/package.json +108 -0
- package/shared/graph/index.d.ts +17 -0
- package/shared/graph/index.mjs +19 -0
- package/shared/index.d.ts +0 -0
- package/shared/index.mjs +1 -0
- package/stores/index.d.ts +0 -0
- package/stores/index.mjs +1 -0
|
@@ -0,0 +1,1617 @@
|
|
|
1
|
+
import { shallowRef, onBeforeUnmount, provide, inject, defineComponent, renderSlot } from 'vue';
|
|
2
|
+
import { useGraph } from '@vunk/graph/composables';
|
|
3
|
+
import forceAtlas2 from 'graphology-layout-forceatlas2';
|
|
4
|
+
import { _ as _export_sfc } from '../_plugin-vue_export-helper.mjs';
|
|
5
|
+
|
|
6
|
+
const props = {
|
|
7
|
+
settings: {
|
|
8
|
+
type: Object,
|
|
9
|
+
default: () => ({})
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function getDefaultExportFromCjs (x) {
|
|
14
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Graphology ForceAtlas2 Layout Webworker
|
|
19
|
+
* ========================================
|
|
20
|
+
*
|
|
21
|
+
* Web worker able to run the layout in a separate thread.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
var webworker;
|
|
25
|
+
var hasRequiredWebworker;
|
|
26
|
+
|
|
27
|
+
function requireWebworker () {
|
|
28
|
+
if (hasRequiredWebworker) return webworker;
|
|
29
|
+
hasRequiredWebworker = 1;
|
|
30
|
+
webworker = function worker() {
|
|
31
|
+
var NODES, EDGES;
|
|
32
|
+
|
|
33
|
+
var moduleShim = {};
|
|
34
|
+
|
|
35
|
+
(function () {
|
|
36
|
+
/* eslint no-constant-condition: 0 */
|
|
37
|
+
/**
|
|
38
|
+
* Graphology ForceAtlas2 Iteration
|
|
39
|
+
* =================================
|
|
40
|
+
*
|
|
41
|
+
* Function used to perform a single iteration of the algorithm.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Matrices properties accessors.
|
|
46
|
+
*/
|
|
47
|
+
var NODE_X = 0;
|
|
48
|
+
var NODE_Y = 1;
|
|
49
|
+
var NODE_DX = 2;
|
|
50
|
+
var NODE_DY = 3;
|
|
51
|
+
var NODE_OLD_DX = 4;
|
|
52
|
+
var NODE_OLD_DY = 5;
|
|
53
|
+
var NODE_MASS = 6;
|
|
54
|
+
var NODE_CONVERGENCE = 7;
|
|
55
|
+
var NODE_SIZE = 8;
|
|
56
|
+
var NODE_FIXED = 9;
|
|
57
|
+
|
|
58
|
+
var EDGE_SOURCE = 0;
|
|
59
|
+
var EDGE_TARGET = 1;
|
|
60
|
+
var EDGE_WEIGHT = 2;
|
|
61
|
+
|
|
62
|
+
var REGION_NODE = 0;
|
|
63
|
+
var REGION_CENTER_X = 1;
|
|
64
|
+
var REGION_CENTER_Y = 2;
|
|
65
|
+
var REGION_SIZE = 3;
|
|
66
|
+
var REGION_NEXT_SIBLING = 4;
|
|
67
|
+
var REGION_FIRST_CHILD = 5;
|
|
68
|
+
var REGION_MASS = 6;
|
|
69
|
+
var REGION_MASS_CENTER_X = 7;
|
|
70
|
+
var REGION_MASS_CENTER_Y = 8;
|
|
71
|
+
|
|
72
|
+
var SUBDIVISION_ATTEMPTS = 3;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Constants.
|
|
76
|
+
*/
|
|
77
|
+
var PPN = 10;
|
|
78
|
+
var PPE = 3;
|
|
79
|
+
var PPR = 9;
|
|
80
|
+
|
|
81
|
+
var MAX_FORCE = 10;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Function used to perform a single interation of the algorithm.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} options - Layout options.
|
|
87
|
+
* @param {Float32Array} NodeMatrix - Node data.
|
|
88
|
+
* @param {Float32Array} EdgeMatrix - Edge data.
|
|
89
|
+
* @return {object} - Some metadata.
|
|
90
|
+
*/
|
|
91
|
+
moduleShim.exports = function iterate(options, NodeMatrix, EdgeMatrix) {
|
|
92
|
+
// Initializing variables
|
|
93
|
+
var l, r, n, n1, n2, rn, e, w, g, s;
|
|
94
|
+
|
|
95
|
+
var order = NodeMatrix.length,
|
|
96
|
+
size = EdgeMatrix.length;
|
|
97
|
+
|
|
98
|
+
var adjustSizes = options.adjustSizes;
|
|
99
|
+
|
|
100
|
+
var thetaSquared = options.barnesHutTheta * options.barnesHutTheta;
|
|
101
|
+
|
|
102
|
+
var outboundAttCompensation, coefficient, xDist, yDist, ewc, distance, factor;
|
|
103
|
+
|
|
104
|
+
var RegionMatrix = [];
|
|
105
|
+
|
|
106
|
+
// 1) Initializing layout data
|
|
107
|
+
//-----------------------------
|
|
108
|
+
|
|
109
|
+
// Resetting positions & computing max values
|
|
110
|
+
for (n = 0; n < order; n += PPN) {
|
|
111
|
+
NodeMatrix[n + NODE_OLD_DX] = NodeMatrix[n + NODE_DX];
|
|
112
|
+
NodeMatrix[n + NODE_OLD_DY] = NodeMatrix[n + NODE_DY];
|
|
113
|
+
NodeMatrix[n + NODE_DX] = 0;
|
|
114
|
+
NodeMatrix[n + NODE_DY] = 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If outbound attraction distribution, compensate
|
|
118
|
+
if (options.outboundAttractionDistribution) {
|
|
119
|
+
outboundAttCompensation = 0;
|
|
120
|
+
for (n = 0; n < order; n += PPN) {
|
|
121
|
+
outboundAttCompensation += NodeMatrix[n + NODE_MASS];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
outboundAttCompensation /= order / PPN;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 1.bis) Barnes-Hut computation
|
|
128
|
+
//------------------------------
|
|
129
|
+
|
|
130
|
+
if (options.barnesHutOptimize) {
|
|
131
|
+
// Setting up
|
|
132
|
+
var minX = Infinity,
|
|
133
|
+
maxX = -Infinity,
|
|
134
|
+
minY = Infinity,
|
|
135
|
+
maxY = -Infinity,
|
|
136
|
+
q,
|
|
137
|
+
q2,
|
|
138
|
+
subdivisionAttempts;
|
|
139
|
+
|
|
140
|
+
// Computing min and max values
|
|
141
|
+
for (n = 0; n < order; n += PPN) {
|
|
142
|
+
minX = Math.min(minX, NodeMatrix[n + NODE_X]);
|
|
143
|
+
maxX = Math.max(maxX, NodeMatrix[n + NODE_X]);
|
|
144
|
+
minY = Math.min(minY, NodeMatrix[n + NODE_Y]);
|
|
145
|
+
maxY = Math.max(maxY, NodeMatrix[n + NODE_Y]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// squarify bounds, it's a quadtree
|
|
149
|
+
var dx = maxX - minX,
|
|
150
|
+
dy = maxY - minY;
|
|
151
|
+
if (dx > dy) {
|
|
152
|
+
minY -= (dx - dy) / 2;
|
|
153
|
+
maxY = minY + dx;
|
|
154
|
+
} else {
|
|
155
|
+
minX -= (dy - dx) / 2;
|
|
156
|
+
maxX = minX + dy;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Build the Barnes Hut root region
|
|
160
|
+
RegionMatrix[0 + REGION_NODE] = -1;
|
|
161
|
+
RegionMatrix[0 + REGION_CENTER_X] = (minX + maxX) / 2;
|
|
162
|
+
RegionMatrix[0 + REGION_CENTER_Y] = (minY + maxY) / 2;
|
|
163
|
+
RegionMatrix[0 + REGION_SIZE] = Math.max(maxX - minX, maxY - minY);
|
|
164
|
+
RegionMatrix[0 + REGION_NEXT_SIBLING] = -1;
|
|
165
|
+
RegionMatrix[0 + REGION_FIRST_CHILD] = -1;
|
|
166
|
+
RegionMatrix[0 + REGION_MASS] = 0;
|
|
167
|
+
RegionMatrix[0 + REGION_MASS_CENTER_X] = 0;
|
|
168
|
+
RegionMatrix[0 + REGION_MASS_CENTER_Y] = 0;
|
|
169
|
+
|
|
170
|
+
// Add each node in the tree
|
|
171
|
+
l = 1;
|
|
172
|
+
for (n = 0; n < order; n += PPN) {
|
|
173
|
+
// Current region, starting with root
|
|
174
|
+
r = 0;
|
|
175
|
+
subdivisionAttempts = SUBDIVISION_ATTEMPTS;
|
|
176
|
+
|
|
177
|
+
while (true) {
|
|
178
|
+
// Are there sub-regions?
|
|
179
|
+
|
|
180
|
+
// We look at first child index
|
|
181
|
+
if (RegionMatrix[r + REGION_FIRST_CHILD] >= 0) {
|
|
182
|
+
// There are sub-regions
|
|
183
|
+
|
|
184
|
+
// We just iterate to find a "leaf" of the tree
|
|
185
|
+
// that is an empty region or a region with a single node
|
|
186
|
+
// (see next case)
|
|
187
|
+
|
|
188
|
+
// Find the quadrant of n
|
|
189
|
+
if (NodeMatrix[n + NODE_X] < RegionMatrix[r + REGION_CENTER_X]) {
|
|
190
|
+
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
|
|
191
|
+
// Top Left quarter
|
|
192
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD];
|
|
193
|
+
} else {
|
|
194
|
+
// Bottom Left quarter
|
|
195
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
|
|
199
|
+
// Top Right quarter
|
|
200
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
|
|
201
|
+
} else {
|
|
202
|
+
// Bottom Right quarter
|
|
203
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Update center of mass and mass (we only do it for non-leave regions)
|
|
208
|
+
RegionMatrix[r + REGION_MASS_CENTER_X] =
|
|
209
|
+
(RegionMatrix[r + REGION_MASS_CENTER_X] *
|
|
210
|
+
RegionMatrix[r + REGION_MASS] +
|
|
211
|
+
NodeMatrix[n + NODE_X] * NodeMatrix[n + NODE_MASS]) /
|
|
212
|
+
(RegionMatrix[r + REGION_MASS] + NodeMatrix[n + NODE_MASS]);
|
|
213
|
+
|
|
214
|
+
RegionMatrix[r + REGION_MASS_CENTER_Y] =
|
|
215
|
+
(RegionMatrix[r + REGION_MASS_CENTER_Y] *
|
|
216
|
+
RegionMatrix[r + REGION_MASS] +
|
|
217
|
+
NodeMatrix[n + NODE_Y] * NodeMatrix[n + NODE_MASS]) /
|
|
218
|
+
(RegionMatrix[r + REGION_MASS] + NodeMatrix[n + NODE_MASS]);
|
|
219
|
+
|
|
220
|
+
RegionMatrix[r + REGION_MASS] += NodeMatrix[n + NODE_MASS];
|
|
221
|
+
|
|
222
|
+
// Iterate on the right quadrant
|
|
223
|
+
r = q;
|
|
224
|
+
continue;
|
|
225
|
+
} else {
|
|
226
|
+
// There are no sub-regions: we are in a "leaf"
|
|
227
|
+
|
|
228
|
+
// Is there a node in this leave?
|
|
229
|
+
if (RegionMatrix[r + REGION_NODE] < 0) {
|
|
230
|
+
// There is no node in region:
|
|
231
|
+
// we record node n and go on
|
|
232
|
+
RegionMatrix[r + REGION_NODE] = n;
|
|
233
|
+
break;
|
|
234
|
+
} else {
|
|
235
|
+
// There is a node in this region
|
|
236
|
+
|
|
237
|
+
// We will need to create sub-regions, stick the two
|
|
238
|
+
// nodes (the old one r[0] and the new one n) in two
|
|
239
|
+
// subregions. If they fall in the same quadrant,
|
|
240
|
+
// we will iterate.
|
|
241
|
+
|
|
242
|
+
// Create sub-regions
|
|
243
|
+
RegionMatrix[r + REGION_FIRST_CHILD] = l * PPR;
|
|
244
|
+
w = RegionMatrix[r + REGION_SIZE] / 2; // new size (half)
|
|
245
|
+
|
|
246
|
+
// NOTE: we use screen coordinates
|
|
247
|
+
// from Top Left to Bottom Right
|
|
248
|
+
|
|
249
|
+
// Top Left sub-region
|
|
250
|
+
g = RegionMatrix[r + REGION_FIRST_CHILD];
|
|
251
|
+
|
|
252
|
+
RegionMatrix[g + REGION_NODE] = -1;
|
|
253
|
+
RegionMatrix[g + REGION_CENTER_X] =
|
|
254
|
+
RegionMatrix[r + REGION_CENTER_X] - w;
|
|
255
|
+
RegionMatrix[g + REGION_CENTER_Y] =
|
|
256
|
+
RegionMatrix[r + REGION_CENTER_Y] - w;
|
|
257
|
+
RegionMatrix[g + REGION_SIZE] = w;
|
|
258
|
+
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
|
|
259
|
+
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
|
|
260
|
+
RegionMatrix[g + REGION_MASS] = 0;
|
|
261
|
+
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
|
|
262
|
+
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
|
|
263
|
+
|
|
264
|
+
// Bottom Left sub-region
|
|
265
|
+
g += PPR;
|
|
266
|
+
RegionMatrix[g + REGION_NODE] = -1;
|
|
267
|
+
RegionMatrix[g + REGION_CENTER_X] =
|
|
268
|
+
RegionMatrix[r + REGION_CENTER_X] - w;
|
|
269
|
+
RegionMatrix[g + REGION_CENTER_Y] =
|
|
270
|
+
RegionMatrix[r + REGION_CENTER_Y] + w;
|
|
271
|
+
RegionMatrix[g + REGION_SIZE] = w;
|
|
272
|
+
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
|
|
273
|
+
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
|
|
274
|
+
RegionMatrix[g + REGION_MASS] = 0;
|
|
275
|
+
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
|
|
276
|
+
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
|
|
277
|
+
|
|
278
|
+
// Top Right sub-region
|
|
279
|
+
g += PPR;
|
|
280
|
+
RegionMatrix[g + REGION_NODE] = -1;
|
|
281
|
+
RegionMatrix[g + REGION_CENTER_X] =
|
|
282
|
+
RegionMatrix[r + REGION_CENTER_X] + w;
|
|
283
|
+
RegionMatrix[g + REGION_CENTER_Y] =
|
|
284
|
+
RegionMatrix[r + REGION_CENTER_Y] - w;
|
|
285
|
+
RegionMatrix[g + REGION_SIZE] = w;
|
|
286
|
+
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
|
|
287
|
+
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
|
|
288
|
+
RegionMatrix[g + REGION_MASS] = 0;
|
|
289
|
+
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
|
|
290
|
+
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
|
|
291
|
+
|
|
292
|
+
// Bottom Right sub-region
|
|
293
|
+
g += PPR;
|
|
294
|
+
RegionMatrix[g + REGION_NODE] = -1;
|
|
295
|
+
RegionMatrix[g + REGION_CENTER_X] =
|
|
296
|
+
RegionMatrix[r + REGION_CENTER_X] + w;
|
|
297
|
+
RegionMatrix[g + REGION_CENTER_Y] =
|
|
298
|
+
RegionMatrix[r + REGION_CENTER_Y] + w;
|
|
299
|
+
RegionMatrix[g + REGION_SIZE] = w;
|
|
300
|
+
RegionMatrix[g + REGION_NEXT_SIBLING] =
|
|
301
|
+
RegionMatrix[r + REGION_NEXT_SIBLING];
|
|
302
|
+
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
|
|
303
|
+
RegionMatrix[g + REGION_MASS] = 0;
|
|
304
|
+
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
|
|
305
|
+
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
|
|
306
|
+
|
|
307
|
+
l += 4;
|
|
308
|
+
|
|
309
|
+
// Now the goal is to find two different sub-regions
|
|
310
|
+
// for the two nodes: the one previously recorded (r[0])
|
|
311
|
+
// and the one we want to add (n)
|
|
312
|
+
|
|
313
|
+
// Find the quadrant of the old node
|
|
314
|
+
if (
|
|
315
|
+
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_X] <
|
|
316
|
+
RegionMatrix[r + REGION_CENTER_X]
|
|
317
|
+
) {
|
|
318
|
+
if (
|
|
319
|
+
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y] <
|
|
320
|
+
RegionMatrix[r + REGION_CENTER_Y]
|
|
321
|
+
) {
|
|
322
|
+
// Top Left quarter
|
|
323
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD];
|
|
324
|
+
} else {
|
|
325
|
+
// Bottom Left quarter
|
|
326
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
if (
|
|
330
|
+
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y] <
|
|
331
|
+
RegionMatrix[r + REGION_CENTER_Y]
|
|
332
|
+
) {
|
|
333
|
+
// Top Right quarter
|
|
334
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
|
|
335
|
+
} else {
|
|
336
|
+
// Bottom Right quarter
|
|
337
|
+
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// We remove r[0] from the region r, add its mass to r and record it in q
|
|
342
|
+
RegionMatrix[r + REGION_MASS] =
|
|
343
|
+
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_MASS];
|
|
344
|
+
RegionMatrix[r + REGION_MASS_CENTER_X] =
|
|
345
|
+
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_X];
|
|
346
|
+
RegionMatrix[r + REGION_MASS_CENTER_Y] =
|
|
347
|
+
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y];
|
|
348
|
+
|
|
349
|
+
RegionMatrix[q + REGION_NODE] = RegionMatrix[r + REGION_NODE];
|
|
350
|
+
RegionMatrix[r + REGION_NODE] = -1;
|
|
351
|
+
|
|
352
|
+
// Find the quadrant of n
|
|
353
|
+
if (NodeMatrix[n + NODE_X] < RegionMatrix[r + REGION_CENTER_X]) {
|
|
354
|
+
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
|
|
355
|
+
// Top Left quarter
|
|
356
|
+
q2 = RegionMatrix[r + REGION_FIRST_CHILD];
|
|
357
|
+
} else {
|
|
358
|
+
// Bottom Left quarter
|
|
359
|
+
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
|
|
363
|
+
// Top Right quarter
|
|
364
|
+
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
|
|
365
|
+
} else {
|
|
366
|
+
// Bottom Right quarter
|
|
367
|
+
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (q === q2) {
|
|
372
|
+
// If both nodes are in the same quadrant,
|
|
373
|
+
// we have to try it again on this quadrant
|
|
374
|
+
if (subdivisionAttempts--) {
|
|
375
|
+
r = q;
|
|
376
|
+
continue; // while
|
|
377
|
+
} else {
|
|
378
|
+
// we are out of precision here, and we cannot subdivide anymore
|
|
379
|
+
// but we have to break the loop anyway
|
|
380
|
+
subdivisionAttempts = SUBDIVISION_ATTEMPTS;
|
|
381
|
+
break; // while
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// If both quadrants are different, we record n
|
|
386
|
+
// in its quadrant
|
|
387
|
+
RegionMatrix[q2 + REGION_NODE] = n;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 2) Repulsion
|
|
396
|
+
//--------------
|
|
397
|
+
// NOTES: adjustSizes = antiCollision & scalingRatio = coefficient
|
|
398
|
+
|
|
399
|
+
if (options.barnesHutOptimize) {
|
|
400
|
+
coefficient = options.scalingRatio;
|
|
401
|
+
|
|
402
|
+
// Applying repulsion through regions
|
|
403
|
+
for (n = 0; n < order; n += PPN) {
|
|
404
|
+
// Computing leaf quad nodes iteration
|
|
405
|
+
|
|
406
|
+
r = 0; // Starting with root region
|
|
407
|
+
while (true) {
|
|
408
|
+
if (RegionMatrix[r + REGION_FIRST_CHILD] >= 0) {
|
|
409
|
+
// The region has sub-regions
|
|
410
|
+
|
|
411
|
+
// We run the Barnes Hut test to see if we are at the right distance
|
|
412
|
+
distance =
|
|
413
|
+
Math.pow(
|
|
414
|
+
NodeMatrix[n + NODE_X] - RegionMatrix[r + REGION_MASS_CENTER_X],
|
|
415
|
+
2
|
|
416
|
+
) +
|
|
417
|
+
Math.pow(
|
|
418
|
+
NodeMatrix[n + NODE_Y] - RegionMatrix[r + REGION_MASS_CENTER_Y],
|
|
419
|
+
2
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
s = RegionMatrix[r + REGION_SIZE];
|
|
423
|
+
|
|
424
|
+
if ((4 * s * s) / distance < thetaSquared) {
|
|
425
|
+
// We treat the region as a single body, and we repulse
|
|
426
|
+
|
|
427
|
+
xDist =
|
|
428
|
+
NodeMatrix[n + NODE_X] - RegionMatrix[r + REGION_MASS_CENTER_X];
|
|
429
|
+
yDist =
|
|
430
|
+
NodeMatrix[n + NODE_Y] - RegionMatrix[r + REGION_MASS_CENTER_Y];
|
|
431
|
+
|
|
432
|
+
if (adjustSizes === true) {
|
|
433
|
+
//-- Linear Anti-collision Repulsion
|
|
434
|
+
if (distance > 0) {
|
|
435
|
+
factor =
|
|
436
|
+
(coefficient *
|
|
437
|
+
NodeMatrix[n + NODE_MASS] *
|
|
438
|
+
RegionMatrix[r + REGION_MASS]) /
|
|
439
|
+
distance;
|
|
440
|
+
|
|
441
|
+
NodeMatrix[n + NODE_DX] += xDist * factor;
|
|
442
|
+
NodeMatrix[n + NODE_DY] += yDist * factor;
|
|
443
|
+
} else if (distance < 0) {
|
|
444
|
+
factor =
|
|
445
|
+
(-coefficient *
|
|
446
|
+
NodeMatrix[n + NODE_MASS] *
|
|
447
|
+
RegionMatrix[r + REGION_MASS]) /
|
|
448
|
+
Math.sqrt(distance);
|
|
449
|
+
|
|
450
|
+
NodeMatrix[n + NODE_DX] += xDist * factor;
|
|
451
|
+
NodeMatrix[n + NODE_DY] += yDist * factor;
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
//-- Linear Repulsion
|
|
455
|
+
if (distance > 0) {
|
|
456
|
+
factor =
|
|
457
|
+
(coefficient *
|
|
458
|
+
NodeMatrix[n + NODE_MASS] *
|
|
459
|
+
RegionMatrix[r + REGION_MASS]) /
|
|
460
|
+
distance;
|
|
461
|
+
|
|
462
|
+
NodeMatrix[n + NODE_DX] += xDist * factor;
|
|
463
|
+
NodeMatrix[n + NODE_DY] += yDist * factor;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// When this is done, we iterate. We have to look at the next sibling.
|
|
468
|
+
r = RegionMatrix[r + REGION_NEXT_SIBLING];
|
|
469
|
+
if (r < 0) break; // No next sibling: we have finished the tree
|
|
470
|
+
|
|
471
|
+
continue;
|
|
472
|
+
} else {
|
|
473
|
+
// The region is too close and we have to look at sub-regions
|
|
474
|
+
r = RegionMatrix[r + REGION_FIRST_CHILD];
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
} else {
|
|
478
|
+
// The region has no sub-region
|
|
479
|
+
// If there is a node r[0] and it is not n, then repulse
|
|
480
|
+
rn = RegionMatrix[r + REGION_NODE];
|
|
481
|
+
|
|
482
|
+
if (rn >= 0 && rn !== n) {
|
|
483
|
+
xDist = NodeMatrix[n + NODE_X] - NodeMatrix[rn + NODE_X];
|
|
484
|
+
yDist = NodeMatrix[n + NODE_Y] - NodeMatrix[rn + NODE_Y];
|
|
485
|
+
|
|
486
|
+
distance = xDist * xDist + yDist * yDist;
|
|
487
|
+
|
|
488
|
+
if (adjustSizes === true) {
|
|
489
|
+
//-- Linear Anti-collision Repulsion
|
|
490
|
+
if (distance > 0) {
|
|
491
|
+
factor =
|
|
492
|
+
(coefficient *
|
|
493
|
+
NodeMatrix[n + NODE_MASS] *
|
|
494
|
+
NodeMatrix[rn + NODE_MASS]) /
|
|
495
|
+
distance;
|
|
496
|
+
|
|
497
|
+
NodeMatrix[n + NODE_DX] += xDist * factor;
|
|
498
|
+
NodeMatrix[n + NODE_DY] += yDist * factor;
|
|
499
|
+
} else if (distance < 0) {
|
|
500
|
+
factor =
|
|
501
|
+
(-coefficient *
|
|
502
|
+
NodeMatrix[n + NODE_MASS] *
|
|
503
|
+
NodeMatrix[rn + NODE_MASS]) /
|
|
504
|
+
Math.sqrt(distance);
|
|
505
|
+
|
|
506
|
+
NodeMatrix[n + NODE_DX] += xDist * factor;
|
|
507
|
+
NodeMatrix[n + NODE_DY] += yDist * factor;
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
//-- Linear Repulsion
|
|
511
|
+
if (distance > 0) {
|
|
512
|
+
factor =
|
|
513
|
+
(coefficient *
|
|
514
|
+
NodeMatrix[n + NODE_MASS] *
|
|
515
|
+
NodeMatrix[rn + NODE_MASS]) /
|
|
516
|
+
distance;
|
|
517
|
+
|
|
518
|
+
NodeMatrix[n + NODE_DX] += xDist * factor;
|
|
519
|
+
NodeMatrix[n + NODE_DY] += yDist * factor;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// When this is done, we iterate. We have to look at the next sibling.
|
|
525
|
+
r = RegionMatrix[r + REGION_NEXT_SIBLING];
|
|
526
|
+
|
|
527
|
+
if (r < 0) break; // No next sibling: we have finished the tree
|
|
528
|
+
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
coefficient = options.scalingRatio;
|
|
535
|
+
|
|
536
|
+
// Square iteration
|
|
537
|
+
for (n1 = 0; n1 < order; n1 += PPN) {
|
|
538
|
+
for (n2 = 0; n2 < n1; n2 += PPN) {
|
|
539
|
+
// Common to both methods
|
|
540
|
+
xDist = NodeMatrix[n1 + NODE_X] - NodeMatrix[n2 + NODE_X];
|
|
541
|
+
yDist = NodeMatrix[n1 + NODE_Y] - NodeMatrix[n2 + NODE_Y];
|
|
542
|
+
|
|
543
|
+
if (adjustSizes === true) {
|
|
544
|
+
//-- Anticollision Linear Repulsion
|
|
545
|
+
distance =
|
|
546
|
+
Math.sqrt(xDist * xDist + yDist * yDist) -
|
|
547
|
+
NodeMatrix[n1 + NODE_SIZE] -
|
|
548
|
+
NodeMatrix[n2 + NODE_SIZE];
|
|
549
|
+
|
|
550
|
+
if (distance > 0) {
|
|
551
|
+
factor =
|
|
552
|
+
(coefficient *
|
|
553
|
+
NodeMatrix[n1 + NODE_MASS] *
|
|
554
|
+
NodeMatrix[n2 + NODE_MASS]) /
|
|
555
|
+
distance /
|
|
556
|
+
distance;
|
|
557
|
+
|
|
558
|
+
// Updating nodes' dx and dy
|
|
559
|
+
NodeMatrix[n1 + NODE_DX] += xDist * factor;
|
|
560
|
+
NodeMatrix[n1 + NODE_DY] += yDist * factor;
|
|
561
|
+
|
|
562
|
+
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
|
|
563
|
+
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
|
|
564
|
+
} else if (distance < 0) {
|
|
565
|
+
factor =
|
|
566
|
+
100 *
|
|
567
|
+
coefficient *
|
|
568
|
+
NodeMatrix[n1 + NODE_MASS] *
|
|
569
|
+
NodeMatrix[n2 + NODE_MASS];
|
|
570
|
+
|
|
571
|
+
// Updating nodes' dx and dy
|
|
572
|
+
NodeMatrix[n1 + NODE_DX] += xDist * factor;
|
|
573
|
+
NodeMatrix[n1 + NODE_DY] += yDist * factor;
|
|
574
|
+
|
|
575
|
+
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
|
|
576
|
+
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
//-- Linear Repulsion
|
|
580
|
+
distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
|
581
|
+
|
|
582
|
+
if (distance > 0) {
|
|
583
|
+
factor =
|
|
584
|
+
(coefficient *
|
|
585
|
+
NodeMatrix[n1 + NODE_MASS] *
|
|
586
|
+
NodeMatrix[n2 + NODE_MASS]) /
|
|
587
|
+
distance /
|
|
588
|
+
distance;
|
|
589
|
+
|
|
590
|
+
// Updating nodes' dx and dy
|
|
591
|
+
NodeMatrix[n1 + NODE_DX] += xDist * factor;
|
|
592
|
+
NodeMatrix[n1 + NODE_DY] += yDist * factor;
|
|
593
|
+
|
|
594
|
+
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
|
|
595
|
+
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// 3) Gravity
|
|
603
|
+
//------------
|
|
604
|
+
g = options.gravity / options.scalingRatio;
|
|
605
|
+
coefficient = options.scalingRatio;
|
|
606
|
+
for (n = 0; n < order; n += PPN) {
|
|
607
|
+
factor = 0;
|
|
608
|
+
|
|
609
|
+
// Common to both methods
|
|
610
|
+
xDist = NodeMatrix[n + NODE_X];
|
|
611
|
+
yDist = NodeMatrix[n + NODE_Y];
|
|
612
|
+
distance = Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
|
|
613
|
+
|
|
614
|
+
if (options.strongGravityMode) {
|
|
615
|
+
//-- Strong gravity
|
|
616
|
+
if (distance > 0) factor = coefficient * NodeMatrix[n + NODE_MASS] * g;
|
|
617
|
+
} else {
|
|
618
|
+
//-- Linear Anti-collision Repulsion n
|
|
619
|
+
if (distance > 0)
|
|
620
|
+
factor = (coefficient * NodeMatrix[n + NODE_MASS] * g) / distance;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Updating node's dx and dy
|
|
624
|
+
NodeMatrix[n + NODE_DX] -= xDist * factor;
|
|
625
|
+
NodeMatrix[n + NODE_DY] -= yDist * factor;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// 4) Attraction
|
|
629
|
+
//---------------
|
|
630
|
+
coefficient =
|
|
631
|
+
1 * (options.outboundAttractionDistribution ? outboundAttCompensation : 1);
|
|
632
|
+
|
|
633
|
+
// TODO: simplify distance
|
|
634
|
+
// TODO: coefficient is always used as -c --> optimize?
|
|
635
|
+
for (e = 0; e < size; e += PPE) {
|
|
636
|
+
n1 = EdgeMatrix[e + EDGE_SOURCE];
|
|
637
|
+
n2 = EdgeMatrix[e + EDGE_TARGET];
|
|
638
|
+
w = EdgeMatrix[e + EDGE_WEIGHT];
|
|
639
|
+
|
|
640
|
+
// Edge weight influence
|
|
641
|
+
ewc = Math.pow(w, options.edgeWeightInfluence);
|
|
642
|
+
|
|
643
|
+
// Common measures
|
|
644
|
+
xDist = NodeMatrix[n1 + NODE_X] - NodeMatrix[n2 + NODE_X];
|
|
645
|
+
yDist = NodeMatrix[n1 + NODE_Y] - NodeMatrix[n2 + NODE_Y];
|
|
646
|
+
|
|
647
|
+
// Applying attraction to nodes
|
|
648
|
+
if (adjustSizes === true) {
|
|
649
|
+
distance =
|
|
650
|
+
Math.sqrt(xDist * xDist + yDist * yDist) -
|
|
651
|
+
NodeMatrix[n1 + NODE_SIZE] -
|
|
652
|
+
NodeMatrix[n2 + NODE_SIZE];
|
|
653
|
+
|
|
654
|
+
if (options.linLogMode) {
|
|
655
|
+
if (options.outboundAttractionDistribution) {
|
|
656
|
+
//-- LinLog Degree Distributed Anti-collision Attraction
|
|
657
|
+
if (distance > 0) {
|
|
658
|
+
factor =
|
|
659
|
+
(-coefficient * ewc * Math.log(1 + distance)) /
|
|
660
|
+
distance /
|
|
661
|
+
NodeMatrix[n1 + NODE_MASS];
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
//-- LinLog Anti-collision Attraction
|
|
665
|
+
if (distance > 0) {
|
|
666
|
+
factor = (-coefficient * ewc * Math.log(1 + distance)) / distance;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
if (options.outboundAttractionDistribution) {
|
|
671
|
+
//-- Linear Degree Distributed Anti-collision Attraction
|
|
672
|
+
if (distance > 0) {
|
|
673
|
+
factor = (-coefficient * ewc) / NodeMatrix[n1 + NODE_MASS];
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
//-- Linear Anti-collision Attraction
|
|
677
|
+
if (distance > 0) {
|
|
678
|
+
factor = -coefficient * ewc;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
} else {
|
|
683
|
+
distance = Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
|
|
684
|
+
|
|
685
|
+
if (options.linLogMode) {
|
|
686
|
+
if (options.outboundAttractionDistribution) {
|
|
687
|
+
//-- LinLog Degree Distributed Attraction
|
|
688
|
+
if (distance > 0) {
|
|
689
|
+
factor =
|
|
690
|
+
(-coefficient * ewc * Math.log(1 + distance)) /
|
|
691
|
+
distance /
|
|
692
|
+
NodeMatrix[n1 + NODE_MASS];
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
//-- LinLog Attraction
|
|
696
|
+
if (distance > 0)
|
|
697
|
+
factor = (-coefficient * ewc * Math.log(1 + distance)) / distance;
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
if (options.outboundAttractionDistribution) {
|
|
701
|
+
//-- Linear Attraction Mass Distributed
|
|
702
|
+
// NOTE: Distance is set to 1 to override next condition
|
|
703
|
+
distance = 1;
|
|
704
|
+
factor = (-coefficient * ewc) / NodeMatrix[n1 + NODE_MASS];
|
|
705
|
+
} else {
|
|
706
|
+
//-- Linear Attraction
|
|
707
|
+
// NOTE: Distance is set to 1 to override next condition
|
|
708
|
+
distance = 1;
|
|
709
|
+
factor = -coefficient * ewc;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Updating nodes' dx and dy
|
|
715
|
+
// TODO: if condition or factor = 1?
|
|
716
|
+
if (distance > 0) {
|
|
717
|
+
// Updating nodes' dx and dy
|
|
718
|
+
NodeMatrix[n1 + NODE_DX] += xDist * factor;
|
|
719
|
+
NodeMatrix[n1 + NODE_DY] += yDist * factor;
|
|
720
|
+
|
|
721
|
+
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
|
|
722
|
+
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// 5) Apply Forces
|
|
727
|
+
//-----------------
|
|
728
|
+
var force, swinging, traction, nodespeed, newX, newY;
|
|
729
|
+
|
|
730
|
+
// MATH: sqrt and square distances
|
|
731
|
+
if (adjustSizes === true) {
|
|
732
|
+
for (n = 0; n < order; n += PPN) {
|
|
733
|
+
if (NodeMatrix[n + NODE_FIXED] !== 1) {
|
|
734
|
+
force = Math.sqrt(
|
|
735
|
+
Math.pow(NodeMatrix[n + NODE_DX], 2) +
|
|
736
|
+
Math.pow(NodeMatrix[n + NODE_DY], 2)
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
if (force > MAX_FORCE) {
|
|
740
|
+
NodeMatrix[n + NODE_DX] =
|
|
741
|
+
(NodeMatrix[n + NODE_DX] * MAX_FORCE) / force;
|
|
742
|
+
NodeMatrix[n + NODE_DY] =
|
|
743
|
+
(NodeMatrix[n + NODE_DY] * MAX_FORCE) / force;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
swinging =
|
|
747
|
+
NodeMatrix[n + NODE_MASS] *
|
|
748
|
+
Math.sqrt(
|
|
749
|
+
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) *
|
|
750
|
+
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) +
|
|
751
|
+
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY]) *
|
|
752
|
+
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY])
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
traction =
|
|
756
|
+
Math.sqrt(
|
|
757
|
+
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) *
|
|
758
|
+
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) +
|
|
759
|
+
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY]) *
|
|
760
|
+
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY])
|
|
761
|
+
) / 2;
|
|
762
|
+
|
|
763
|
+
nodespeed = (0.1 * Math.log(1 + traction)) / (1 + Math.sqrt(swinging));
|
|
764
|
+
|
|
765
|
+
// Updating node's positon
|
|
766
|
+
newX =
|
|
767
|
+
NodeMatrix[n + NODE_X] +
|
|
768
|
+
NodeMatrix[n + NODE_DX] * (nodespeed / options.slowDown);
|
|
769
|
+
NodeMatrix[n + NODE_X] = newX;
|
|
770
|
+
|
|
771
|
+
newY =
|
|
772
|
+
NodeMatrix[n + NODE_Y] +
|
|
773
|
+
NodeMatrix[n + NODE_DY] * (nodespeed / options.slowDown);
|
|
774
|
+
NodeMatrix[n + NODE_Y] = newY;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
for (n = 0; n < order; n += PPN) {
|
|
779
|
+
if (NodeMatrix[n + NODE_FIXED] !== 1) {
|
|
780
|
+
swinging =
|
|
781
|
+
NodeMatrix[n + NODE_MASS] *
|
|
782
|
+
Math.sqrt(
|
|
783
|
+
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) *
|
|
784
|
+
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) +
|
|
785
|
+
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY]) *
|
|
786
|
+
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY])
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
traction =
|
|
790
|
+
Math.sqrt(
|
|
791
|
+
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) *
|
|
792
|
+
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) +
|
|
793
|
+
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY]) *
|
|
794
|
+
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY])
|
|
795
|
+
) / 2;
|
|
796
|
+
|
|
797
|
+
nodespeed =
|
|
798
|
+
(NodeMatrix[n + NODE_CONVERGENCE] * Math.log(1 + traction)) /
|
|
799
|
+
(1 + Math.sqrt(swinging));
|
|
800
|
+
|
|
801
|
+
// Updating node convergence
|
|
802
|
+
NodeMatrix[n + NODE_CONVERGENCE] = Math.min(
|
|
803
|
+
1,
|
|
804
|
+
Math.sqrt(
|
|
805
|
+
(nodespeed *
|
|
806
|
+
(Math.pow(NodeMatrix[n + NODE_DX], 2) +
|
|
807
|
+
Math.pow(NodeMatrix[n + NODE_DY], 2))) /
|
|
808
|
+
(1 + Math.sqrt(swinging))
|
|
809
|
+
)
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
// Updating node's positon
|
|
813
|
+
newX =
|
|
814
|
+
NodeMatrix[n + NODE_X] +
|
|
815
|
+
NodeMatrix[n + NODE_DX] * (nodespeed / options.slowDown);
|
|
816
|
+
NodeMatrix[n + NODE_X] = newX;
|
|
817
|
+
|
|
818
|
+
newY =
|
|
819
|
+
NodeMatrix[n + NODE_Y] +
|
|
820
|
+
NodeMatrix[n + NODE_DY] * (nodespeed / options.slowDown);
|
|
821
|
+
NodeMatrix[n + NODE_Y] = newY;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// We return the information about the layout (no need to return the matrices)
|
|
827
|
+
return {};
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
})();
|
|
831
|
+
|
|
832
|
+
var iterate = moduleShim.exports;
|
|
833
|
+
|
|
834
|
+
self.addEventListener('message', function (event) {
|
|
835
|
+
var data = event.data;
|
|
836
|
+
|
|
837
|
+
NODES = new Float32Array(data.nodes);
|
|
838
|
+
|
|
839
|
+
if (data.edges) EDGES = new Float32Array(data.edges);
|
|
840
|
+
|
|
841
|
+
// Running the iteration
|
|
842
|
+
iterate(data.settings, NODES, EDGES);
|
|
843
|
+
|
|
844
|
+
// Sending result to supervisor
|
|
845
|
+
self.postMessage(
|
|
846
|
+
{
|
|
847
|
+
nodes: NODES.buffer
|
|
848
|
+
},
|
|
849
|
+
[NODES.buffer]
|
|
850
|
+
);
|
|
851
|
+
});
|
|
852
|
+
};
|
|
853
|
+
return webworker;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Graphology isGraph
|
|
858
|
+
* ===================
|
|
859
|
+
*
|
|
860
|
+
* Very simple function aiming at ensuring the given variable is a
|
|
861
|
+
* graphology instance.
|
|
862
|
+
*/
|
|
863
|
+
|
|
864
|
+
var isGraph;
|
|
865
|
+
var hasRequiredIsGraph;
|
|
866
|
+
|
|
867
|
+
function requireIsGraph () {
|
|
868
|
+
if (hasRequiredIsGraph) return isGraph;
|
|
869
|
+
hasRequiredIsGraph = 1;
|
|
870
|
+
/**
|
|
871
|
+
* Checking the value is a graphology instance.
|
|
872
|
+
*
|
|
873
|
+
* @param {any} value - Target value.
|
|
874
|
+
* @return {boolean}
|
|
875
|
+
*/
|
|
876
|
+
isGraph = function isGraph(value) {
|
|
877
|
+
return (
|
|
878
|
+
value !== null &&
|
|
879
|
+
typeof value === 'object' &&
|
|
880
|
+
typeof value.addUndirectedEdgeWithKey === 'function' &&
|
|
881
|
+
typeof value.dropNode === 'function' &&
|
|
882
|
+
typeof value.multi === 'boolean'
|
|
883
|
+
);
|
|
884
|
+
};
|
|
885
|
+
return isGraph;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
var getters = {};
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Graphology Weight Getter
|
|
892
|
+
* =========================
|
|
893
|
+
*
|
|
894
|
+
* Function creating weight getters.
|
|
895
|
+
*/
|
|
896
|
+
|
|
897
|
+
var hasRequiredGetters;
|
|
898
|
+
|
|
899
|
+
function requireGetters () {
|
|
900
|
+
if (hasRequiredGetters) return getters;
|
|
901
|
+
hasRequiredGetters = 1;
|
|
902
|
+
function coerceWeight(value) {
|
|
903
|
+
// Ensuring target value is a correct number
|
|
904
|
+
if (typeof value !== 'number' || isNaN(value)) return 1;
|
|
905
|
+
|
|
906
|
+
return value;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function createNodeValueGetter(nameOrFunction, defaultValue) {
|
|
910
|
+
var getter = {};
|
|
911
|
+
|
|
912
|
+
var coerceToDefault = function (v) {
|
|
913
|
+
if (typeof v === 'undefined') return defaultValue;
|
|
914
|
+
|
|
915
|
+
return v;
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
if (typeof defaultValue === 'function') coerceToDefault = defaultValue;
|
|
919
|
+
|
|
920
|
+
var get = function (attributes) {
|
|
921
|
+
return coerceToDefault(attributes[nameOrFunction]);
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
var returnDefault = function () {
|
|
925
|
+
return coerceToDefault(undefined);
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
if (typeof nameOrFunction === 'string') {
|
|
929
|
+
getter.fromAttributes = get;
|
|
930
|
+
getter.fromGraph = function (graph, node) {
|
|
931
|
+
return get(graph.getNodeAttributes(node));
|
|
932
|
+
};
|
|
933
|
+
getter.fromEntry = function (node, attributes) {
|
|
934
|
+
return get(attributes);
|
|
935
|
+
};
|
|
936
|
+
} else if (typeof nameOrFunction === 'function') {
|
|
937
|
+
getter.fromAttributes = function () {
|
|
938
|
+
throw new Error(
|
|
939
|
+
'graphology-utils/getters/createNodeValueGetter: irrelevant usage.'
|
|
940
|
+
);
|
|
941
|
+
};
|
|
942
|
+
getter.fromGraph = function (graph, node) {
|
|
943
|
+
return coerceToDefault(
|
|
944
|
+
nameOrFunction(node, graph.getNodeAttributes(node))
|
|
945
|
+
);
|
|
946
|
+
};
|
|
947
|
+
getter.fromEntry = function (node, attributes) {
|
|
948
|
+
return coerceToDefault(nameOrFunction(node, attributes));
|
|
949
|
+
};
|
|
950
|
+
} else {
|
|
951
|
+
getter.fromAttributes = returnDefault;
|
|
952
|
+
getter.fromGraph = returnDefault;
|
|
953
|
+
getter.fromEntry = returnDefault;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return getter;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function createEdgeValueGetter(nameOrFunction, defaultValue) {
|
|
960
|
+
var getter = {};
|
|
961
|
+
|
|
962
|
+
var coerceToDefault = function (v) {
|
|
963
|
+
if (typeof v === 'undefined') return defaultValue;
|
|
964
|
+
|
|
965
|
+
return v;
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
if (typeof defaultValue === 'function') coerceToDefault = defaultValue;
|
|
969
|
+
|
|
970
|
+
var get = function (attributes) {
|
|
971
|
+
return coerceToDefault(attributes[nameOrFunction]);
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
var returnDefault = function () {
|
|
975
|
+
return coerceToDefault(undefined);
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
if (typeof nameOrFunction === 'string') {
|
|
979
|
+
getter.fromAttributes = get;
|
|
980
|
+
getter.fromGraph = function (graph, edge) {
|
|
981
|
+
return get(graph.getEdgeAttributes(edge));
|
|
982
|
+
};
|
|
983
|
+
getter.fromEntry = function (edge, attributes) {
|
|
984
|
+
return get(attributes);
|
|
985
|
+
};
|
|
986
|
+
getter.fromPartialEntry = getter.fromEntry;
|
|
987
|
+
getter.fromMinimalEntry = getter.fromEntry;
|
|
988
|
+
} else if (typeof nameOrFunction === 'function') {
|
|
989
|
+
getter.fromAttributes = function () {
|
|
990
|
+
throw new Error(
|
|
991
|
+
'graphology-utils/getters/createEdgeValueGetter: irrelevant usage.'
|
|
992
|
+
);
|
|
993
|
+
};
|
|
994
|
+
getter.fromGraph = function (graph, edge) {
|
|
995
|
+
// TODO: we can do better, check #310
|
|
996
|
+
var extremities = graph.extremities(edge);
|
|
997
|
+
return coerceToDefault(
|
|
998
|
+
nameOrFunction(
|
|
999
|
+
edge,
|
|
1000
|
+
graph.getEdgeAttributes(edge),
|
|
1001
|
+
extremities[0],
|
|
1002
|
+
extremities[1],
|
|
1003
|
+
graph.getNodeAttributes(extremities[0]),
|
|
1004
|
+
graph.getNodeAttributes(extremities[1]),
|
|
1005
|
+
graph.isUndirected(edge)
|
|
1006
|
+
)
|
|
1007
|
+
);
|
|
1008
|
+
};
|
|
1009
|
+
getter.fromEntry = function (e, a, s, t, sa, ta, u) {
|
|
1010
|
+
return coerceToDefault(nameOrFunction(e, a, s, t, sa, ta, u));
|
|
1011
|
+
};
|
|
1012
|
+
getter.fromPartialEntry = function (e, a, s, t) {
|
|
1013
|
+
return coerceToDefault(nameOrFunction(e, a, s, t));
|
|
1014
|
+
};
|
|
1015
|
+
getter.fromMinimalEntry = function (e, a) {
|
|
1016
|
+
return coerceToDefault(nameOrFunction(e, a));
|
|
1017
|
+
};
|
|
1018
|
+
} else {
|
|
1019
|
+
getter.fromAttributes = returnDefault;
|
|
1020
|
+
getter.fromGraph = returnDefault;
|
|
1021
|
+
getter.fromEntry = returnDefault;
|
|
1022
|
+
getter.fromMinimalEntry = returnDefault;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
return getter;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
getters.createNodeValueGetter = createNodeValueGetter;
|
|
1029
|
+
getters.createEdgeValueGetter = createEdgeValueGetter;
|
|
1030
|
+
getters.createEdgeWeightGetter = function (name) {
|
|
1031
|
+
return createEdgeValueGetter(name, coerceWeight);
|
|
1032
|
+
};
|
|
1033
|
+
return getters;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
var helpers = {};
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Graphology ForceAtlas2 Helpers
|
|
1040
|
+
* ===============================
|
|
1041
|
+
*
|
|
1042
|
+
* Miscellaneous helper functions.
|
|
1043
|
+
*/
|
|
1044
|
+
|
|
1045
|
+
var hasRequiredHelpers;
|
|
1046
|
+
|
|
1047
|
+
function requireHelpers () {
|
|
1048
|
+
if (hasRequiredHelpers) return helpers;
|
|
1049
|
+
hasRequiredHelpers = 1;
|
|
1050
|
+
/**
|
|
1051
|
+
* Constants.
|
|
1052
|
+
*/
|
|
1053
|
+
var PPN = 10;
|
|
1054
|
+
var PPE = 3;
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Very simple Object.assign-like function.
|
|
1058
|
+
*
|
|
1059
|
+
* @param {object} target - First object.
|
|
1060
|
+
* @param {object} [...objects] - Objects to merge.
|
|
1061
|
+
* @return {object}
|
|
1062
|
+
*/
|
|
1063
|
+
helpers.assign = function (target) {
|
|
1064
|
+
target = target || {};
|
|
1065
|
+
|
|
1066
|
+
var objects = Array.prototype.slice.call(arguments).slice(1),
|
|
1067
|
+
i,
|
|
1068
|
+
k,
|
|
1069
|
+
l;
|
|
1070
|
+
|
|
1071
|
+
for (i = 0, l = objects.length; i < l; i++) {
|
|
1072
|
+
if (!objects[i]) continue;
|
|
1073
|
+
|
|
1074
|
+
for (k in objects[i]) target[k] = objects[i][k];
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return target;
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Function used to validate the given settings.
|
|
1082
|
+
*
|
|
1083
|
+
* @param {object} settings - Settings to validate.
|
|
1084
|
+
* @return {object|null}
|
|
1085
|
+
*/
|
|
1086
|
+
helpers.validateSettings = function (settings) {
|
|
1087
|
+
if ('linLogMode' in settings && typeof settings.linLogMode !== 'boolean')
|
|
1088
|
+
return {message: 'the `linLogMode` setting should be a boolean.'};
|
|
1089
|
+
|
|
1090
|
+
if (
|
|
1091
|
+
'outboundAttractionDistribution' in settings &&
|
|
1092
|
+
typeof settings.outboundAttractionDistribution !== 'boolean'
|
|
1093
|
+
)
|
|
1094
|
+
return {
|
|
1095
|
+
message:
|
|
1096
|
+
'the `outboundAttractionDistribution` setting should be a boolean.'
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
if ('adjustSizes' in settings && typeof settings.adjustSizes !== 'boolean')
|
|
1100
|
+
return {message: 'the `adjustSizes` setting should be a boolean.'};
|
|
1101
|
+
|
|
1102
|
+
if (
|
|
1103
|
+
'edgeWeightInfluence' in settings &&
|
|
1104
|
+
typeof settings.edgeWeightInfluence !== 'number'
|
|
1105
|
+
)
|
|
1106
|
+
return {
|
|
1107
|
+
message: 'the `edgeWeightInfluence` setting should be a number.'
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
if (
|
|
1111
|
+
'scalingRatio' in settings &&
|
|
1112
|
+
!(typeof settings.scalingRatio === 'number' && settings.scalingRatio >= 0)
|
|
1113
|
+
)
|
|
1114
|
+
return {message: 'the `scalingRatio` setting should be a number >= 0.'};
|
|
1115
|
+
|
|
1116
|
+
if (
|
|
1117
|
+
'strongGravityMode' in settings &&
|
|
1118
|
+
typeof settings.strongGravityMode !== 'boolean'
|
|
1119
|
+
)
|
|
1120
|
+
return {message: 'the `strongGravityMode` setting should be a boolean.'};
|
|
1121
|
+
|
|
1122
|
+
if (
|
|
1123
|
+
'gravity' in settings &&
|
|
1124
|
+
!(typeof settings.gravity === 'number' && settings.gravity >= 0)
|
|
1125
|
+
)
|
|
1126
|
+
return {message: 'the `gravity` setting should be a number >= 0.'};
|
|
1127
|
+
|
|
1128
|
+
if (
|
|
1129
|
+
'slowDown' in settings &&
|
|
1130
|
+
!(typeof settings.slowDown === 'number' || settings.slowDown >= 0)
|
|
1131
|
+
)
|
|
1132
|
+
return {message: 'the `slowDown` setting should be a number >= 0.'};
|
|
1133
|
+
|
|
1134
|
+
if (
|
|
1135
|
+
'barnesHutOptimize' in settings &&
|
|
1136
|
+
typeof settings.barnesHutOptimize !== 'boolean'
|
|
1137
|
+
)
|
|
1138
|
+
return {message: 'the `barnesHutOptimize` setting should be a boolean.'};
|
|
1139
|
+
|
|
1140
|
+
if (
|
|
1141
|
+
'barnesHutTheta' in settings &&
|
|
1142
|
+
!(
|
|
1143
|
+
typeof settings.barnesHutTheta === 'number' &&
|
|
1144
|
+
settings.barnesHutTheta >= 0
|
|
1145
|
+
)
|
|
1146
|
+
)
|
|
1147
|
+
return {message: 'the `barnesHutTheta` setting should be a number >= 0.'};
|
|
1148
|
+
|
|
1149
|
+
return null;
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Function generating a flat matrix for both nodes & edges of the given graph.
|
|
1154
|
+
*
|
|
1155
|
+
* @param {Graph} graph - Target graph.
|
|
1156
|
+
* @param {function} getEdgeWeight - Edge weight getter function.
|
|
1157
|
+
* @return {object} - Both matrices.
|
|
1158
|
+
*/
|
|
1159
|
+
helpers.graphToByteArrays = function (graph, getEdgeWeight) {
|
|
1160
|
+
var order = graph.order;
|
|
1161
|
+
var size = graph.size;
|
|
1162
|
+
var index = {};
|
|
1163
|
+
var j;
|
|
1164
|
+
|
|
1165
|
+
// NOTE: float32 could lead to issues if edge array needs to index large
|
|
1166
|
+
// number of nodes.
|
|
1167
|
+
var NodeMatrix = new Float32Array(order * PPN);
|
|
1168
|
+
var EdgeMatrix = new Float32Array(size * PPE);
|
|
1169
|
+
|
|
1170
|
+
// Iterate through nodes
|
|
1171
|
+
j = 0;
|
|
1172
|
+
graph.forEachNode(function (node, attr) {
|
|
1173
|
+
// Node index
|
|
1174
|
+
index[node] = j;
|
|
1175
|
+
|
|
1176
|
+
// Populating byte array
|
|
1177
|
+
NodeMatrix[j] = attr.x;
|
|
1178
|
+
NodeMatrix[j + 1] = attr.y;
|
|
1179
|
+
NodeMatrix[j + 2] = 0; // dx
|
|
1180
|
+
NodeMatrix[j + 3] = 0; // dy
|
|
1181
|
+
NodeMatrix[j + 4] = 0; // old_dx
|
|
1182
|
+
NodeMatrix[j + 5] = 0; // old_dy
|
|
1183
|
+
NodeMatrix[j + 6] = 1; // mass
|
|
1184
|
+
NodeMatrix[j + 7] = 1; // convergence
|
|
1185
|
+
NodeMatrix[j + 8] = attr.size || 1;
|
|
1186
|
+
NodeMatrix[j + 9] = attr.fixed ? 1 : 0;
|
|
1187
|
+
j += PPN;
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
// Iterate through edges
|
|
1191
|
+
j = 0;
|
|
1192
|
+
graph.forEachEdge(function (edge, attr, source, target, sa, ta, u) {
|
|
1193
|
+
var sj = index[source];
|
|
1194
|
+
var tj = index[target];
|
|
1195
|
+
|
|
1196
|
+
var weight = getEdgeWeight(edge, attr, source, target, sa, ta, u);
|
|
1197
|
+
|
|
1198
|
+
// Incrementing mass to be a node's weighted degree
|
|
1199
|
+
NodeMatrix[sj + 6] += weight;
|
|
1200
|
+
NodeMatrix[tj + 6] += weight;
|
|
1201
|
+
|
|
1202
|
+
// Populating byte array
|
|
1203
|
+
EdgeMatrix[j] = sj;
|
|
1204
|
+
EdgeMatrix[j + 1] = tj;
|
|
1205
|
+
EdgeMatrix[j + 2] = weight;
|
|
1206
|
+
j += PPE;
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
return {
|
|
1210
|
+
nodes: NodeMatrix,
|
|
1211
|
+
edges: EdgeMatrix
|
|
1212
|
+
};
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* Function applying the layout back to the graph.
|
|
1217
|
+
*
|
|
1218
|
+
* @param {Graph} graph - Target graph.
|
|
1219
|
+
* @param {Float32Array} NodeMatrix - Node matrix.
|
|
1220
|
+
* @param {function|null} outputReducer - A node reducer.
|
|
1221
|
+
*/
|
|
1222
|
+
helpers.assignLayoutChanges = function (graph, NodeMatrix, outputReducer) {
|
|
1223
|
+
var i = 0;
|
|
1224
|
+
|
|
1225
|
+
graph.updateEachNodeAttributes(function (node, attr) {
|
|
1226
|
+
attr.x = NodeMatrix[i];
|
|
1227
|
+
attr.y = NodeMatrix[i + 1];
|
|
1228
|
+
|
|
1229
|
+
i += PPN;
|
|
1230
|
+
|
|
1231
|
+
return outputReducer ? outputReducer(node, attr) : attr;
|
|
1232
|
+
});
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Function reading the positions (only) from the graph, to write them in the matrix.
|
|
1237
|
+
*
|
|
1238
|
+
* @param {Graph} graph - Target graph.
|
|
1239
|
+
* @param {Float32Array} NodeMatrix - Node matrix.
|
|
1240
|
+
*/
|
|
1241
|
+
helpers.readGraphPositions = function (graph, NodeMatrix) {
|
|
1242
|
+
var i = 0;
|
|
1243
|
+
|
|
1244
|
+
graph.forEachNode(function (node, attr) {
|
|
1245
|
+
NodeMatrix[i] = attr.x;
|
|
1246
|
+
NodeMatrix[i + 1] = attr.y;
|
|
1247
|
+
|
|
1248
|
+
i += PPN;
|
|
1249
|
+
});
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Function collecting the layout positions.
|
|
1254
|
+
*
|
|
1255
|
+
* @param {Graph} graph - Target graph.
|
|
1256
|
+
* @param {Float32Array} NodeMatrix - Node matrix.
|
|
1257
|
+
* @param {function|null} outputReducer - A nodes reducer.
|
|
1258
|
+
* @return {object} - Map to node positions.
|
|
1259
|
+
*/
|
|
1260
|
+
helpers.collectLayoutChanges = function (graph, NodeMatrix, outputReducer) {
|
|
1261
|
+
var nodes = graph.nodes(),
|
|
1262
|
+
positions = {};
|
|
1263
|
+
|
|
1264
|
+
for (var i = 0, j = 0, l = NodeMatrix.length; i < l; i += PPN) {
|
|
1265
|
+
if (outputReducer) {
|
|
1266
|
+
var newAttr = Object.assign({}, graph.getNodeAttributes(nodes[j]));
|
|
1267
|
+
newAttr.x = NodeMatrix[i];
|
|
1268
|
+
newAttr.y = NodeMatrix[i + 1];
|
|
1269
|
+
newAttr = outputReducer(nodes[j], newAttr);
|
|
1270
|
+
positions[nodes[j]] = {
|
|
1271
|
+
x: newAttr.x,
|
|
1272
|
+
y: newAttr.y
|
|
1273
|
+
};
|
|
1274
|
+
} else {
|
|
1275
|
+
positions[nodes[j]] = {
|
|
1276
|
+
x: NodeMatrix[i],
|
|
1277
|
+
y: NodeMatrix[i + 1]
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
j++;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return positions;
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Function returning a web worker from the given function.
|
|
1289
|
+
*
|
|
1290
|
+
* @param {function} fn - Function for the worker.
|
|
1291
|
+
* @return {DOMString}
|
|
1292
|
+
*/
|
|
1293
|
+
helpers.createWorker = function createWorker(fn) {
|
|
1294
|
+
var xURL = window.URL || window.webkitURL;
|
|
1295
|
+
var code = fn.toString();
|
|
1296
|
+
var objectUrl = xURL.createObjectURL(
|
|
1297
|
+
new Blob(['(' + code + ').call(this);'], {type: 'text/javascript'})
|
|
1298
|
+
);
|
|
1299
|
+
var worker = new Worker(objectUrl);
|
|
1300
|
+
xURL.revokeObjectURL(objectUrl);
|
|
1301
|
+
|
|
1302
|
+
return worker;
|
|
1303
|
+
};
|
|
1304
|
+
return helpers;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* Graphology ForceAtlas2 Layout Default Settings
|
|
1309
|
+
* ===============================================
|
|
1310
|
+
*/
|
|
1311
|
+
|
|
1312
|
+
var defaults;
|
|
1313
|
+
var hasRequiredDefaults;
|
|
1314
|
+
|
|
1315
|
+
function requireDefaults () {
|
|
1316
|
+
if (hasRequiredDefaults) return defaults;
|
|
1317
|
+
hasRequiredDefaults = 1;
|
|
1318
|
+
defaults = {
|
|
1319
|
+
linLogMode: false,
|
|
1320
|
+
outboundAttractionDistribution: false,
|
|
1321
|
+
adjustSizes: false,
|
|
1322
|
+
edgeWeightInfluence: 1,
|
|
1323
|
+
scalingRatio: 1,
|
|
1324
|
+
strongGravityMode: false,
|
|
1325
|
+
gravity: 1,
|
|
1326
|
+
slowDown: 1,
|
|
1327
|
+
barnesHutOptimize: false,
|
|
1328
|
+
barnesHutTheta: 0.5
|
|
1329
|
+
};
|
|
1330
|
+
return defaults;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Graphology ForceAtlas2 Layout Supervisor
|
|
1335
|
+
* =========================================
|
|
1336
|
+
*
|
|
1337
|
+
* Supervisor class able to spawn a web worker to run the FA2 layout in a
|
|
1338
|
+
* separate thread not to block UI with heavy synchronous computations.
|
|
1339
|
+
*/
|
|
1340
|
+
|
|
1341
|
+
var worker;
|
|
1342
|
+
var hasRequiredWorker;
|
|
1343
|
+
|
|
1344
|
+
function requireWorker () {
|
|
1345
|
+
if (hasRequiredWorker) return worker;
|
|
1346
|
+
hasRequiredWorker = 1;
|
|
1347
|
+
var workerFunction = requireWebworker();
|
|
1348
|
+
var isGraph = requireIsGraph();
|
|
1349
|
+
var createEdgeWeightGetter =
|
|
1350
|
+
requireGetters().createEdgeWeightGetter;
|
|
1351
|
+
var helpers = requireHelpers();
|
|
1352
|
+
|
|
1353
|
+
var DEFAULT_SETTINGS = requireDefaults();
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Class representing a FA2 layout run by a webworker.
|
|
1357
|
+
*
|
|
1358
|
+
* @constructor
|
|
1359
|
+
* @param {Graph} graph - Target graph.
|
|
1360
|
+
* @param {object|number} params - Parameters:
|
|
1361
|
+
* @param {object} [settings] - Settings.
|
|
1362
|
+
*/
|
|
1363
|
+
function FA2LayoutSupervisor(graph, params) {
|
|
1364
|
+
params = params || {};
|
|
1365
|
+
|
|
1366
|
+
// Validation
|
|
1367
|
+
if (!isGraph(graph))
|
|
1368
|
+
throw new Error(
|
|
1369
|
+
'graphology-layout-forceatlas2/worker: the given graph is not a valid graphology instance.'
|
|
1370
|
+
);
|
|
1371
|
+
|
|
1372
|
+
var getEdgeWeight = createEdgeWeightGetter(
|
|
1373
|
+
'getEdgeWeight' in params ? params.getEdgeWeight : 'weight'
|
|
1374
|
+
).fromEntry;
|
|
1375
|
+
|
|
1376
|
+
// Validating settings
|
|
1377
|
+
var settings = helpers.assign({}, DEFAULT_SETTINGS, params.settings);
|
|
1378
|
+
var validationError = helpers.validateSettings(settings);
|
|
1379
|
+
|
|
1380
|
+
if (validationError)
|
|
1381
|
+
throw new Error(
|
|
1382
|
+
'graphology-layout-forceatlas2/worker: ' + validationError.message
|
|
1383
|
+
);
|
|
1384
|
+
|
|
1385
|
+
// Properties
|
|
1386
|
+
this.worker = null;
|
|
1387
|
+
this.graph = graph;
|
|
1388
|
+
this.settings = settings;
|
|
1389
|
+
this.getEdgeWeight = getEdgeWeight;
|
|
1390
|
+
this.matrices = null;
|
|
1391
|
+
this.running = false;
|
|
1392
|
+
this.killed = false;
|
|
1393
|
+
this.outputReducer =
|
|
1394
|
+
typeof params.outputReducer === 'function' ? params.outputReducer : null;
|
|
1395
|
+
|
|
1396
|
+
// Binding listeners
|
|
1397
|
+
this.handleMessage = this.handleMessage.bind(this);
|
|
1398
|
+
|
|
1399
|
+
var respawnFrame = undefined;
|
|
1400
|
+
var self = this;
|
|
1401
|
+
|
|
1402
|
+
this.handleGraphUpdate = function () {
|
|
1403
|
+
if (self.worker) self.worker.terminate();
|
|
1404
|
+
|
|
1405
|
+
if (respawnFrame) clearTimeout(respawnFrame);
|
|
1406
|
+
|
|
1407
|
+
respawnFrame = setTimeout(function () {
|
|
1408
|
+
respawnFrame = undefined;
|
|
1409
|
+
self.spawnWorker();
|
|
1410
|
+
}, 0);
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
graph.on('nodeAdded', this.handleGraphUpdate);
|
|
1414
|
+
graph.on('edgeAdded', this.handleGraphUpdate);
|
|
1415
|
+
graph.on('nodeDropped', this.handleGraphUpdate);
|
|
1416
|
+
graph.on('edgeDropped', this.handleGraphUpdate);
|
|
1417
|
+
|
|
1418
|
+
// Spawning worker
|
|
1419
|
+
this.spawnWorker();
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
FA2LayoutSupervisor.prototype.isRunning = function () {
|
|
1423
|
+
return this.running;
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* Internal method used to spawn the web worker.
|
|
1428
|
+
*/
|
|
1429
|
+
FA2LayoutSupervisor.prototype.spawnWorker = function () {
|
|
1430
|
+
if (this.worker) this.worker.terminate();
|
|
1431
|
+
|
|
1432
|
+
this.worker = helpers.createWorker(workerFunction);
|
|
1433
|
+
this.worker.addEventListener('message', this.handleMessage);
|
|
1434
|
+
|
|
1435
|
+
if (this.running) {
|
|
1436
|
+
this.running = false;
|
|
1437
|
+
this.start();
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* Internal method used to handle the worker's messages.
|
|
1443
|
+
*
|
|
1444
|
+
* @param {object} event - Event to handle.
|
|
1445
|
+
*/
|
|
1446
|
+
FA2LayoutSupervisor.prototype.handleMessage = function (event) {
|
|
1447
|
+
if (!this.running) return;
|
|
1448
|
+
|
|
1449
|
+
var matrix = new Float32Array(event.data.nodes);
|
|
1450
|
+
|
|
1451
|
+
helpers.assignLayoutChanges(this.graph, matrix, this.outputReducer);
|
|
1452
|
+
if (this.outputReducer) helpers.readGraphPositions(this.graph, matrix);
|
|
1453
|
+
this.matrices.nodes = matrix;
|
|
1454
|
+
|
|
1455
|
+
// Looping
|
|
1456
|
+
this.askForIterations();
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Internal method used to ask for iterations from the worker.
|
|
1461
|
+
*
|
|
1462
|
+
* @param {boolean} withEdges - Should we send edges along?
|
|
1463
|
+
* @return {FA2LayoutSupervisor}
|
|
1464
|
+
*/
|
|
1465
|
+
FA2LayoutSupervisor.prototype.askForIterations = function (withEdges) {
|
|
1466
|
+
var matrices = this.matrices;
|
|
1467
|
+
|
|
1468
|
+
var payload = {
|
|
1469
|
+
settings: this.settings,
|
|
1470
|
+
nodes: matrices.nodes.buffer
|
|
1471
|
+
};
|
|
1472
|
+
|
|
1473
|
+
var buffers = [matrices.nodes.buffer];
|
|
1474
|
+
|
|
1475
|
+
if (withEdges) {
|
|
1476
|
+
payload.edges = matrices.edges.buffer;
|
|
1477
|
+
buffers.push(matrices.edges.buffer);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
this.worker.postMessage(payload, buffers);
|
|
1481
|
+
|
|
1482
|
+
return this;
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Method used to start the layout.
|
|
1487
|
+
*
|
|
1488
|
+
* @return {FA2LayoutSupervisor}
|
|
1489
|
+
*/
|
|
1490
|
+
FA2LayoutSupervisor.prototype.start = function () {
|
|
1491
|
+
if (this.killed)
|
|
1492
|
+
throw new Error(
|
|
1493
|
+
'graphology-layout-forceatlas2/worker.start: layout was killed.'
|
|
1494
|
+
);
|
|
1495
|
+
|
|
1496
|
+
if (this.running) return this;
|
|
1497
|
+
|
|
1498
|
+
// Building matrices
|
|
1499
|
+
this.matrices = helpers.graphToByteArrays(this.graph, this.getEdgeWeight);
|
|
1500
|
+
|
|
1501
|
+
this.running = true;
|
|
1502
|
+
this.askForIterations(true);
|
|
1503
|
+
|
|
1504
|
+
return this;
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* Method used to stop the layout.
|
|
1509
|
+
*
|
|
1510
|
+
* @return {FA2LayoutSupervisor}
|
|
1511
|
+
*/
|
|
1512
|
+
FA2LayoutSupervisor.prototype.stop = function () {
|
|
1513
|
+
this.running = false;
|
|
1514
|
+
|
|
1515
|
+
return this;
|
|
1516
|
+
};
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Method used to kill the layout.
|
|
1520
|
+
*
|
|
1521
|
+
* @return {FA2LayoutSupervisor}
|
|
1522
|
+
*/
|
|
1523
|
+
FA2LayoutSupervisor.prototype.kill = function () {
|
|
1524
|
+
if (this.killed) return this;
|
|
1525
|
+
|
|
1526
|
+
this.running = false;
|
|
1527
|
+
this.killed = true;
|
|
1528
|
+
|
|
1529
|
+
// Clearing memory
|
|
1530
|
+
this.matrices = null;
|
|
1531
|
+
|
|
1532
|
+
// Terminating worker
|
|
1533
|
+
this.worker.terminate();
|
|
1534
|
+
|
|
1535
|
+
// Unbinding listeners
|
|
1536
|
+
this.graph.removeListener('nodeAdded', this.handleGraphUpdate);
|
|
1537
|
+
this.graph.removeListener('edgeAdded', this.handleGraphUpdate);
|
|
1538
|
+
this.graph.removeListener('nodeDropped', this.handleGraphUpdate);
|
|
1539
|
+
this.graph.removeListener('edgeDropped', this.handleGraphUpdate);
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* Exporting.
|
|
1544
|
+
*/
|
|
1545
|
+
worker = FA2LayoutSupervisor;
|
|
1546
|
+
return worker;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
var workerExports = requireWorker();
|
|
1550
|
+
var FA2Layout = /*@__PURE__*/getDefaultExportFromCjs(workerExports);
|
|
1551
|
+
|
|
1552
|
+
const key = "vunk-graph-layout-forceatlas2";
|
|
1553
|
+
function initLayoutForceatlas2(props = {}) {
|
|
1554
|
+
const graph = useGraph();
|
|
1555
|
+
const layoutRef = shallowRef(null);
|
|
1556
|
+
function start() {
|
|
1557
|
+
const sensibleSettings = forceAtlas2.inferSettings(graph);
|
|
1558
|
+
layoutRef.value = new FA2Layout(graph, {
|
|
1559
|
+
settings: {
|
|
1560
|
+
...sensibleSettings,
|
|
1561
|
+
...props.settings
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
layoutRef.value.start();
|
|
1565
|
+
}
|
|
1566
|
+
function stop() {
|
|
1567
|
+
layoutRef.value?.stop();
|
|
1568
|
+
}
|
|
1569
|
+
function kill() {
|
|
1570
|
+
layoutRef.value?.kill();
|
|
1571
|
+
}
|
|
1572
|
+
onBeforeUnmount(() => {
|
|
1573
|
+
layoutRef.value?.kill();
|
|
1574
|
+
});
|
|
1575
|
+
provide(key, {
|
|
1576
|
+
start,
|
|
1577
|
+
stop,
|
|
1578
|
+
kill,
|
|
1579
|
+
layoutRef
|
|
1580
|
+
});
|
|
1581
|
+
return {
|
|
1582
|
+
start,
|
|
1583
|
+
stop,
|
|
1584
|
+
kill
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function useLayoutForceatlas2() {
|
|
1588
|
+
const instance = inject(key);
|
|
1589
|
+
if (!instance) {
|
|
1590
|
+
throw new Error("useLayoutForceatlas2 must be used after provideLayoutForceatlas2");
|
|
1591
|
+
}
|
|
1592
|
+
return instance;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
var _sfc_main = defineComponent({
|
|
1596
|
+
name: "VkLayoutForceatlas2",
|
|
1597
|
+
props,
|
|
1598
|
+
setup(props2) {
|
|
1599
|
+
initLayoutForceatlas2(props2);
|
|
1600
|
+
return {};
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
1605
|
+
return renderSlot(_ctx.$slots, "default");
|
|
1606
|
+
}
|
|
1607
|
+
var VkLayoutForceatlas2 = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
|
|
1608
|
+
|
|
1609
|
+
var types = /*#__PURE__*/Object.freeze({
|
|
1610
|
+
__proto__: null
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
VkLayoutForceatlas2.install = (app) => {
|
|
1614
|
+
app.component(VkLayoutForceatlas2.name || "VkLayoutForceatlas2", VkLayoutForceatlas2);
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
export { VkLayoutForceatlas2, types as __VkLayoutForceatlas2, VkLayoutForceatlas2 as default, useLayoutForceatlas2 };
|