@antv/layout 0.3.22 → 0.3.24

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,4 +1,4 @@
1
- import { Base } from "./base";
1
+ import { Base } from './base';
2
2
  export interface Node {
3
3
  id: string;
4
4
  }
@@ -77,7 +77,7 @@ export interface ComboTree {
77
77
  children?: ComboTree[];
78
78
  depth?: number;
79
79
  parentId?: string;
80
- itemType?: "node" | "combo";
80
+ itemType?: 'node' | 'combo';
81
81
  [key: string]: unknown;
82
82
  }
83
83
  export interface ComboConfig {
@@ -87,7 +87,7 @@ export interface ComboConfig {
87
87
  depth?: number;
88
88
  }
89
89
  export interface CircularLayoutOptions {
90
- type: "circular";
90
+ type: 'circular';
91
91
  center?: PointTuple;
92
92
  width?: number;
93
93
  height?: number;
@@ -96,7 +96,7 @@ export interface CircularLayoutOptions {
96
96
  endRadius?: number | null;
97
97
  clockwise?: boolean;
98
98
  divisions?: number;
99
- ordering?: "topology" | "topology-directed" | "degree" | null;
99
+ ordering?: 'topology' | 'topology-directed' | 'degree' | null;
100
100
  angleRatio?: number;
101
101
  workerEnabled?: boolean;
102
102
  startAngle?: number;
@@ -104,7 +104,7 @@ export interface CircularLayoutOptions {
104
104
  onLayoutEnd?: () => void;
105
105
  }
106
106
  export interface ComboForceLayoutOptions {
107
- type: "comboForce";
107
+ type: 'comboForce';
108
108
  center?: PointTuple;
109
109
  maxIteration?: number;
110
110
  linkDistance?: number | ((d?: unknown) => number);
@@ -134,7 +134,7 @@ export interface ComboForceLayoutOptions {
134
134
  workerEnabled?: boolean;
135
135
  }
136
136
  export interface ComboCombinedLayoutOptions {
137
- type: "comboConcentricForce";
137
+ type: 'comboConcentricForce';
138
138
  center?: PointTuple;
139
139
  nodeSize?: number | number[] | ((d?: any) => number) | undefined;
140
140
  spacing?: number | number[] | ((d?: any) => number) | undefined;
@@ -144,7 +144,7 @@ export interface ComboCombinedLayoutOptions {
144
144
  innerLayout?: Base;
145
145
  }
146
146
  export interface ConcentricLayoutOptions {
147
- type: "concentric";
147
+ type: 'concentric';
148
148
  center?: PointTuple;
149
149
  preventOverlap?: boolean;
150
150
  nodeSize?: number | PointTuple;
@@ -161,9 +161,9 @@ export interface ConcentricLayoutOptions {
161
161
  onLayoutEnd?: () => void;
162
162
  }
163
163
  export interface DagreLayoutOptions {
164
- type: "dagre";
165
- rankdir?: "TB" | "BT" | "LR" | "RL";
166
- align?: "UL" | "UR" | "DL" | "DR";
164
+ type: 'dagre';
165
+ rankdir?: 'TB' | 'BT' | 'LR' | 'RL';
166
+ align?: 'UL' | 'UR' | 'DL' | 'DR';
167
167
  begin?: PointTuple;
168
168
  nodeSize?: number | number[] | undefined;
169
169
  nodesep?: number;
@@ -182,9 +182,9 @@ export interface DagreLayoutOptions {
182
182
  ranksepFunc?: ((d?: any) => number) | undefined;
183
183
  }
184
184
  export interface DagreCompoundLayoutOptions {
185
- type?: "dagreCompound";
186
- rankdir?: "TB" | "BT" | "LR" | "RL";
187
- align?: "UL" | "UR" | "DL" | "DR";
185
+ type?: 'dagreCompound';
186
+ rankdir?: 'TB' | 'BT' | 'LR' | 'RL';
187
+ align?: 'UL' | 'UR' | 'DL' | 'DR';
188
188
  begin?: PointTuple;
189
189
  nodeSize?: number | number[] | undefined;
190
190
  nodesep?: number;
@@ -195,7 +195,7 @@ export interface DagreCompoundLayoutOptions {
195
195
  onLayoutEnd?: () => void;
196
196
  }
197
197
  export interface FruchtermanLayoutOptions {
198
- type: "fruchterman";
198
+ type: 'fruchterman';
199
199
  center?: PointTuple;
200
200
  maxIteration?: number;
201
201
  width?: number;
@@ -223,7 +223,7 @@ export interface CentripetalOptions {
223
223
  };
224
224
  }
225
225
  export interface Force2LayoutOptions {
226
- type?: "force2";
226
+ type?: 'force2';
227
227
  center?: PointTuple;
228
228
  width?: number;
229
229
  height?: number;
@@ -262,7 +262,7 @@ export interface Force2LayoutOptions {
262
262
  }) => void;
263
263
  }
264
264
  export interface GForceLayoutOptions {
265
- type?: "gForce";
265
+ type?: 'gForce';
266
266
  center?: PointTuple;
267
267
  width?: number;
268
268
  height?: number;
@@ -291,7 +291,7 @@ type INode = OutNode & {
291
291
  size: number | PointTuple;
292
292
  };
293
293
  export interface GridLayoutOptions {
294
- type: "grid";
294
+ type: 'grid';
295
295
  width?: number;
296
296
  height?: number;
297
297
  begin?: PointTuple;
@@ -310,14 +310,14 @@ export interface GridLayoutOptions {
310
310
  onLayoutEnd?: () => void;
311
311
  }
312
312
  export interface MDSLayoutOptions {
313
- type: "mds";
313
+ type: 'mds';
314
314
  center?: PointTuple;
315
315
  linkDistance?: number;
316
316
  workerEnabled?: boolean;
317
317
  onLayoutEnd?: () => void;
318
318
  }
319
319
  export interface RandomLayoutOptions {
320
- type: "random";
320
+ type: 'random';
321
321
  center?: PointTuple;
322
322
  width?: number;
323
323
  height?: number;
@@ -325,7 +325,7 @@ export interface RandomLayoutOptions {
325
325
  onLayoutEnd?: () => void;
326
326
  }
327
327
  export interface ForceLayoutOptions {
328
- type: "force";
328
+ type: 'force';
329
329
  center?: PointTuple;
330
330
  linkDistance?: number | ((d?: any) => number) | undefined;
331
331
  edgeStrength?: number | ((d?: any) => number) | undefined;
@@ -349,7 +349,7 @@ export interface ForceLayoutOptions {
349
349
  workerEnabled?: boolean;
350
350
  }
351
351
  export interface RadialLayoutOptions {
352
- type: "radial";
352
+ type: 'radial';
353
353
  center?: PointTuple;
354
354
  width?: number;
355
355
  height?: number;
@@ -365,10 +365,11 @@ export interface RadialLayoutOptions {
365
365
  sortBy?: string | undefined;
366
366
  sortStrength?: number;
367
367
  workerEnabled?: boolean;
368
+ initWithMDS?: boolean;
368
369
  onLayoutEnd?: () => void;
369
370
  }
370
371
  export interface FruchtermanGPULayoutOptions {
371
- type: "fruchterman-gpu";
372
+ type: 'fruchterman-gpu';
372
373
  center?: PointTuple;
373
374
  width?: number;
374
375
  height?: number;
@@ -382,7 +383,7 @@ export interface FruchtermanGPULayoutOptions {
382
383
  onLayoutEnd?: () => void;
383
384
  }
384
385
  export interface GForceGPULayoutOptions {
385
- type: "gForce-gpu";
386
+ type: 'gForce-gpu';
386
387
  center?: PointTuple;
387
388
  linkDistance?: number | ((d?: any) => number) | undefined;
388
389
  nodeStrength?: number | ((d?: any) => number) | undefined;
@@ -400,7 +401,7 @@ export interface GForceGPULayoutOptions {
400
401
  gpuEnabled?: boolean;
401
402
  }
402
403
  export interface ForceAtlas2LayoutOptions {
403
- type: "forceAtlas2";
404
+ type: 'forceAtlas2';
404
405
  center?: PointTuple;
405
406
  width?: number;
406
407
  height?: number;
@@ -420,13 +421,13 @@ export interface ForceAtlas2LayoutOptions {
420
421
  prune?: boolean;
421
422
  }
422
423
  export interface ERLayoutOptions {
423
- type: "er";
424
+ type: 'er';
424
425
  width?: number;
425
426
  height?: number;
426
427
  nodeMinGap?: number;
427
428
  }
428
429
  export declare namespace ILayout {
429
- type LayoutTypes = "grid" | "random" | "force" | "circular" | "dagre" | "radial" | "concentric" | "mds" | "fruchterman" | "fruchterman-gpu" | "gForce" | "gForce-gpu" | "comboForce" | "forceAtlas2" | "er";
430
+ type LayoutTypes = 'grid' | 'random' | 'force' | 'circular' | 'dagre' | 'radial' | 'concentric' | 'mds' | 'fruchterman' | 'fruchterman-gpu' | 'gForce' | 'gForce-gpu' | 'comboForce' | 'forceAtlas2' | 'er';
430
431
  type LayoutOptions = GridLayoutOptions | RandomLayoutOptions | ForceLayoutOptions | CircularLayoutOptions | DagreLayoutOptions | RadialLayoutOptions | ConcentricLayoutOptions | MDSLayoutOptions | FruchtermanLayoutOptions | FruchtermanGPULayoutOptions | GForceLayoutOptions | GForceGPULayoutOptions | ComboForceLayoutOptions | ComboCombinedLayoutOptions | ForceAtlas2LayoutOptions | ERLayoutOptions;
431
432
  }
432
433
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antv/layout",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "graph layout algorithm",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -3,18 +3,32 @@
3
3
  * @author shiwu.wyy@antfin.com
4
4
  */
5
5
 
6
- import {
6
+ import {
7
7
  Edge,
8
8
  Combo,
9
9
  OutNode,
10
10
  PointTuple,
11
11
  ComboTree,
12
- ComboCombinedLayoutOptions
13
- } from "./types";
12
+ ComboCombinedLayoutOptions,
13
+ } from './types';
14
14
  import { FORCE_LAYOUT_TYPE_MAP } from './constants';
15
- import { Base } from "./base";
16
- import { isArray, isNumber, isFunction, traverseTreeUp, isObject, getLayoutBBox } from "../util";
17
- import { CircularLayout, ConcentricLayout, GridLayout, RadialLayout, GForceLayout, MDSLayout } from ".";
15
+ import { Base } from './base';
16
+ import {
17
+ isArray,
18
+ isNumber,
19
+ isFunction,
20
+ traverseTreeUp,
21
+ isObject,
22
+ getLayoutBBox,
23
+ } from '../util';
24
+ import {
25
+ CircularLayout,
26
+ ConcentricLayout,
27
+ GridLayout,
28
+ RadialLayout,
29
+ GForceLayout,
30
+ MDSLayout,
31
+ } from '.';
18
32
 
19
33
  type Node = OutNode & {
20
34
  depth?: number;
@@ -29,7 +43,6 @@ type Node = OutNode & {
29
43
  * combined two layouts (inner and outer) for graph with combos
30
44
  */
31
45
  export class ComboCombinedLayout extends Base {
32
-
33
46
  /** 布局中心 */
34
47
  public center: PointTuple = [0, 0];
35
48
 
@@ -52,7 +65,11 @@ export class ComboCombinedLayout extends Base {
52
65
  public outerLayout: any;
53
66
 
54
67
  /** combo 内部的布局算法,默认为 concentric */
55
- public innerLayout: ConcentricLayout | CircularLayout | GridLayout | RadialLayout;
68
+ public innerLayout:
69
+ | ConcentricLayout
70
+ | CircularLayout
71
+ | GridLayout
72
+ | RadialLayout;
56
73
 
57
74
  /** Combo 内部的 padding */
58
75
  public comboPadding:
@@ -101,7 +118,7 @@ export class ComboCombinedLayout extends Base {
101
118
  public run() {
102
119
  const self = this;
103
120
  const { nodes, edges, combos, comboEdges, center } = self;
104
-
121
+
105
122
  const nodeMap: any = {};
106
123
  nodes.forEach((node) => {
107
124
  nodeMap[node.id] = node;
@@ -128,10 +145,15 @@ export class ComboCombinedLayout extends Base {
128
145
  fx: innerNode.fx || comboMap[cTree.id].fx,
129
146
  fy: innerNode.fy || comboMap[cTree.id].fy,
130
147
  mass: innerNode.mass || comboMap[cTree.id].mass,
131
- size: innerNode.size
148
+ size: innerNode.size,
132
149
  };
133
150
  outerNodes.push(oNode);
134
- if (!isNaN(oNode.x) && oNode.x !== 0 && !isNaN(oNode.y) && oNode.y !== 0) {
151
+ if (
152
+ !isNaN(oNode.x) &&
153
+ oNode.x !== 0 &&
154
+ !isNaN(oNode.y) &&
155
+ oNode.y !== 0
156
+ ) {
135
157
  allHaveNoPosition = false;
136
158
  } else {
137
159
  oNode.x = Math.random() * 100;
@@ -148,7 +170,12 @@ export class ComboCombinedLayout extends Base {
148
170
  // 代表节点的节点
149
171
  const oNode: Node = { ...node };
150
172
  outerNodes.push(oNode);
151
- if (!isNaN(oNode.x) && oNode.x !== 0 && !isNaN(oNode.y) && oNode.y !== 0) {
173
+ if (
174
+ !isNaN(oNode.x) &&
175
+ oNode.x !== 0 &&
176
+ !isNaN(oNode.y) &&
177
+ oNode.y !== 0
178
+ ) {
152
179
  allHaveNoPosition = false;
153
180
  } else {
154
181
  oNode.x = Math.random() * 100;
@@ -161,12 +188,14 @@ export class ComboCombinedLayout extends Base {
161
188
  const sourceAncestorId = nodeAncestorIdMap[edge.source] || edge.source;
162
189
  const targetAncestorId = nodeAncestorIdMap[edge.target] || edge.target;
163
190
  // 若两个点的祖先都在力导图的节点中,且是不同的节点,创建一条链接两个祖先的边到力导图的边中
164
- if (sourceAncestorId !== targetAncestorId &&
191
+ if (
192
+ sourceAncestorId !== targetAncestorId &&
165
193
  outerNodeIds.includes(sourceAncestorId) &&
166
- outerNodeIds.includes(targetAncestorId)) {
194
+ outerNodeIds.includes(targetAncestorId)
195
+ ) {
167
196
  outerEdges.push({
168
197
  source: sourceAncestorId,
169
- target: targetAncestorId
198
+ target: targetAncestorId,
170
199
  });
171
200
  }
172
201
  });
@@ -179,19 +208,22 @@ export class ComboCombinedLayout extends Base {
179
208
  } else {
180
209
  const outerData = {
181
210
  nodes: outerNodes,
182
- edges: outerEdges
211
+ edges: outerEdges,
183
212
  };
184
213
 
185
214
  // 需要使用一个同步的布局
186
215
  // @ts-ignore
187
- const outerLayout = this.outerLayout || new GForceLayout({
188
- gravity: 1,
189
- factor: 4,
190
- linkDistance: (edge: any, source: any, target: any) => {
191
- const nodeSize = ((source.size?.[0] || 30) + (target.size?.[0] || 30)) / 2;
192
- return Math.min(nodeSize * 1.5, 700);
193
- }
194
- });
216
+ const outerLayout =
217
+ this.outerLayout ||
218
+ new GForceLayout({
219
+ gravity: 1,
220
+ factor: 4,
221
+ linkDistance: (edge: any, source: any, target: any) => {
222
+ const nodeSize =
223
+ ((source.size?.[0] || 30) + (target.size?.[0] || 30)) / 2;
224
+ return Math.min(nodeSize * 1.5, 700);
225
+ },
226
+ });
195
227
  const outerLayoutType = outerLayout.getType?.();
196
228
  outerLayout.updateCfg({
197
229
  center,
@@ -201,7 +233,8 @@ export class ComboCombinedLayout extends Base {
201
233
  });
202
234
  // 若所有 outerNodes 都没有位置,且 outerLayout 是力导家族的布局,则先执行 preset mds 或 grid
203
235
  if (allHaveNoPosition && FORCE_LAYOUT_TYPE_MAP[outerLayoutType]) {
204
- const outerLayoutPreset = outerNodes.length < 100 ? new MDSLayout() : new GridLayout();
236
+ const outerLayoutPreset =
237
+ outerNodes.length < 100 ? new MDSLayout() : new GridLayout();
205
238
  outerLayoutPreset.layout(outerData);
206
239
  }
207
240
  outerLayout.layout(outerData);
@@ -235,8 +268,8 @@ export class ComboCombinedLayout extends Base {
235
268
  if (!innerGraph) continue;
236
269
  innerGraph.nodes.forEach((node: OutNode) => {
237
270
  if (!innerGraph.visited) {
238
- node.x += (innerGraph.x || 0);
239
- node.y += (innerGraph.y || 0);
271
+ node.x += innerGraph.x || 0;
272
+ node.y += innerGraph.y || 0;
240
273
  }
241
274
  if (nodeMap[node.id]) {
242
275
  nodeMap[node.id].x = node.x;
@@ -257,7 +290,9 @@ export class ComboCombinedLayout extends Base {
257
290
  const innerGraphs: any = {};
258
291
 
259
292
  // @ts-ignore
260
- const innerGraphLayout: any = this.innerLayout || (new ConcentricLayout({ sortBy: 'id' }));
293
+ const innerGraphLayout: any =
294
+ this.innerLayout ||
295
+ new ConcentricLayout({ type: 'concentric', sortBy: 'id' });
261
296
  innerGraphLayout.center = [0, 0];
262
297
  innerGraphLayout.preventOverlap = true;
263
298
  innerGraphLayout.nodeSpacing = spacing;
@@ -270,11 +305,13 @@ export class ComboCombinedLayout extends Base {
270
305
  if (!treeNode.children?.length) {
271
306
  // 空 combo
272
307
  if (treeNode.itemType === 'combo') {
273
- const treeNodeSize = padding ? [padding * 2, padding * 2] : [30, 30];
308
+ const treeNodeSize = padding
309
+ ? [padding * 2, padding * 2]
310
+ : [30, 30];
274
311
  innerGraphs[treeNode.id] = {
275
312
  id: treeNode.id,
276
313
  nodes: [],
277
- size: treeNodeSize
314
+ size: treeNodeSize,
278
315
  };
279
316
  }
280
317
  } else {
@@ -287,19 +324,25 @@ export class ComboCombinedLayout extends Base {
287
324
  const innerGraphNodeIds = innerGraphNodes.map((node) => node.id);
288
325
  const innerGraphData = {
289
326
  nodes: innerGraphNodes,
290
- edges: edges.filter((edge) => innerGraphNodeIds.includes(edge.source) && innerGraphNodeIds.includes(edge.target))
327
+ edges: edges.filter(
328
+ (edge) =>
329
+ innerGraphNodeIds.includes(edge.source) &&
330
+ innerGraphNodeIds.includes(edge.target)
331
+ ),
291
332
  };
292
333
  let minNodeSize = Infinity;
293
334
  innerGraphNodes.forEach((node) => {
294
335
  // @ts-ignore
295
- if (!node.size) node.size = innerGraphs[node.id]?.size || nodeSize?.(node) || [30, 30];
336
+ if (!node.size)
337
+ node.size = innerGraphs[node.id]?.size ||
338
+ (nodeSize as Function)?.(node) || [30, 30];
296
339
  if (isNumber(node.size)) node.size = [node.size, node.size];
297
340
  if (minNodeSize > node.size[0]) minNodeSize = node.size[0];
298
341
  if (minNodeSize > node.size[1]) minNodeSize = node.size[1];
299
342
  });
300
343
 
301
344
  // 根据节点数量、spacing,调整布局参数
302
-
345
+
303
346
  innerGraphLayout.layout(innerGraphData);
304
347
  const { minX, minY, maxX, maxY } = getLayoutBBox(innerGraphNodes);
305
348
  // move the innerGraph to [0, 0],for later controled by parent layout
@@ -308,11 +351,14 @@ export class ComboCombinedLayout extends Base {
308
351
  node.x -= center.x;
309
352
  node.y -= center.y;
310
353
  });
311
- const innerGraphSize = Math.max(maxX - minX, maxY - minY, minNodeSize) + padding * 2;
354
+ const innerGraphWidth =
355
+ Math.max(maxX - minX, minNodeSize) + padding * 2;
356
+ const innerGraphHeight =
357
+ Math.max(maxY - minY, minNodeSize) + padding * 2;
312
358
  innerGraphs[treeNode.id] = {
313
359
  id: treeNode.id,
314
360
  nodes: innerGraphNodes,
315
- size: [innerGraphSize, innerGraphSize]
361
+ size: [innerGraphWidth, innerGraphHeight],
316
362
  };
317
363
  }
318
364
  return true;
@@ -347,8 +393,10 @@ export class ComboCombinedLayout extends Base {
347
393
  if (isArray(d.size)) {
348
394
  const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
349
395
  return (res + spacing) / 2;
350
- } if (isObject(d.size)) {
351
- const res = d.size.width > d.size.height ? d.size.width : d.size.height;
396
+ }
397
+ if (isObject(d.size)) {
398
+ const res =
399
+ d.size.width > d.size.height ? d.size.width : d.size.height;
352
400
  return (res + spacing) / 2;
353
401
  }
354
402
  return (d.size + spacing) / 2;
@@ -392,6 +440,6 @@ export class ComboCombinedLayout extends Base {
392
440
  this.comboPadding = comboPaddingFunc;
393
441
  }
394
442
  public getType() {
395
- return "comboCombined";
443
+ return 'comboCombined';
396
444
  }
397
445
  }
@@ -9,8 +9,8 @@ import {
9
9
  OutNode,
10
10
  Edge,
11
11
  Matrix,
12
- RadialLayoutOptions
13
- } from "../types";
12
+ RadialLayoutOptions,
13
+ } from '../types';
14
14
  import {
15
15
  isNaN,
16
16
  isArray,
@@ -19,13 +19,13 @@ import {
19
19
  isString,
20
20
  floydWarshall,
21
21
  getAdjMatrix,
22
- isObject
23
- } from "../../util";
24
- import { Base } from "../base";
25
- import MDS from "./mds";
22
+ isObject,
23
+ } from '../../util';
24
+ import { Base } from '../base';
25
+ import MDS from './mds';
26
26
  import RadialNonoverlapForce, {
27
- RadialNonoverlapForceParam
28
- } from "./radialNonoverlapForce";
27
+ RadialNonoverlapForceParam,
28
+ } from './radialNonoverlapForce';
29
29
 
30
30
  type INode = OutNode & {
31
31
  size?: number | PointTuple;
@@ -123,6 +123,8 @@ export class RadialLayout extends Base {
123
123
 
124
124
  public onLayoutEnd: () => void;
125
125
 
126
+ public initWithMDS: boolean;
127
+
126
128
  constructor(options?: RadialLayoutOptions) {
127
129
  super();
128
130
  this.updateCfg(options);
@@ -140,7 +142,8 @@ export class RadialLayout extends Base {
140
142
  strictRadial: true,
141
143
  maxPreventOverlapIteration: 200,
142
144
  sortBy: undefined,
143
- sortStrength: 10
145
+ sortStrength: 10,
146
+ initWithMDS: true,
144
147
  };
145
148
  }
146
149
 
@@ -156,10 +159,10 @@ export class RadialLayout extends Base {
156
159
  return;
157
160
  }
158
161
 
159
- if (!self.width && typeof window !== "undefined") {
162
+ if (!self.width && typeof window !== 'undefined') {
160
163
  self.width = window.innerWidth;
161
164
  }
162
- if (!self.height && typeof window !== "undefined") {
165
+ if (!self.height && typeof window !== 'undefined') {
163
166
  self.height = window.innerHeight;
164
167
  }
165
168
  if (!self.center) {
@@ -245,25 +248,34 @@ export class RadialLayout extends Base {
245
248
  self.weights = W;
246
249
 
247
250
  // the initial positions from mds
248
- const mds = new MDS({ linkDistance, distances: eIdealD });
249
- let positions = mds.layout();
250
- positions.forEach((p: PointTuple) => {
251
- if (isNaN(p[0])) {
252
- p[0] = Math.random() * linkDistance;
253
- }
254
- if (isNaN(p[1])) {
255
- p[1] = Math.random() * linkDistance;
256
- }
257
- });
258
- self.positions = positions;
259
- positions.forEach((p: PointTuple, i: number) => {
251
+ if (self.initWithMDS) {
252
+ const mds = new MDS({ linkDistance, distances: eIdealD });
253
+ let positions = mds.layout();
254
+ positions.forEach((p: PointTuple) => {
255
+ if (isNaN(p[0])) {
256
+ p[0] = Math.random() * linkDistance;
257
+ }
258
+ if (isNaN(p[1])) {
259
+ p[1] = Math.random() * linkDistance;
260
+ }
261
+ });
262
+ self.positions = positions;
263
+ } else {
264
+ self.positions = nodes.map((node, i) => {
265
+ return [
266
+ (Math.random() - 0.5) * eIdealD[i][focusIndex],
267
+ (Math.random() - 0.5) * eIdealD[i][focusIndex],
268
+ ];
269
+ });
270
+ }
271
+ self.positions.forEach((p: PointTuple, i: number) => {
260
272
  nodes[i].x = p[0] + center[0];
261
273
  nodes[i].y = p[1] + center[1];
262
274
  });
263
275
  // move the graph to origin, centered at focusNode
264
- positions.forEach((p: PointTuple) => {
265
- p[0] -= positions[focusIndex][0];
266
- p[1] -= positions[focusIndex][1];
276
+ self.positions.forEach((p: PointTuple) => {
277
+ p[0] -= (self.positions as PointTuple[])[focusIndex][0];
278
+ p[1] -= (self.positions as PointTuple[])[focusIndex][1];
267
279
  });
268
280
  self.run();
269
281
  const preventOverlap = self.preventOverlap;
@@ -287,9 +299,11 @@ export class RadialLayout extends Base {
287
299
  if (isArray(d.size)) {
288
300
  const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
289
301
  return res + nodeSpacingFunc(d);
290
- } if (isObject(d.size)) {
291
- const res = d.size.width > d.size.height ? d.size.width : d.size.height;
292
- return res + nodeSpacingFunc(d);
302
+ }
303
+ if (isObject(d.size)) {
304
+ const res =
305
+ d.size.width > d.size.height ? d.size.width : d.size.height;
306
+ return res + nodeSpacingFunc(d);
293
307
  }
294
308
  return d.size + nodeSpacingFunc(d);
295
309
  }
@@ -307,20 +321,20 @@ export class RadialLayout extends Base {
307
321
  nodes,
308
322
  nodeSizeFunc,
309
323
  adjMatrix,
310
- positions,
324
+ positions: self.positions,
311
325
  radii,
312
326
  height,
313
327
  width,
314
328
  strictRadial,
315
329
  focusID: focusIndex,
316
330
  iterations: self.maxPreventOverlapIteration || 200,
317
- k: positions.length / 4.5
331
+ k: self.positions.length / 4.5,
318
332
  };
319
333
  const nonoverlapForce = new RadialNonoverlapForce(nonoverlapForceParams);
320
- positions = nonoverlapForce.layout();
334
+ self.positions = nonoverlapForce.layout();
321
335
  }
322
336
  // move the graph to center
323
- positions.forEach((p: PointTuple, i: number) => {
337
+ self.positions.forEach((p: PointTuple, i: number) => {
324
338
  nodes[i].x = p[0] + center[0];
325
339
  nodes[i].y = p[1] + center[1];
326
340
  });
@@ -329,7 +343,7 @@ export class RadialLayout extends Base {
329
343
 
330
344
  return {
331
345
  nodes,
332
- edges
346
+ edges,
333
347
  };
334
348
  }
335
349
 
@@ -413,7 +427,7 @@ export class RadialLayout extends Base {
413
427
  newRow.push(0);
414
428
  } else if (radii[i] === radii[j]) {
415
429
  // i and j are on the same circle
416
- if (self.sortBy === "data") {
430
+ if (self.sortBy === 'data') {
417
431
  // sort the nodes on the same circle according to the ordering of the data
418
432
  newRow.push(
419
433
  (v * (Math.abs(i - j) * self.sortStrength)) /
@@ -495,6 +509,6 @@ export class RadialLayout extends Base {
495
509
  }
496
510
 
497
511
  public getType() {
498
- return "radial";
512
+ return 'radial';
499
513
  }
500
514
  }