@deck.gl-community/graph-layers 9.0.2 → 9.1.0-beta.3
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/LICENSE +1 -1
- package/dist/core/graph-engine.d.ts +16 -7
- package/dist/core/graph-engine.d.ts.map +1 -1
- package/dist/core/graph-engine.js +13 -4
- package/dist/core/graph-layout.d.ts +69 -0
- package/dist/core/graph-layout.d.ts.map +1 -0
- package/dist/core/{base-layout.js → graph-layout.js} +63 -80
- package/dist/core/interaction-manager.d.ts +1 -1
- package/dist/core/interaction-manager.d.ts.map +1 -1
- package/dist/{core → graph}/edge.d.ts +18 -17
- package/dist/graph/edge.d.ts.map +1 -0
- package/dist/{core → graph}/edge.js +12 -15
- package/dist/{core → graph}/graph.d.ts +34 -31
- package/dist/graph/graph.d.ts.map +1 -0
- package/dist/{core → graph}/graph.js +43 -36
- package/dist/{core → graph}/node.d.ts +20 -20
- package/dist/graph/node.d.ts.map +1 -0
- package/dist/{core → graph}/node.js +16 -18
- package/dist/index.cjs +1181 -434
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +16 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -18
- package/dist/layers/graph-layer.d.ts +45 -5
- package/dist/layers/graph-layer.d.ts.map +1 -1
- package/dist/layers/graph-layer.js +80 -38
- package/dist/layers/node-layers/{path-rounded-rectange-layer.d.ts → path-rounded-rectangle-layer.d.ts} +1 -1
- package/dist/layers/node-layers/path-rounded-rectangle-layer.d.ts.map +1 -0
- package/dist/layers/node-layers/rounded-rectangle-layer-fragment.d.ts +1 -1
- package/dist/layers/node-layers/rounded-rectangle-layer-fragment.d.ts.map +1 -1
- package/dist/layers/node-layers/rounded-rectangle-layer-fragment.js +1 -3
- package/dist/layers/node-layers/rounded-rectangle-layer.d.ts +12 -3
- package/dist/layers/node-layers/rounded-rectangle-layer.d.ts.map +1 -1
- package/dist/layers/node-layers/rounded-rectangle-layer.js +25 -11
- package/dist/layouts/d3-force/d3-force-layout.d.ts +12 -3
- package/dist/layouts/d3-force/d3-force-layout.d.ts.map +1 -1
- package/dist/layouts/d3-force/d3-force-layout.js +11 -11
- package/dist/layouts/d3-force/worker.d.ts.map +1 -1
- package/dist/layouts/experimental/force-multi-graph-layout.d.ts +43 -0
- package/dist/layouts/experimental/force-multi-graph-layout.d.ts.map +1 -0
- package/dist/layouts/experimental/force-multi-graph-layout.js +226 -0
- package/dist/layouts/experimental/hive-plot-layout.d.ts +34 -0
- package/dist/layouts/experimental/hive-plot-layout.d.ts.map +1 -0
- package/dist/layouts/experimental/hive-plot-layout.js +142 -0
- package/dist/layouts/experimental/radial-layout.d.ts +28 -0
- package/dist/layouts/experimental/radial-layout.d.ts.map +1 -0
- package/dist/layouts/experimental/radial-layout.js +164 -0
- package/dist/layouts/gpu-force/gpu-force-layout.d.ts +15 -3
- package/dist/layouts/gpu-force/gpu-force-layout.d.ts.map +1 -1
- package/dist/layouts/gpu-force/gpu-force-layout.js +20 -18
- package/dist/layouts/gpu-force/worker.d.ts.map +1 -1
- package/dist/layouts/simple-layout.d.ts +42 -0
- package/dist/layouts/simple-layout.d.ts.map +1 -0
- package/dist/layouts/{simple-layout/simple-layout.js → simple-layout.js} +8 -7
- package/dist/loaders/create-graph.d.ts +13 -0
- package/dist/loaders/create-graph.d.ts.map +1 -0
- package/dist/{utils → loaders}/create-graph.js +9 -4
- package/dist/loaders/edge-parsers.d.ts +2 -6
- package/dist/loaders/edge-parsers.d.ts.map +1 -1
- package/dist/loaders/json-loader.js +1 -1
- package/dist/loaders/node-parsers.d.ts +2 -3
- package/dist/loaders/node-parsers.d.ts.map +1 -1
- package/dist/loaders/simple-json-graph-loader.d.ts +12 -0
- package/dist/loaders/simple-json-graph-loader.d.ts.map +1 -0
- package/dist/loaders/simple-json-graph-loader.js +20 -0
- package/dist/loaders/table-graph-loader.d.ts +17 -0
- package/dist/loaders/table-graph-loader.d.ts.map +1 -0
- package/dist/loaders/table-graph-loader.js +91 -0
- package/dist/utils/log.d.ts +1 -1
- package/dist/utils/log.d.ts.map +1 -1
- package/dist/utils/log.js +3 -3
- package/dist/widgets/long-press-button.d.ts +13 -0
- package/dist/widgets/long-press-button.d.ts.map +1 -0
- package/dist/widgets/long-press-button.js +31 -0
- package/dist/widgets/view-control-widget.d.ts +78 -0
- package/dist/widgets/view-control-widget.d.ts.map +1 -0
- package/dist/widgets/view-control-widget.js +194 -0
- package/package.json +8 -6
- package/src/core/graph-engine.ts +30 -10
- package/src/core/graph-layout.ts +146 -0
- package/src/core/interaction-manager.ts +2 -2
- package/src/{core → graph}/edge.ts +19 -17
- package/src/{core → graph}/graph.ts +51 -36
- package/src/{core → graph}/node.ts +21 -20
- package/src/index.ts +28 -28
- package/src/layers/graph-layer.ts +133 -46
- package/src/layers/node-layers/rounded-rectangle-layer-fragment.ts +1 -3
- package/src/layers/node-layers/rounded-rectangle-layer.ts +34 -10
- package/src/layouts/d3-force/d3-force-layout.ts +21 -11
- package/src/layouts/experimental/force-multi-graph-layout.ts +268 -0
- package/src/layouts/experimental/hive-plot-layout.ts +182 -0
- package/src/layouts/experimental/radial-layout.ts +210 -0
- package/src/layouts/gpu-force/gpu-force-layout.ts +32 -17
- package/src/layouts/{simple-layout/simple-layout.ts → simple-layout.ts} +34 -19
- package/src/{utils → loaders}/create-graph.ts +9 -4
- package/src/loaders/edge-parsers.ts +2 -1
- package/src/loaders/json-loader.ts +1 -1
- package/src/loaders/node-parsers.ts +2 -1
- package/src/loaders/simple-json-graph-loader.ts +28 -0
- package/src/loaders/table-graph-loader.ts +124 -0
- package/src/utils/log.ts +3 -3
- package/src/widgets/long-press-button.tsx +50 -0
- package/src/widgets/view-control-widget.tsx +337 -0
- package/dist/core/base-layout.d.ts +0 -72
- package/dist/core/base-layout.d.ts.map +0 -1
- package/dist/core/edge.d.ts.map +0 -1
- package/dist/core/graph.d.ts.map +0 -1
- package/dist/core/node.d.ts.map +0 -1
- package/dist/layers/node-layers/path-rounded-rectange-layer.d.ts.map +0 -1
- package/dist/layouts/simple-layout/simple-layout.d.ts +0 -23
- package/dist/layouts/simple-layout/simple-layout.d.ts.map +0 -1
- package/dist/utils/create-graph.d.ts +0 -9
- package/dist/utils/create-graph.d.ts.map +0 -1
- package/src/core/base-layout.ts +0 -154
- /package/dist/layers/node-layers/{path-rounded-rectange-layer.js → path-rounded-rectangle-layer.js} +0 -0
- /package/src/layers/node-layers/{path-rounded-rectange-layer.ts → path-rounded-rectangle-layer.ts} +0 -0
- /package/src/layouts/d3-force/{worker.ts → worker.js} +0 -0
- /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-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
edgeEvents
|
|
66
|
-
onClick: () =>
|
|
67
|
-
onHover: () =>
|
|
68
|
-
}
|
|
69
|
-
enableDragging
|
|
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
|
|
74
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
110
|
-
|
|
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
|
|
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
|
|
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
|
|
294
|
+
return (this.state.interactionManager.onHover(info, event) as unknown as boolean) || false;
|
|
205
295
|
}
|
|
206
296
|
|
|
207
297
|
onDragStart(info, event) {
|
|
208
|
-
|
|
298
|
+
this.state.interactionManager.onDragStart(info, event);
|
|
209
299
|
}
|
|
210
300
|
|
|
211
301
|
onDrag(info, event) {
|
|
212
|
-
|
|
302
|
+
this.state.interactionManager.onDrag(info, event);
|
|
213
303
|
}
|
|
214
304
|
|
|
215
305
|
onDragEnd(info, event) {
|
|
216
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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 {
|
|
5
|
+
import {GraphLayout, GraphLayoutOptions} from '../../core/graph-layout';
|
|
6
6
|
|
|
7
7
|
import {EDGE_TYPE} from '../../core/constants';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
alpha
|
|
11
|
-
resumeAlpha
|
|
12
|
-
nBodyStrength
|
|
13
|
-
nBodyDistanceMin
|
|
14
|
-
nBodyDistanceMax
|
|
15
|
-
getCollisionRadius
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|