@deck.gl-community/graph-layers 9.0.1 → 9.1.0-beta.2

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.
Files changed (182) hide show
  1. package/LICENSE +1 -1
  2. package/dist/core/cache.d.ts +1 -0
  3. package/dist/core/cache.d.ts.map +1 -0
  4. package/dist/core/constants.d.ts +1 -0
  5. package/dist/core/constants.d.ts.map +1 -0
  6. package/dist/core/constants.js +1 -1
  7. package/dist/core/graph-engine.d.ts +17 -7
  8. package/dist/core/graph-engine.d.ts.map +1 -0
  9. package/dist/core/graph-engine.js +14 -5
  10. package/dist/core/graph-layout.d.ts +69 -0
  11. package/dist/core/graph-layout.d.ts.map +1 -0
  12. package/dist/core/{base-layout.js → graph-layout.js} +63 -80
  13. package/dist/core/interaction-manager.d.ts +3 -2
  14. package/dist/core/interaction-manager.d.ts.map +1 -0
  15. package/dist/core/interaction-manager.js +1 -1
  16. package/dist/{core → graph}/edge.d.ts +20 -18
  17. package/dist/graph/edge.d.ts.map +1 -0
  18. package/dist/{core → graph}/edge.js +12 -15
  19. package/dist/{core → graph}/graph.d.ts +37 -33
  20. package/dist/graph/graph.d.ts.map +1 -0
  21. package/dist/{core → graph}/graph.js +44 -37
  22. package/dist/{core → graph}/node.d.ts +22 -21
  23. package/dist/graph/node.d.ts.map +1 -0
  24. package/dist/{core → graph}/node.js +16 -18
  25. package/dist/index.cjs +1181 -434
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +22 -19
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +24 -23
  30. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-fragment.glsl.d.ts +1 -0
  31. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-fragment.glsl.d.ts.map +1 -0
  32. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex-tf.glsl.d.ts +1 -0
  33. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex-tf.glsl.d.ts.map +1 -0
  34. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex.glsl.d.ts +1 -0
  35. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex.glsl.d.ts.map +1 -0
  36. package/dist/layers/common-layers/flow-path-layer/flow-path-layer.d.ts +1 -0
  37. package/dist/layers/common-layers/flow-path-layer/flow-path-layer.d.ts.map +1 -0
  38. package/dist/layers/common-layers/marker-layer/atlas-data-url.d.ts +1 -0
  39. package/dist/layers/common-layers/marker-layer/atlas-data-url.d.ts.map +1 -0
  40. package/dist/layers/common-layers/marker-layer/marker-layer.d.ts +1 -0
  41. package/dist/layers/common-layers/marker-layer/marker-layer.d.ts.map +1 -0
  42. package/dist/layers/common-layers/marker-layer/marker-layer.js +2 -2
  43. package/dist/layers/common-layers/marker-layer/marker-list.d.ts +1 -0
  44. package/dist/layers/common-layers/marker-layer/marker-list.d.ts.map +1 -0
  45. package/dist/layers/common-layers/marker-layer/marker-mapping.d.ts +1 -0
  46. package/dist/layers/common-layers/marker-layer/marker-mapping.d.ts.map +1 -0
  47. package/dist/layers/common-layers/spline-layer/spline-layer.d.ts +1 -0
  48. package/dist/layers/common-layers/spline-layer/spline-layer.d.ts.map +1 -0
  49. package/dist/layers/common-layers/zoomable-text-layer/zoomable-text-layer.d.ts +1 -0
  50. package/dist/layers/common-layers/zoomable-text-layer/zoomable-text-layer.d.ts.map +1 -0
  51. package/dist/layers/edge-layer.d.ts +4 -3
  52. package/dist/layers/edge-layer.d.ts.map +1 -0
  53. package/dist/layers/edge-layer.js +4 -4
  54. package/dist/layers/edge-layers/curved-edge-layer.d.ts +2 -1
  55. package/dist/layers/edge-layers/curved-edge-layer.d.ts.map +1 -0
  56. package/dist/layers/edge-layers/curved-edge-layer.js +1 -1
  57. package/dist/layers/edge-layers/edge-label-layer.d.ts +2 -1
  58. package/dist/layers/edge-layers/edge-label-layer.d.ts.map +1 -0
  59. package/dist/layers/edge-layers/edge-label-layer.js +1 -1
  60. package/dist/layers/edge-layers/flow-layer.d.ts +2 -1
  61. package/dist/layers/edge-layers/flow-layer.d.ts.map +1 -0
  62. package/dist/layers/edge-layers/flow-layer.js +1 -1
  63. package/dist/layers/edge-layers/path-edge-layer.d.ts +1 -0
  64. package/dist/layers/edge-layers/path-edge-layer.d.ts.map +1 -0
  65. package/dist/layers/edge-layers/straight-line-edge-layer.d.ts +1 -0
  66. package/dist/layers/edge-layers/straight-line-edge-layer.d.ts.map +1 -0
  67. package/dist/layers/graph-layer.d.ts +54 -13
  68. package/dist/layers/graph-layer.d.ts.map +1 -0
  69. package/dist/layers/graph-layer.js +93 -51
  70. package/dist/layers/node-layers/circle-layer.d.ts +1 -0
  71. package/dist/layers/node-layers/circle-layer.d.ts.map +1 -0
  72. package/dist/layers/node-layers/image-layer.d.ts +1 -0
  73. package/dist/layers/node-layers/image-layer.d.ts.map +1 -0
  74. package/dist/layers/node-layers/label-layer.d.ts +2 -1
  75. package/dist/layers/node-layers/label-layer.d.ts.map +1 -0
  76. package/dist/layers/node-layers/label-layer.js +1 -1
  77. package/dist/layers/node-layers/{path-rounded-rectange-layer.d.ts → path-rounded-rectangle-layer.d.ts} +1 -0
  78. package/dist/layers/node-layers/path-rounded-rectangle-layer.d.ts.map +1 -0
  79. package/dist/layers/node-layers/{path-rounded-rectange-layer.js → path-rounded-rectangle-layer.js} +1 -1
  80. package/dist/layers/node-layers/rectangle-layer.d.ts +1 -0
  81. package/dist/layers/node-layers/rectangle-layer.d.ts.map +1 -0
  82. package/dist/layers/node-layers/rounded-rectangle-layer-fragment.d.ts +2 -1
  83. package/dist/layers/node-layers/rounded-rectangle-layer-fragment.d.ts.map +1 -0
  84. package/dist/layers/node-layers/rounded-rectangle-layer-fragment.js +1 -3
  85. package/dist/layers/node-layers/rounded-rectangle-layer.d.ts +14 -4
  86. package/dist/layers/node-layers/rounded-rectangle-layer.d.ts.map +1 -0
  87. package/dist/layers/node-layers/rounded-rectangle-layer.js +27 -13
  88. package/dist/layers/node-layers/zoomable-marker-layer.d.ts +2 -1
  89. package/dist/layers/node-layers/zoomable-marker-layer.d.ts.map +1 -0
  90. package/dist/layers/node-layers/zoomable-marker-layer.js +1 -1
  91. package/dist/layouts/d3-force/d3-force-layout.d.ts +13 -3
  92. package/dist/layouts/d3-force/d3-force-layout.d.ts.map +1 -0
  93. package/dist/layouts/d3-force/d3-force-layout.js +12 -12
  94. package/dist/layouts/d3-force/worker.d.ts +1 -0
  95. package/dist/layouts/d3-force/worker.d.ts.map +1 -0
  96. package/dist/layouts/experimental/force-multi-graph-layout.d.ts +43 -0
  97. package/dist/layouts/experimental/force-multi-graph-layout.d.ts.map +1 -0
  98. package/dist/layouts/experimental/force-multi-graph-layout.js +226 -0
  99. package/dist/layouts/experimental/hive-plot-layout.d.ts +34 -0
  100. package/dist/layouts/experimental/hive-plot-layout.d.ts.map +1 -0
  101. package/dist/layouts/experimental/hive-plot-layout.js +142 -0
  102. package/dist/layouts/experimental/radial-layout.d.ts +28 -0
  103. package/dist/layouts/experimental/radial-layout.d.ts.map +1 -0
  104. package/dist/layouts/experimental/radial-layout.js +164 -0
  105. package/dist/layouts/gpu-force/gpu-force-layout.d.ts +16 -3
  106. package/dist/layouts/gpu-force/gpu-force-layout.d.ts.map +1 -0
  107. package/dist/layouts/gpu-force/gpu-force-layout.js +21 -19
  108. package/dist/layouts/gpu-force/worker.d.ts +1 -0
  109. package/dist/layouts/gpu-force/worker.d.ts.map +1 -0
  110. package/dist/layouts/simple-layout.d.ts +42 -0
  111. package/dist/layouts/simple-layout.d.ts.map +1 -0
  112. package/dist/layouts/{simple-layout/simple-layout.js → simple-layout.js} +8 -7
  113. package/dist/loaders/create-graph.d.ts +13 -0
  114. package/dist/loaders/create-graph.d.ts.map +1 -0
  115. package/dist/{utils → loaders}/create-graph.js +9 -4
  116. package/dist/loaders/edge-parsers.d.ts +3 -6
  117. package/dist/loaders/edge-parsers.d.ts.map +1 -0
  118. package/dist/loaders/edge-parsers.js +1 -1
  119. package/dist/loaders/json-loader.d.ts +3 -2
  120. package/dist/loaders/json-loader.d.ts.map +1 -0
  121. package/dist/loaders/json-loader.js +4 -4
  122. package/dist/loaders/node-parsers.d.ts +3 -3
  123. package/dist/loaders/node-parsers.d.ts.map +1 -0
  124. package/dist/loaders/node-parsers.js +1 -1
  125. package/dist/loaders/simple-json-graph-loader.d.ts +12 -0
  126. package/dist/loaders/simple-json-graph-loader.d.ts.map +1 -0
  127. package/dist/loaders/simple-json-graph-loader.js +20 -0
  128. package/dist/loaders/table-graph-loader.d.ts +17 -0
  129. package/dist/loaders/table-graph-loader.d.ts.map +1 -0
  130. package/dist/loaders/table-graph-loader.js +91 -0
  131. package/dist/style/style-property.d.ts +1 -0
  132. package/dist/style/style-property.d.ts.map +1 -0
  133. package/dist/style/style-property.js +1 -1
  134. package/dist/style/style-sheet.d.ts +1 -0
  135. package/dist/style/style-sheet.d.ts.map +1 -0
  136. package/dist/style/style-sheet.js +3 -3
  137. package/dist/utils/layer-utils.d.ts +1 -0
  138. package/dist/utils/layer-utils.d.ts.map +1 -0
  139. package/dist/utils/log.d.ts +2 -1
  140. package/dist/utils/log.d.ts.map +1 -0
  141. package/dist/utils/log.js +3 -3
  142. package/dist/utils/polygon-calculations.d.ts +1 -0
  143. package/dist/utils/polygon-calculations.d.ts.map +1 -0
  144. package/dist/widgets/long-press-button.d.ts +13 -0
  145. package/dist/widgets/long-press-button.d.ts.map +1 -0
  146. package/dist/widgets/long-press-button.js +31 -0
  147. package/dist/widgets/view-control-widget.d.ts +78 -0
  148. package/dist/widgets/view-control-widget.d.ts.map +1 -0
  149. package/dist/widgets/view-control-widget.js +194 -0
  150. package/package.json +8 -7
  151. package/src/core/graph-engine.ts +30 -10
  152. package/src/core/graph-layout.ts +146 -0
  153. package/src/core/interaction-manager.ts +2 -2
  154. package/src/{core → graph}/edge.ts +19 -17
  155. package/src/{core → graph}/graph.ts +51 -36
  156. package/src/{core → graph}/node.ts +21 -20
  157. package/src/index.ts +28 -28
  158. package/src/layers/graph-layer.ts +133 -46
  159. package/src/layers/node-layers/rounded-rectangle-layer-fragment.ts +1 -3
  160. package/src/layers/node-layers/rounded-rectangle-layer.ts +34 -10
  161. package/src/layouts/d3-force/d3-force-layout.ts +21 -11
  162. package/src/layouts/experimental/force-multi-graph-layout.ts +268 -0
  163. package/src/layouts/experimental/hive-plot-layout.ts +182 -0
  164. package/src/layouts/experimental/radial-layout.ts +210 -0
  165. package/src/layouts/gpu-force/gpu-force-layout.ts +32 -17
  166. package/src/layouts/{simple-layout/simple-layout.ts → simple-layout.ts} +34 -19
  167. package/src/{utils → loaders}/create-graph.ts +9 -4
  168. package/src/loaders/edge-parsers.ts +2 -1
  169. package/src/loaders/json-loader.ts +1 -1
  170. package/src/loaders/node-parsers.ts +2 -1
  171. package/src/loaders/simple-json-graph-loader.ts +28 -0
  172. package/src/loaders/table-graph-loader.ts +124 -0
  173. package/src/utils/log.ts +3 -3
  174. package/src/widgets/long-press-button.tsx +50 -0
  175. package/src/widgets/view-control-widget.tsx +337 -0
  176. package/dist/core/base-layout.d.ts +0 -71
  177. package/dist/layouts/simple-layout/simple-layout.d.ts +0 -22
  178. package/dist/utils/create-graph.d.ts +0 -8
  179. package/src/core/base-layout.ts +0 -154
  180. /package/src/layers/node-layers/{path-rounded-rectange-layer.ts → path-rounded-rectangle-layer.ts} +0 -0
  181. /package/src/layouts/d3-force/{worker.ts → worker.js} +0 -0
  182. /package/src/layouts/gpu-force/{worker.ts → worker.js} +0 -0
@@ -2,10 +2,15 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import type {CompositeLayerProps} from '@deck.gl/core';
5
6
  import {COORDINATE_SYSTEM, CompositeLayer} from '@deck.gl/core';
6
7
 
7
- import {Stylesheet} from '../style/style-sheet';
8
8
  import {NODE_TYPE, EDGE_DECORATOR_TYPE} from '../core/constants';
9
+ import {Graph} from '../graph/graph';
10
+ import {GraphLayout} from '../core/graph-layout';
11
+ import {GraphEngine} from '../core/graph-engine';
12
+
13
+ import {Stylesheet} from '../style/style-sheet';
9
14
  import {mixedGetPosition} from '../utils/layer-utils';
10
15
  import {InteractionManager} from '../core/interaction-manager';
11
16
 
@@ -17,7 +22,7 @@ import {ImageLayer} from './node-layers/image-layer';
17
22
  import {LabelLayer} from './node-layers/label-layer';
18
23
  import {RectangleLayer} from './node-layers/rectangle-layer';
19
24
  import {RoundedRectangleLayer} from './node-layers/rounded-rectangle-layer';
20
- import {PathBasedRoundedRectangleLayer} from './node-layers/path-rounded-rectange-layer';
25
+ import {PathBasedRoundedRectangleLayer} from './node-layers/path-rounded-rectangle-layer';
21
26
  import {ZoomableMarkerLayer} from './node-layers/zoomable-marker-layer';
22
27
 
23
28
  // edge layers
@@ -25,6 +30,8 @@ import {EdgeLayer} from './edge-layer';
25
30
  import {EdgeLabelLayer} from './edge-layers/edge-label-layer';
26
31
  import {FlowLayer} from './edge-layers/flow-layer';
27
32
 
33
+ import {JSONLoader} from '../loaders/json-loader';
34
+
28
35
  const NODE_LAYER_MAP = {
29
36
  [NODE_TYPE.RECTANGLE]: RectangleLayer,
30
37
  [NODE_TYPE.ROUNDED_RECTANGLE]: RoundedRectangleLayer,
@@ -46,32 +53,73 @@ const SHARED_LAYER_PROPS = {
46
53
  depthTest: false
47
54
  }
48
55
  };
49
- const defaultProps = {
56
+
57
+ export type GraphLayerProps = CompositeLayerProps & _GraphLayerProps;
58
+
59
+ export type _GraphLayerProps = {
60
+ graph?: Graph;
61
+ layout?: GraphLayout;
62
+ graphLoader?: (opts: {json: any}) => Graph;
63
+ engine?: GraphEngine;
64
+
50
65
  // an array of styles for layers
51
- nodeStyle: [],
52
- nodeEvents: {
53
- onMouseLeave: () => {},
54
- onHover: () => {},
55
- onMouseEnter: () => {},
56
- onClick: () => {},
57
- onDrag: () => {}
58
- },
59
- edgeStyle: {
60
- color: 'black',
61
- strokeWidth: 1,
62
- // an array of styles for layers
63
- decorators: []
64
- },
65
- edgeEvents: {
66
- onClick: () => {},
67
- onHover: () => {}
68
- },
69
- enableDragging: false
66
+ nodeStyle?: any[];
67
+ edgeStyle?: {
68
+ stroke?: string;
69
+ strokeWidth?: number;
70
+ /** an array of styles for layers */
71
+ decorators?: any[];
72
+ };
73
+ nodeEvents?: {
74
+ onMouseLeave?: () => void;
75
+ onHover?: () => void;
76
+ onMouseEnter?: () => void;
77
+ onClick?: () => void;
78
+ onDrag?: () => void;
79
+ };
80
+ edgeEvents?: {
81
+ onClick: () => void;
82
+ onHover: () => void;
83
+ };
84
+ enableDragging?: boolean;
70
85
  };
71
86
 
72
- export class GraphLayer extends CompositeLayer {
73
- static defaultProps = {
74
- pickable: true
87
+ export class GraphLayer extends CompositeLayer<GraphLayerProps> {
88
+ static layerName = 'GraphLayer';
89
+
90
+ static defaultProps: Required<_GraphLayerProps> = {
91
+ // Composite layer props
92
+ // @ts-expect-error composite layer props
93
+ pickable: true,
94
+
95
+ // Graph props
96
+ graphLoader: JSONLoader,
97
+
98
+ nodeStyle: [],
99
+ nodeEvents: {
100
+ onMouseLeave: () => {},
101
+ onHover: () => {},
102
+ onMouseEnter: () => {},
103
+ onClick: () => {},
104
+ onDrag: () => {}
105
+ },
106
+ edgeStyle: {
107
+ stroke: 'black',
108
+ strokeWidth: 1,
109
+ // an array of styles for layers
110
+ decorators: []
111
+ },
112
+ edgeEvents: {
113
+ onClick: () => {},
114
+ onHover: () => {}
115
+ },
116
+ enableDragging: false
117
+ };
118
+
119
+ // @ts-expect-error Some typescript confusion due to override of base class state
120
+ state!: CompositeLayer<GraphLayerProps>['state'] & {
121
+ interactionManager: InteractionManager;
122
+ graphEngine?: GraphEngine;
75
123
  };
76
124
 
77
125
  forceUpdate = () => {
@@ -81,35 +129,76 @@ export class GraphLayer extends CompositeLayer {
81
129
  }
82
130
  };
83
131
 
84
- constructor(props) {
132
+ constructor(props: GraphLayerProps & CompositeLayerProps) {
85
133
  super(props);
86
-
87
- // added or removed a node, or in general something layout related changed
88
- props.engine.addEventListener('onLayoutChange', this.forceUpdate);
89
134
  }
90
135
 
91
136
  initializeState() {
92
- const interactionManager = new InteractionManager(this.props as any, () => this.forceUpdate());
93
- this.state = {interactionManager};
137
+ this.state = {
138
+ interactionManager: new InteractionManager(this.props as any, () => this.forceUpdate())
139
+ };
140
+ const engine = this.props.engine;
141
+ this._setGraphEngine(engine);
94
142
  }
95
143
 
96
144
  shouldUpdateState({changeFlags}) {
97
145
  return changeFlags.dataChanged || changeFlags.propsChanged;
98
146
  }
99
147
 
100
- updateState({props}) {
101
- (this.state.interactionManager as any).updateProps(props);
148
+ updateState({props, oldProps, changeFlags}) {
149
+ if (
150
+ changeFlags.dataChanged &&
151
+ props.data &&
152
+ !(Array.isArray(props.data) && props.data.length === 0)
153
+ ) {
154
+ // console.log(props.data);
155
+ const graph = this.props.graphLoader({json: props.data});
156
+ const layout = this.props.layout;
157
+ const graphEngine = new GraphEngine({graph, layout});
158
+ this._setGraphEngine(graphEngine);
159
+ this.state.interactionManager.updateProps(props);
160
+ this.forceUpdate();
161
+ } else if (changeFlags.propsChanged && props.graph !== oldProps.graph) {
162
+ const graphEngine = new GraphEngine({graph: props.graph, layout: props.layout});
163
+ this._setGraphEngine(graphEngine);
164
+ this.state.interactionManager.updateProps(props);
165
+ this.forceUpdate();
166
+ }
102
167
  }
103
168
 
104
169
  finalize() {
105
- (this.props as any).engine.removeEventListener('onLayoutChange', this.forceUpdate);
170
+ this._removeGraphEngine();
171
+ }
172
+
173
+ _setGraphEngine(graphEngine: GraphEngine) {
174
+ if (graphEngine === this.state.graphEngine) {
175
+ return;
176
+ }
177
+
178
+ this._removeGraphEngine();
179
+ if (graphEngine) {
180
+ this.state.graphEngine = graphEngine;
181
+ this.state.graphEngine.run();
182
+ // added or removed a node, or in general something layout related changed
183
+ this.state.graphEngine.addEventListener('onLayoutChange', this.forceUpdate);
184
+ }
185
+ }
186
+
187
+ _removeGraphEngine() {
188
+ if (this.state.graphEngine) {
189
+ this.state.graphEngine.removeEventListener('onLayoutChange', this.forceUpdate);
190
+ this.state.graphEngine.clear();
191
+ this.state.graphEngine = null;
192
+ }
106
193
  }
107
194
 
108
195
  createNodeLayers() {
109
- const {engine, nodeStyle} = this.props as any;
110
- if (!nodeStyle || !Array.isArray(nodeStyle) || nodeStyle.length === 0) {
196
+ const engine = this.state.graphEngine;
197
+ const {nodeStyle} = this.props;
198
+ if (!engine || !nodeStyle || !Array.isArray(nodeStyle) || nodeStyle.length === 0) {
111
199
  return [];
112
200
  }
201
+
113
202
  return nodeStyle.filter(Boolean).map((style, idx) => {
114
203
  const {pickable = true, visible = true, data = (nodes) => nodes, ...restStyle} = style;
115
204
  const LayerType = NODE_LAYER_MAP[style.type];
@@ -139,9 +228,10 @@ export class GraphLayer extends CompositeLayer {
139
228
  }
140
229
 
141
230
  createEdgeLayers() {
142
- const {edgeStyle, engine} = this.props as any;
231
+ const engine = this.state.graphEngine;
232
+ const {edgeStyle} = this.props;
143
233
 
144
- if (!edgeStyle) {
234
+ if (!engine || !edgeStyle) {
145
235
  return [];
146
236
  }
147
237
 
@@ -197,29 +287,26 @@ export class GraphLayer extends CompositeLayer {
197
287
  }
198
288
 
199
289
  onClick(info, event): boolean {
200
- return (this.state.interactionManager as any).onClick(info, event) || false;
290
+ return (this.state.interactionManager.onClick(info, event) as unknown as boolean) || false;
201
291
  }
202
292
 
203
293
  onHover(info, event): boolean {
204
- return (this.state.interactionManager as any).onHover(info, event) || false;
294
+ return (this.state.interactionManager.onHover(info, event) as unknown as boolean) || false;
205
295
  }
206
296
 
207
297
  onDragStart(info, event) {
208
- (this.state.interactionManager as any).onDragStart(info, event);
298
+ this.state.interactionManager.onDragStart(info, event);
209
299
  }
210
300
 
211
301
  onDrag(info, event) {
212
- (this.state.interactionManager as any).onDrag(info, event);
302
+ this.state.interactionManager.onDrag(info, event);
213
303
  }
214
304
 
215
305
  onDragEnd(info, event) {
216
- (this.state.interactionManager as any).onDragEnd(info, event);
306
+ this.state.interactionManager.onDragEnd(info, event);
217
307
  }
218
308
 
219
309
  renderLayers() {
220
310
  return [this.createEdgeLayers(), this.createNodeLayers()];
221
311
  }
222
312
  }
223
-
224
- GraphLayer.layerName = 'GraphLayer';
225
- (GraphLayer as any).defaultProps = defaultProps;
@@ -7,8 +7,6 @@ export const fs = /* glsl */ `\
7
7
 
8
8
  precision highp float;
9
9
 
10
- uniform float cornerRadius;
11
-
12
10
  varying vec4 vFillColor;
13
11
  varying vec2 unitPosition;
14
12
 
@@ -17,7 +15,7 @@ void main(void) {
17
15
  float distToCenter = length(unitPosition);
18
16
 
19
17
  /* Calculate the cutoff radius for the rounded corners */
20
- float threshold = sqrt(2.0) * (1.0 - cornerRadius) + 1.0 * cornerRadius;
18
+ float threshold = sqrt(2.0) * (1.0 - roundedRectangle.cornerRadius) + 1.0 * roundedRectangle.cornerRadius;
21
19
  if (distToCenter <= threshold) {
22
20
  gl_FragColor = vFillColor;
23
21
  } else {
@@ -3,25 +3,49 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  // import {ScatterplotLayer} from '@deck.gl/layers';
6
+ import type {ShaderModule} from '@luma.gl/shadertools';
7
+ import type {Model} from '@luma.gl/engine';
6
8
  import {fs} from './rounded-rectangle-layer-fragment';
7
9
  import {RectangleLayer} from './rectangle-layer';
8
10
 
11
+ const uniformBlock = `\
12
+ uniform roundedRectangleUniforms {
13
+ float cornerRadius;
14
+ } roundedRectangle;
15
+ `;
16
+
17
+ export type RoundedRectangleProps = {
18
+ cornerRadius: number;
19
+ };
20
+
21
+ export const roundedRectangleUniforms = {
22
+ name: 'roundedRectangle',
23
+ vs: uniformBlock,
24
+ fs: uniformBlock,
25
+ uniformTypes: {
26
+ cornerRadius: 'f32'
27
+ }
28
+ } as const satisfies ShaderModule<RoundedRectangleProps>;
29
+
9
30
  export class RoundedRectangleLayer extends RectangleLayer {
10
31
  static layerName = 'RoundedRectangleLayer';
11
- draw({uniforms}) {
12
- super.draw({
13
- uniforms: {
14
- ...uniforms,
15
- cornerRadius: (this.props as any).cornerRadius
16
- }
17
- });
32
+
33
+ draw(props) {
34
+ const {cornerRadius} = this.props as any;
35
+ const roundedRectangleProps: RoundedRectangleProps = {cornerRadius};
36
+ const model = this.state.model as Model;
37
+ model.shaderInputs.setProps({roundedRectangle: roundedRectangleProps});
38
+ super.draw(props);
18
39
  }
19
40
 
20
41
  getShaders() {
21
42
  // use object.assign to make sure we don't overwrite existing fields like `vs`, `modules`...
22
- return Object.assign({}, super.getShaders(undefined!), {
23
- fs
24
- });
43
+ const shaders = super.getShaders(undefined!);
44
+ return {
45
+ ...shaders,
46
+ fs,
47
+ modules: [...shaders.modules, roundedRectangleUniforms]
48
+ };
25
49
  }
26
50
  }
27
51
 
@@ -2,29 +2,39 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {BaseLayout} from '../../core/base-layout';
5
+ import {GraphLayout, GraphLayoutOptions} from '../../core/graph-layout';
6
6
 
7
7
  import {EDGE_TYPE} from '../../core/constants';
8
8
 
9
- const defaultOptions = {
10
- alpha: 0.3,
11
- resumeAlpha: 0.1,
12
- nBodyStrength: -900,
13
- nBodyDistanceMin: 100,
14
- nBodyDistanceMax: 400,
15
- getCollisionRadius: 0
9
+ export type D3ForceLayoutOptions = GraphLayoutOptions & {
10
+ alpha?: number;
11
+ resumeAlpha?: number;
12
+ nBodyStrength?: number;
13
+ nBodyDistanceMin?: number;
14
+ nBodyDistanceMax?: number;
15
+ getCollisionRadius?: number;
16
16
  };
17
17
 
18
- export class D3ForceLayout extends BaseLayout {
18
+ export class D3ForceLayout extends GraphLayout<D3ForceLayoutOptions> {
19
+ static defaultOptions: Required<D3ForceLayoutOptions> = {
20
+ alpha: 0.3,
21
+ resumeAlpha: 0.1,
22
+ nBodyStrength: -900,
23
+ nBodyDistanceMin: 100,
24
+ nBodyDistanceMax: 400,
25
+ getCollisionRadius: 0
26
+ };
27
+
19
28
  protected readonly _name = 'D3';
20
29
  private _positionsByNodeId = new Map();
21
30
  private _graph: any;
22
31
  private _worker: any;
23
- constructor(options) {
32
+
33
+ constructor(options?: D3ForceLayoutOptions) {
24
34
  super(options);
25
35
 
26
36
  this._options = {
27
- ...defaultOptions,
37
+ ...D3ForceLayout.defaultOptions,
28
38
  ...options
29
39
  };
30
40
  }
@@ -0,0 +1,268 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {GraphLayout, GraphLayoutOptions} from '../../core/graph-layout';
6
+ import {Node} from '../../graph/node';
7
+ import {EDGE_TYPE} from '../../core/constants';
8
+ import {Graph} from '../../graph/graph';
9
+ import * as d3 from 'd3-force';
10
+
11
+ export type ForceMultiGraphLayoutOptions = GraphLayoutOptions & {
12
+ alpha?: number;
13
+ nBodyStrength?: number;
14
+ nBodyDistanceMin?: number;
15
+ nBodyDistanceMax?: number;
16
+ };
17
+
18
+ export class ForceMultiGraphLayout extends GraphLayout<ForceMultiGraphLayoutOptions> {
19
+ static defaultOptions = {
20
+ alpha: 3,
21
+ nBodyStrength: -1200,
22
+ nBodyDistanceMin: 100,
23
+ nBodyDistanceMax: 1400
24
+ };
25
+
26
+ _name = 'ForceMultiGraphLayout';
27
+ _graph: Graph;
28
+
29
+ // d3 part
30
+ // custom graph data
31
+ _d3Graph = {nodes: [], edges: []};
32
+ _nodeMap = {};
33
+ _edgeMap = {};
34
+ _simulator;
35
+
36
+ constructor(options: ForceMultiGraphLayoutOptions = {}) {
37
+ super(options);
38
+ this._options = {
39
+ ...ForceMultiGraphLayout.defaultOptions,
40
+ ...options
41
+ };
42
+ }
43
+
44
+ initializeGraph(graph: Graph): void {
45
+ this.updateGraph(graph);
46
+ }
47
+
48
+ _strength = (d3Edge) => {
49
+ if (d3Edge.isVirtual) {
50
+ return 1 / d3Edge.edgeCount;
51
+ }
52
+ const sourceDegree = this._graph.getDegree(d3Edge.source.id);
53
+ const targetDegree = this._graph.getDegree(d3Edge.target.id);
54
+ return 1 / Math.min(sourceDegree, targetDegree);
55
+ };
56
+
57
+ _generateSimulator() {
58
+ if (this._simulator) {
59
+ this._simulator.on('tick', null).on('end', null);
60
+ this._simulator = null;
61
+ }
62
+ const {alpha, nBodyStrength, nBodyDistanceMin, nBodyDistanceMax} = this._options;
63
+
64
+ const g = this._d3Graph;
65
+ this._simulator = d3
66
+ .forceSimulation(g.nodes)
67
+ .force(
68
+ 'edge',
69
+ d3
70
+ .forceLink(g.edges)
71
+ .id((n) => n.id)
72
+ .strength(this._strength)
73
+ )
74
+ .force(
75
+ 'charge',
76
+ d3
77
+ .forceManyBody()
78
+ .strength(nBodyStrength)
79
+ .distanceMin(nBodyDistanceMin)
80
+ .distanceMax(nBodyDistanceMax)
81
+ )
82
+ .force('center', d3.forceCenter())
83
+ .alpha(alpha);
84
+ // register event callbacks
85
+ this._simulator.on('tick', this._onLayoutChange).on('end', this._onLayoutDone);
86
+ }
87
+
88
+ start() {
89
+ this._generateSimulator();
90
+ this._simulator.restart();
91
+ }
92
+
93
+ resume() {
94
+ this._simulator.restart();
95
+ }
96
+
97
+ stop() {
98
+ this._simulator.stop();
99
+ }
100
+
101
+ updateGraph(graph) {
102
+ this._graph = graph;
103
+
104
+ // nodes
105
+ const newNodeMap = {};
106
+ const newD3Nodes = graph.getNodes().map((node) => {
107
+ const oldD3Node = this._nodeMap[node.id];
108
+ const newD3Node = oldD3Node ? oldD3Node : {id: node.id};
109
+ newNodeMap[node.id] = newD3Node;
110
+ return newD3Node;
111
+ });
112
+
113
+ // edges
114
+ // bucket edges between the same source/target node pairs.
115
+ const nodePairs = graph.getEdges().reduce((res, edge) => {
116
+ const nodes = [edge.getSourceNodeId(), edge.getTargetNodeId()];
117
+ // sort the node ids to count the edges with the same pair
118
+ // but different direction (a -> b or b -> a)
119
+ const pairId = nodes.sort().toString();
120
+ // push this edge into the bucket
121
+ if (!res[pairId]) {
122
+ res[pairId] = [edge];
123
+ } else {
124
+ res[pairId].push(edge);
125
+ }
126
+ return res;
127
+ }, {});
128
+
129
+ // go through each pair of edges,
130
+ // if only one edge between two nodes, create a straight line
131
+ // otherwise, create one virtual node and two edges for each edge
132
+ const newD3Edges = [];
133
+ const newEdgeMap = {};
134
+
135
+ Object.keys(nodePairs).forEach((pairId) => {
136
+ const betweenEdges = nodePairs[pairId];
137
+ const firstEdge = betweenEdges[0];
138
+ if (betweenEdges.length === 1) {
139
+ // do nothing, this is a real edge
140
+ const newD3Edge = {
141
+ type: EDGE_TYPE.LINE,
142
+ id: firstEdge.getId(),
143
+ source: newNodeMap[firstEdge.getSourceNodeId()],
144
+ target: newNodeMap[firstEdge.getTargetNodeId()],
145
+ isVirtual: false
146
+ };
147
+ newEdgeMap[firstEdge.getId()] = newD3Edge;
148
+ newD3Edges.push(newD3Edge);
149
+ return;
150
+ }
151
+
152
+ // else reduce to one virtual edge
153
+ const newD3Edge = {
154
+ type: EDGE_TYPE.LINE,
155
+ id: pairId,
156
+ source: newNodeMap[firstEdge.getSourceNodeId()],
157
+ target: newNodeMap[firstEdge.getTargetNodeId()],
158
+ isVirtual: true,
159
+ edgeCount: betweenEdges.length
160
+ };
161
+ newEdgeMap[pairId] = newD3Edge;
162
+ newD3Edges.push(newD3Edge);
163
+
164
+ betweenEdges.forEach((e, idx) => {
165
+ newEdgeMap[e.id] = {
166
+ type: EDGE_TYPE.SPLINE_CURVE,
167
+ id: e.id,
168
+ source: newNodeMap[e.getSourceNodeId()],
169
+ target: newNodeMap[e.getTargetNodeId()],
170
+ virtualEdgeId: pairId,
171
+ isVirtual: true,
172
+ index: idx
173
+ };
174
+ });
175
+ });
176
+
177
+ this._nodeMap = newNodeMap;
178
+ this._d3Graph.nodes = newD3Nodes;
179
+ this._edgeMap = newEdgeMap;
180
+ this._d3Graph.edges = newD3Edges;
181
+ }
182
+
183
+ getNodePosition = (node: Node): [number, number] => {
184
+ const d3Node = this._nodeMap[node.id];
185
+ if (d3Node) {
186
+ return [d3Node.x, d3Node.y];
187
+ }
188
+ // default value
189
+ return [0, 0];
190
+ };
191
+
192
+ getEdgePosition = (edge) => {
193
+ const d3Edge = this._edgeMap[edge.id];
194
+ if (d3Edge) {
195
+ if (!d3Edge.isVirtual) {
196
+ return {
197
+ type: EDGE_TYPE.LINE,
198
+ sourcePosition: [d3Edge.source.x, d3Edge.source.y],
199
+ targetPosition: [d3Edge.target.x, d3Edge.target.y],
200
+ controlPoints: []
201
+ };
202
+ }
203
+ // else, check the referenced virtual edge
204
+ const virtualEdge = this._edgeMap[d3Edge.virtualEdgeId];
205
+ const edgeCount = virtualEdge.edgeCount;
206
+ // get the position of source and target nodes
207
+ const sourcePosition = [virtualEdge.source.x, virtualEdge.source.y];
208
+ const targetPosition = [virtualEdge.target.x, virtualEdge.target.y];
209
+ // calculate a symmetric curve
210
+ const distance = Math.hypot(
211
+ sourcePosition[0] - targetPosition[0],
212
+ sourcePosition[1] - targetPosition[1]
213
+ );
214
+ const index = d3Edge.index;
215
+ // curve direction: inward vs. outward
216
+ const direction = index % 2 ? 1 : -1;
217
+ // if the number of the parallel edges is an even number => symmetric shape
218
+ // otherwise, the 0th node will be a staight line, and rest of them are symmetrical.
219
+ const symmetricShape = edgeCount % 2 === 0;
220
+ const offset =
221
+ Math.max(distance / 10, 5) *
222
+ (symmetricShape ? Math.floor(index / 2 + 1) : Math.ceil(index / 2));
223
+ const controlPoint = computeControlPoint(sourcePosition, targetPosition, direction, offset);
224
+ return {
225
+ type: EDGE_TYPE.SPLINE_CURVE,
226
+ sourcePosition,
227
+ targetPosition,
228
+ controlPoints: [controlPoint]
229
+ };
230
+ }
231
+ // default value
232
+ return {
233
+ type: EDGE_TYPE.LINE,
234
+ sourcePosition: [0, 0],
235
+ targetPosition: [0, 0],
236
+ controlPoints: []
237
+ };
238
+ };
239
+
240
+ lockNodePosition = (node, x, y) => {
241
+ const d3Node = this._nodeMap[node.id];
242
+ d3Node.x = x;
243
+ d3Node.y = y;
244
+ this._onLayoutChange();
245
+ this._onLayoutDone();
246
+ };
247
+ }
248
+
249
+ /**
250
+ * A helper function to compute the control point of a curve
251
+ * @param {number[]} source - the coordinates of source point, ex: [x, y, z]
252
+ * @param {number[]} target - the coordinates of target point, ex: [x, y, z]
253
+ * @param {number} direction - the direction of the curve, 1 or -1
254
+ * @param {number} offset - offset from the midpoint
255
+ * @return {number[]} - the coordinates of the control point
256
+ */
257
+ function computeControlPoint(source, target, direction, offset) {
258
+ const midPoint = [(source[0] + target[0]) / 2, (source[1] + target[1]) / 2];
259
+ const dx = target[0] - source[0];
260
+ const dy = target[1] - source[1];
261
+ const normal = [dy, -dx];
262
+ const length = Math.sqrt(Math.pow(normal[0], 2.0) + Math.pow(normal[1], 2.0));
263
+ const normalized = [normal[0] / length, normal[1] / length];
264
+ return [
265
+ midPoint[0] + normalized[0] * offset * direction,
266
+ midPoint[1] + normalized[1] * offset * direction
267
+ ];
268
+ }