@deck.gl-community/graph-layers 9.0.2 → 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.
- 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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// deck.gl-community
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
import { GraphLayout } from "../../core/graph-layout.js";
|
|
5
|
+
import { EDGE_TYPE } from "../../core/constants.js";
|
|
6
|
+
import * as d3 from 'd3-force';
|
|
7
|
+
export class ForceMultiGraphLayout extends GraphLayout {
|
|
8
|
+
static defaultOptions = {
|
|
9
|
+
alpha: 3,
|
|
10
|
+
nBodyStrength: -1200,
|
|
11
|
+
nBodyDistanceMin: 100,
|
|
12
|
+
nBodyDistanceMax: 1400
|
|
13
|
+
};
|
|
14
|
+
_name = 'ForceMultiGraphLayout';
|
|
15
|
+
_graph;
|
|
16
|
+
// d3 part
|
|
17
|
+
// custom graph data
|
|
18
|
+
_d3Graph = { nodes: [], edges: [] };
|
|
19
|
+
_nodeMap = {};
|
|
20
|
+
_edgeMap = {};
|
|
21
|
+
_simulator;
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
super(options);
|
|
24
|
+
this._options = {
|
|
25
|
+
...ForceMultiGraphLayout.defaultOptions,
|
|
26
|
+
...options
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
initializeGraph(graph) {
|
|
30
|
+
this.updateGraph(graph);
|
|
31
|
+
}
|
|
32
|
+
_strength = (d3Edge) => {
|
|
33
|
+
if (d3Edge.isVirtual) {
|
|
34
|
+
return 1 / d3Edge.edgeCount;
|
|
35
|
+
}
|
|
36
|
+
const sourceDegree = this._graph.getDegree(d3Edge.source.id);
|
|
37
|
+
const targetDegree = this._graph.getDegree(d3Edge.target.id);
|
|
38
|
+
return 1 / Math.min(sourceDegree, targetDegree);
|
|
39
|
+
};
|
|
40
|
+
_generateSimulator() {
|
|
41
|
+
if (this._simulator) {
|
|
42
|
+
this._simulator.on('tick', null).on('end', null);
|
|
43
|
+
this._simulator = null;
|
|
44
|
+
}
|
|
45
|
+
const { alpha, nBodyStrength, nBodyDistanceMin, nBodyDistanceMax } = this._options;
|
|
46
|
+
const g = this._d3Graph;
|
|
47
|
+
this._simulator = d3
|
|
48
|
+
.forceSimulation(g.nodes)
|
|
49
|
+
.force('edge', d3
|
|
50
|
+
.forceLink(g.edges)
|
|
51
|
+
.id((n) => n.id)
|
|
52
|
+
.strength(this._strength))
|
|
53
|
+
.force('charge', d3
|
|
54
|
+
.forceManyBody()
|
|
55
|
+
.strength(nBodyStrength)
|
|
56
|
+
.distanceMin(nBodyDistanceMin)
|
|
57
|
+
.distanceMax(nBodyDistanceMax))
|
|
58
|
+
.force('center', d3.forceCenter())
|
|
59
|
+
.alpha(alpha);
|
|
60
|
+
// register event callbacks
|
|
61
|
+
this._simulator.on('tick', this._onLayoutChange).on('end', this._onLayoutDone);
|
|
62
|
+
}
|
|
63
|
+
start() {
|
|
64
|
+
this._generateSimulator();
|
|
65
|
+
this._simulator.restart();
|
|
66
|
+
}
|
|
67
|
+
resume() {
|
|
68
|
+
this._simulator.restart();
|
|
69
|
+
}
|
|
70
|
+
stop() {
|
|
71
|
+
this._simulator.stop();
|
|
72
|
+
}
|
|
73
|
+
updateGraph(graph) {
|
|
74
|
+
this._graph = graph;
|
|
75
|
+
// nodes
|
|
76
|
+
const newNodeMap = {};
|
|
77
|
+
const newD3Nodes = graph.getNodes().map((node) => {
|
|
78
|
+
const oldD3Node = this._nodeMap[node.id];
|
|
79
|
+
const newD3Node = oldD3Node ? oldD3Node : { id: node.id };
|
|
80
|
+
newNodeMap[node.id] = newD3Node;
|
|
81
|
+
return newD3Node;
|
|
82
|
+
});
|
|
83
|
+
// edges
|
|
84
|
+
// bucket edges between the same source/target node pairs.
|
|
85
|
+
const nodePairs = graph.getEdges().reduce((res, edge) => {
|
|
86
|
+
const nodes = [edge.getSourceNodeId(), edge.getTargetNodeId()];
|
|
87
|
+
// sort the node ids to count the edges with the same pair
|
|
88
|
+
// but different direction (a -> b or b -> a)
|
|
89
|
+
const pairId = nodes.sort().toString();
|
|
90
|
+
// push this edge into the bucket
|
|
91
|
+
if (!res[pairId]) {
|
|
92
|
+
res[pairId] = [edge];
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
res[pairId].push(edge);
|
|
96
|
+
}
|
|
97
|
+
return res;
|
|
98
|
+
}, {});
|
|
99
|
+
// go through each pair of edges,
|
|
100
|
+
// if only one edge between two nodes, create a straight line
|
|
101
|
+
// otherwise, create one virtual node and two edges for each edge
|
|
102
|
+
const newD3Edges = [];
|
|
103
|
+
const newEdgeMap = {};
|
|
104
|
+
Object.keys(nodePairs).forEach((pairId) => {
|
|
105
|
+
const betweenEdges = nodePairs[pairId];
|
|
106
|
+
const firstEdge = betweenEdges[0];
|
|
107
|
+
if (betweenEdges.length === 1) {
|
|
108
|
+
// do nothing, this is a real edge
|
|
109
|
+
const newD3Edge = {
|
|
110
|
+
type: EDGE_TYPE.LINE,
|
|
111
|
+
id: firstEdge.getId(),
|
|
112
|
+
source: newNodeMap[firstEdge.getSourceNodeId()],
|
|
113
|
+
target: newNodeMap[firstEdge.getTargetNodeId()],
|
|
114
|
+
isVirtual: false
|
|
115
|
+
};
|
|
116
|
+
newEdgeMap[firstEdge.getId()] = newD3Edge;
|
|
117
|
+
newD3Edges.push(newD3Edge);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// else reduce to one virtual edge
|
|
121
|
+
const newD3Edge = {
|
|
122
|
+
type: EDGE_TYPE.LINE,
|
|
123
|
+
id: pairId,
|
|
124
|
+
source: newNodeMap[firstEdge.getSourceNodeId()],
|
|
125
|
+
target: newNodeMap[firstEdge.getTargetNodeId()],
|
|
126
|
+
isVirtual: true,
|
|
127
|
+
edgeCount: betweenEdges.length
|
|
128
|
+
};
|
|
129
|
+
newEdgeMap[pairId] = newD3Edge;
|
|
130
|
+
newD3Edges.push(newD3Edge);
|
|
131
|
+
betweenEdges.forEach((e, idx) => {
|
|
132
|
+
newEdgeMap[e.id] = {
|
|
133
|
+
type: EDGE_TYPE.SPLINE_CURVE,
|
|
134
|
+
id: e.id,
|
|
135
|
+
source: newNodeMap[e.getSourceNodeId()],
|
|
136
|
+
target: newNodeMap[e.getTargetNodeId()],
|
|
137
|
+
virtualEdgeId: pairId,
|
|
138
|
+
isVirtual: true,
|
|
139
|
+
index: idx
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
this._nodeMap = newNodeMap;
|
|
144
|
+
this._d3Graph.nodes = newD3Nodes;
|
|
145
|
+
this._edgeMap = newEdgeMap;
|
|
146
|
+
this._d3Graph.edges = newD3Edges;
|
|
147
|
+
}
|
|
148
|
+
getNodePosition = (node) => {
|
|
149
|
+
const d3Node = this._nodeMap[node.id];
|
|
150
|
+
if (d3Node) {
|
|
151
|
+
return [d3Node.x, d3Node.y];
|
|
152
|
+
}
|
|
153
|
+
// default value
|
|
154
|
+
return [0, 0];
|
|
155
|
+
};
|
|
156
|
+
getEdgePosition = (edge) => {
|
|
157
|
+
const d3Edge = this._edgeMap[edge.id];
|
|
158
|
+
if (d3Edge) {
|
|
159
|
+
if (!d3Edge.isVirtual) {
|
|
160
|
+
return {
|
|
161
|
+
type: EDGE_TYPE.LINE,
|
|
162
|
+
sourcePosition: [d3Edge.source.x, d3Edge.source.y],
|
|
163
|
+
targetPosition: [d3Edge.target.x, d3Edge.target.y],
|
|
164
|
+
controlPoints: []
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// else, check the referenced virtual edge
|
|
168
|
+
const virtualEdge = this._edgeMap[d3Edge.virtualEdgeId];
|
|
169
|
+
const edgeCount = virtualEdge.edgeCount;
|
|
170
|
+
// get the position of source and target nodes
|
|
171
|
+
const sourcePosition = [virtualEdge.source.x, virtualEdge.source.y];
|
|
172
|
+
const targetPosition = [virtualEdge.target.x, virtualEdge.target.y];
|
|
173
|
+
// calculate a symmetric curve
|
|
174
|
+
const distance = Math.hypot(sourcePosition[0] - targetPosition[0], sourcePosition[1] - targetPosition[1]);
|
|
175
|
+
const index = d3Edge.index;
|
|
176
|
+
// curve direction: inward vs. outward
|
|
177
|
+
const direction = index % 2 ? 1 : -1;
|
|
178
|
+
// if the number of the parallel edges is an even number => symmetric shape
|
|
179
|
+
// otherwise, the 0th node will be a staight line, and rest of them are symmetrical.
|
|
180
|
+
const symmetricShape = edgeCount % 2 === 0;
|
|
181
|
+
const offset = Math.max(distance / 10, 5) *
|
|
182
|
+
(symmetricShape ? Math.floor(index / 2 + 1) : Math.ceil(index / 2));
|
|
183
|
+
const controlPoint = computeControlPoint(sourcePosition, targetPosition, direction, offset);
|
|
184
|
+
return {
|
|
185
|
+
type: EDGE_TYPE.SPLINE_CURVE,
|
|
186
|
+
sourcePosition,
|
|
187
|
+
targetPosition,
|
|
188
|
+
controlPoints: [controlPoint]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// default value
|
|
192
|
+
return {
|
|
193
|
+
type: EDGE_TYPE.LINE,
|
|
194
|
+
sourcePosition: [0, 0],
|
|
195
|
+
targetPosition: [0, 0],
|
|
196
|
+
controlPoints: []
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
lockNodePosition = (node, x, y) => {
|
|
200
|
+
const d3Node = this._nodeMap[node.id];
|
|
201
|
+
d3Node.x = x;
|
|
202
|
+
d3Node.y = y;
|
|
203
|
+
this._onLayoutChange();
|
|
204
|
+
this._onLayoutDone();
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* A helper function to compute the control point of a curve
|
|
209
|
+
* @param {number[]} source - the coordinates of source point, ex: [x, y, z]
|
|
210
|
+
* @param {number[]} target - the coordinates of target point, ex: [x, y, z]
|
|
211
|
+
* @param {number} direction - the direction of the curve, 1 or -1
|
|
212
|
+
* @param {number} offset - offset from the midpoint
|
|
213
|
+
* @return {number[]} - the coordinates of the control point
|
|
214
|
+
*/
|
|
215
|
+
function computeControlPoint(source, target, direction, offset) {
|
|
216
|
+
const midPoint = [(source[0] + target[0]) / 2, (source[1] + target[1]) / 2];
|
|
217
|
+
const dx = target[0] - source[0];
|
|
218
|
+
const dy = target[1] - source[1];
|
|
219
|
+
const normal = [dy, -dx];
|
|
220
|
+
const length = Math.sqrt(Math.pow(normal[0], 2.0) + Math.pow(normal[1], 2.0));
|
|
221
|
+
const normalized = [normal[0] / length, normal[1] / length];
|
|
222
|
+
return [
|
|
223
|
+
midPoint[0] + normalized[0] * offset * direction,
|
|
224
|
+
midPoint[1] + normalized[1] * offset * direction
|
|
225
|
+
];
|
|
226
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { GraphLayout, GraphLayoutOptions } from "../../core/graph-layout.js";
|
|
2
|
+
import { Node } from "../../graph/node.js";
|
|
3
|
+
import { Graph } from "../../graph/graph.js";
|
|
4
|
+
export type HivePlotLayoutOptions = GraphLayoutOptions & {
|
|
5
|
+
innerRadius?: number;
|
|
6
|
+
outerRadius?: number;
|
|
7
|
+
getNodeAxis?: (node: Node) => any;
|
|
8
|
+
};
|
|
9
|
+
export declare class HivePlotLayout extends GraphLayout<HivePlotLayoutOptions> {
|
|
10
|
+
static defaultOptions: {
|
|
11
|
+
readonly innerRadius: 100;
|
|
12
|
+
readonly outerRadius: 500;
|
|
13
|
+
readonly getNodeAxis: (node: Node) => unknown;
|
|
14
|
+
};
|
|
15
|
+
_name: string;
|
|
16
|
+
_graph: Graph;
|
|
17
|
+
_totalAxis: number;
|
|
18
|
+
_axis: Record<string, any>;
|
|
19
|
+
_nodeMap: {};
|
|
20
|
+
_nodePositionMap: {};
|
|
21
|
+
constructor(options?: HivePlotLayoutOptions);
|
|
22
|
+
initializeGraph(graph: Graph): void;
|
|
23
|
+
updateGraph(graph: any): void;
|
|
24
|
+
start(): void;
|
|
25
|
+
getNodePosition: (node: any) => any;
|
|
26
|
+
getEdgePosition: (edge: any) => {
|
|
27
|
+
type: string;
|
|
28
|
+
sourcePosition: any;
|
|
29
|
+
targetPosition: any;
|
|
30
|
+
controlPoints: [number, number][];
|
|
31
|
+
};
|
|
32
|
+
lockNodePosition: (node: any, x: any, y: any) => void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=hive-plot-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hive-plot-layout.d.ts","sourceRoot":"","sources":["../../../src/layouts/experimental/hive-plot-layout.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,WAAW,EAAE,kBAAkB,EAAC,mCAAgC;AACxE,OAAO,EAAC,IAAI,EAAC,4BAAyB;AAEtC,OAAO,EAAC,KAAK,EAAC,6BAA0B;AAExC,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,GAAG;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,GAAG,CAAC;CACnC,CAAC;AAEF,qBAAa,cAAe,SAAQ,WAAW,CAAC,qBAAqB,CAAC;IACpE,MAAM,CAAC,cAAc;;;qCAGC,IAAI;MACqC;IAE/D,KAAK,SAAc;IACnB,MAAM,EAAE,KAAK,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,QAAQ,KAAM;IACd,gBAAgB,KAAM;gBAEV,OAAO,GAAE,qBAA0B;IAQ/C,eAAe,CAAC,KAAK,EAAE,KAAK;IAI5B,WAAW,CAAC,KAAK,KAAA;IAsDjB,KAAK;IAKL,eAAe,qBAAiD;IAEhE,eAAe;;;;;MAqCb;IAEF,gBAAgB,sCAId;CACH"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// deck.gl-community
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
import { GraphLayout } from "../../core/graph-layout.js";
|
|
5
|
+
import { EDGE_TYPE } from "../../core/constants.js";
|
|
6
|
+
export class HivePlotLayout extends GraphLayout {
|
|
7
|
+
static defaultOptions = {
|
|
8
|
+
innerRadius: 100,
|
|
9
|
+
outerRadius: 500,
|
|
10
|
+
getNodeAxis: (node) => node.getPropertyValue('group')
|
|
11
|
+
};
|
|
12
|
+
_name = 'HivePlot';
|
|
13
|
+
_graph;
|
|
14
|
+
_totalAxis;
|
|
15
|
+
_axis;
|
|
16
|
+
_nodeMap = {};
|
|
17
|
+
_nodePositionMap = {};
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
super(options);
|
|
20
|
+
this._options = {
|
|
21
|
+
...HivePlotLayout.defaultOptions,
|
|
22
|
+
...options
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
initializeGraph(graph) {
|
|
26
|
+
this.updateGraph(graph);
|
|
27
|
+
}
|
|
28
|
+
updateGraph(graph) {
|
|
29
|
+
const { getNodeAxis, innerRadius, outerRadius } = this._options;
|
|
30
|
+
this._graph = graph;
|
|
31
|
+
this._nodeMap = graph.getNodes().reduce((res, node) => {
|
|
32
|
+
res[node.getId()] = node;
|
|
33
|
+
return res;
|
|
34
|
+
}, {});
|
|
35
|
+
// bucket nodes into few axis
|
|
36
|
+
this._axis = graph.getNodes().reduce((res, node) => {
|
|
37
|
+
const axis = getNodeAxis(node);
|
|
38
|
+
if (!res[axis]) {
|
|
39
|
+
res[axis] = [];
|
|
40
|
+
}
|
|
41
|
+
res[axis].push(node);
|
|
42
|
+
return res;
|
|
43
|
+
}, {});
|
|
44
|
+
// sort nodes along the same axis by degree
|
|
45
|
+
this._axis = Object.keys(this._axis).reduce((res, axis) => {
|
|
46
|
+
const bucketedNodes = this._axis[axis];
|
|
47
|
+
const sortedNodes = bucketedNodes.sort((a, b) => {
|
|
48
|
+
if (a.getDegree() > b.getDegree()) {
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
if (a.getDegree() === b.getDegree()) {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
return -1;
|
|
55
|
+
});
|
|
56
|
+
res[axis] = sortedNodes;
|
|
57
|
+
return res;
|
|
58
|
+
}, {});
|
|
59
|
+
this._totalAxis = Object.keys(this._axis).length;
|
|
60
|
+
const center = [0, 0];
|
|
61
|
+
const angleInterval = 360 / Object.keys(this._axis).length;
|
|
62
|
+
// calculate positions
|
|
63
|
+
this._nodePositionMap = Object.keys(this._axis).reduce((res, axis, axisIdx) => {
|
|
64
|
+
const axisAngle = angleInterval * axisIdx;
|
|
65
|
+
const bucketedNodes = this._axis[axis];
|
|
66
|
+
const interval = (outerRadius - innerRadius) / bucketedNodes.length;
|
|
67
|
+
bucketedNodes.forEach((node, idx) => {
|
|
68
|
+
const radius = innerRadius + idx * interval;
|
|
69
|
+
const x = Math.cos((axisAngle / 180) * Math.PI) * radius + center[0];
|
|
70
|
+
const y = Math.sin((axisAngle / 180) * Math.PI) * radius + center[1];
|
|
71
|
+
res[node.getId()] = [x, y];
|
|
72
|
+
});
|
|
73
|
+
return res;
|
|
74
|
+
}, {});
|
|
75
|
+
}
|
|
76
|
+
start() {
|
|
77
|
+
this._onLayoutChange();
|
|
78
|
+
this._onLayoutDone();
|
|
79
|
+
}
|
|
80
|
+
getNodePosition = (node) => this._nodePositionMap[node.getId()];
|
|
81
|
+
getEdgePosition = (edge) => {
|
|
82
|
+
const { getNodeAxis } = this._options;
|
|
83
|
+
const sourceNodeId = edge.getSourceNodeId();
|
|
84
|
+
const targetNodeId = edge.getTargetNodeId();
|
|
85
|
+
const sourcePosition = this._nodePositionMap[sourceNodeId];
|
|
86
|
+
const targetPosition = this._nodePositionMap[targetNodeId];
|
|
87
|
+
const sourceNode = this._nodeMap[sourceNodeId];
|
|
88
|
+
const targetNode = this._nodeMap[targetNodeId];
|
|
89
|
+
const sourceNodeAxis = getNodeAxis(sourceNode);
|
|
90
|
+
const targetNodeAxis = getNodeAxis(targetNode);
|
|
91
|
+
if (sourceNodeAxis === targetNodeAxis) {
|
|
92
|
+
return {
|
|
93
|
+
type: EDGE_TYPE.LINE,
|
|
94
|
+
sourcePosition,
|
|
95
|
+
targetPosition,
|
|
96
|
+
controlPoints: []
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const controlPoint = computeControlPoint({
|
|
100
|
+
sourcePosition,
|
|
101
|
+
sourceNodeAxis,
|
|
102
|
+
targetPosition,
|
|
103
|
+
targetNodeAxis,
|
|
104
|
+
totalAxis: this._totalAxis
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
type: EDGE_TYPE.SPLINE_CURVE,
|
|
108
|
+
sourcePosition,
|
|
109
|
+
targetPosition,
|
|
110
|
+
controlPoints: [controlPoint]
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
lockNodePosition = (node, x, y) => {
|
|
114
|
+
this._nodePositionMap[node.id] = [x, y];
|
|
115
|
+
this._onLayoutChange();
|
|
116
|
+
this._onLayoutDone();
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function computeControlPoint({ sourcePosition, sourceNodeAxis, targetPosition, targetNodeAxis, totalAxis }) {
|
|
120
|
+
const halfAxis = (totalAxis - 1) / 2;
|
|
121
|
+
// check whether the source/target are at the same side.
|
|
122
|
+
const sameSide = (sourceNodeAxis <= halfAxis && targetNodeAxis <= halfAxis) ||
|
|
123
|
+
(sourceNodeAxis > halfAxis && targetNodeAxis > halfAxis);
|
|
124
|
+
// curve direction
|
|
125
|
+
const direction = sameSide && sourceNodeAxis <= halfAxis && targetNodeAxis <= halfAxis ? 1 : -1;
|
|
126
|
+
// flip the source/target to follow the clockwise diretion
|
|
127
|
+
const source = sourceNodeAxis < targetNodeAxis && sameSide ? sourcePosition : targetPosition;
|
|
128
|
+
const target = sourceNodeAxis < targetNodeAxis && sameSide ? targetPosition : sourcePosition;
|
|
129
|
+
// calculate offset
|
|
130
|
+
const distance = Math.hypot(source[0] - target[0], source[1] - target[1]);
|
|
131
|
+
const offset = distance * 0.2;
|
|
132
|
+
const midPoint = [(source[0] + target[0]) / 2, (source[1] + target[1]) / 2];
|
|
133
|
+
const dx = target[0] - source[0];
|
|
134
|
+
const dy = target[1] - source[1];
|
|
135
|
+
const normal = [dy, -dx];
|
|
136
|
+
const length = Math.hypot(dy, -dx);
|
|
137
|
+
const normalized = [normal[0] / length, normal[1] / length];
|
|
138
|
+
return [
|
|
139
|
+
midPoint[0] + normalized[0] * offset * direction,
|
|
140
|
+
midPoint[1] + normalized[1] * offset * direction
|
|
141
|
+
];
|
|
142
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { GraphLayout, GraphLayoutOptions } from "../../core/graph-layout.js";
|
|
2
|
+
import { Graph } from "../../graph/graph.js";
|
|
3
|
+
export type RadialLayoutOptions = GraphLayoutOptions & {
|
|
4
|
+
radius?: number;
|
|
5
|
+
tree?: any;
|
|
6
|
+
};
|
|
7
|
+
export declare class RadialLayout extends GraphLayout<RadialLayoutOptions> {
|
|
8
|
+
static defaultOptions: {
|
|
9
|
+
radius: number;
|
|
10
|
+
};
|
|
11
|
+
_name: string;
|
|
12
|
+
_graph: Graph;
|
|
13
|
+
_hierarchicalPoints: {};
|
|
14
|
+
nestedTree: any;
|
|
15
|
+
constructor(options?: RadialLayoutOptions);
|
|
16
|
+
initializeGraph(graph: Graph): void;
|
|
17
|
+
updateGraph(graph: Graph): void;
|
|
18
|
+
start(): void;
|
|
19
|
+
getNodePosition: (node: any) => any;
|
|
20
|
+
getEdgePosition: (edge: any) => {
|
|
21
|
+
type: string;
|
|
22
|
+
sourcePosition: any;
|
|
23
|
+
targetPosition: any;
|
|
24
|
+
controlPoints: any[];
|
|
25
|
+
};
|
|
26
|
+
lockNodePosition: (node: any, x: any, y: any) => void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=radial-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radial-layout.d.ts","sourceRoot":"","sources":["../../../src/layouts/experimental/radial-layout.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,WAAW,EAAE,kBAAkB,EAAC,mCAAgC;AAGxE,OAAO,EAAC,KAAK,EAAC,6BAA0B;AAExC,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC;AA2CF,qBAAa,YAAa,SAAQ,WAAW,CAAC,mBAAmB,CAAC;IAChE,MAAM,CAAC,cAAc;;MAEnB;IAEF,KAAK,SAAkB;IACvB,MAAM,EAAE,KAAK,CAAQ;IAErB,mBAAmB,KAAM;IACzB,UAAU,MAAC;gBAEC,OAAO,GAAE,mBAAwB;IAQ7C,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAInC,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAI/B,KAAK,IAAI,IAAI;IAsEb,eAAe,qBAEb;IAGF,eAAe;;;;;MAoCb;IAEF,gBAAgB,sCAId;CACH"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// deck.gl-community
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
import { GraphLayout } from "../../core/graph-layout.js";
|
|
5
|
+
import { EDGE_TYPE } from "../../core/constants.js";
|
|
6
|
+
const traverseTree = (nodeId, nodeMap) => {
|
|
7
|
+
const node = nodeMap[nodeId];
|
|
8
|
+
if (node.isLeaf) {
|
|
9
|
+
return node;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
...node,
|
|
13
|
+
children: node.children.map((nid) => traverseTree(nid, nodeMap))
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const getLeafNodeCount = (node, count) => {
|
|
17
|
+
if (!node.children || node.children.length === 0) {
|
|
18
|
+
return count + 1;
|
|
19
|
+
}
|
|
20
|
+
const sum = node.children.reduce((res, c) => {
|
|
21
|
+
return res + getLeafNodeCount(c, 0);
|
|
22
|
+
}, 0);
|
|
23
|
+
return count + sum;
|
|
24
|
+
};
|
|
25
|
+
const getTreeDepth = (node, depth = 0) => {
|
|
26
|
+
if (node.isLeaf) {
|
|
27
|
+
return depth;
|
|
28
|
+
}
|
|
29
|
+
return getTreeDepth(node.children[0], depth + 1);
|
|
30
|
+
};
|
|
31
|
+
const getPath = (node, targetId, path) => {
|
|
32
|
+
if (node.id === targetId) {
|
|
33
|
+
path.push(node.id);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const inChildren = node.children && node.children.some((c) => getPath(c, targetId, path));
|
|
37
|
+
if (inChildren) {
|
|
38
|
+
path.push(node.id);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
export class RadialLayout extends GraphLayout {
|
|
44
|
+
static defaultOptions = {
|
|
45
|
+
radius: 500
|
|
46
|
+
};
|
|
47
|
+
_name = 'RadialLayout';
|
|
48
|
+
_graph = null;
|
|
49
|
+
// custom layout data structure
|
|
50
|
+
_hierarchicalPoints = {};
|
|
51
|
+
nestedTree;
|
|
52
|
+
constructor(options = {}) {
|
|
53
|
+
super(options);
|
|
54
|
+
this._options = {
|
|
55
|
+
...RadialLayout.defaultOptions,
|
|
56
|
+
...options
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
initializeGraph(graph) {
|
|
60
|
+
this.updateGraph(graph);
|
|
61
|
+
}
|
|
62
|
+
updateGraph(graph) {
|
|
63
|
+
this._graph = graph;
|
|
64
|
+
}
|
|
65
|
+
start() {
|
|
66
|
+
const nodeCount = this._graph.getNodes().length;
|
|
67
|
+
if (nodeCount === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { tree } = this._options;
|
|
71
|
+
if (!tree || tree.length === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const { radius } = this._options;
|
|
75
|
+
const unitAngle = 360 / nodeCount;
|
|
76
|
+
// hierarchical positions
|
|
77
|
+
const rootNode = tree[0];
|
|
78
|
+
const nodeMap = tree.reduce((res, node) => {
|
|
79
|
+
res[node.id] = {
|
|
80
|
+
...node,
|
|
81
|
+
isLeaf: !node.children || node.children.length === 0
|
|
82
|
+
};
|
|
83
|
+
return res;
|
|
84
|
+
}, {});
|
|
85
|
+
// nested structure
|
|
86
|
+
this.nestedTree = traverseTree(rootNode.id, nodeMap);
|
|
87
|
+
const totalLevels = getTreeDepth(this.nestedTree, 0);
|
|
88
|
+
const distanceBetweenLevels = radius / (totalLevels - 1);
|
|
89
|
+
const calculatePosition = (node, level, startAngle, positionMap) => {
|
|
90
|
+
const isRoot = node.id === rootNode.id;
|
|
91
|
+
if (node.children && node.children.length !== 0) {
|
|
92
|
+
const groupSize = getLeafNodeCount(node, 0);
|
|
93
|
+
// center the pos
|
|
94
|
+
positionMap[node.id] = isRoot
|
|
95
|
+
? [0, 0]
|
|
96
|
+
: rotate(0, 0, 0, distanceBetweenLevels * (level + 1), startAngle + unitAngle * (groupSize / 2));
|
|
97
|
+
// calculate children position
|
|
98
|
+
let tempAngle = startAngle;
|
|
99
|
+
node.children.forEach((n) => {
|
|
100
|
+
calculatePosition(n, level + 1, tempAngle, positionMap);
|
|
101
|
+
tempAngle += getLeafNodeCount(n, 0) * unitAngle;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
positionMap[node.id] = rotate(0, 0, 0, distanceBetweenLevels * (level + 1), startAngle + unitAngle);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
this._hierarchicalPoints = {};
|
|
109
|
+
calculatePosition(this.nestedTree, 0, 0, this._hierarchicalPoints);
|
|
110
|
+
// layout completes: notifiy component to re-render
|
|
111
|
+
this._onLayoutChange();
|
|
112
|
+
this._onLayoutDone();
|
|
113
|
+
}
|
|
114
|
+
getNodePosition = (node) => {
|
|
115
|
+
return this._hierarchicalPoints[node.id];
|
|
116
|
+
};
|
|
117
|
+
// spline curve version
|
|
118
|
+
getEdgePosition = (edge) => {
|
|
119
|
+
const sourceNodeId = edge.getSourceNodeId();
|
|
120
|
+
const targetNodeId = edge.getTargetNodeId();
|
|
121
|
+
const sourceNodePos = this._hierarchicalPoints[sourceNodeId];
|
|
122
|
+
const targetNodePos = this._hierarchicalPoints[targetNodeId];
|
|
123
|
+
const sourcePath = [];
|
|
124
|
+
getPath(this.nestedTree, sourceNodeId, sourcePath);
|
|
125
|
+
const targetPath = [];
|
|
126
|
+
getPath(this.nestedTree, targetNodeId, targetPath);
|
|
127
|
+
const totalLevels = sourcePath.length;
|
|
128
|
+
let commonAncestorLevel = totalLevels - 1; // root
|
|
129
|
+
for (let i = 0; i < totalLevels; i++) {
|
|
130
|
+
if (sourcePath[i] === targetPath[i]) {
|
|
131
|
+
commonAncestorLevel = i;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const wayPoints = [];
|
|
136
|
+
for (let i = 1; i <= commonAncestorLevel; i++) {
|
|
137
|
+
const nodeId = sourcePath[i];
|
|
138
|
+
wayPoints.push(this._hierarchicalPoints[nodeId]);
|
|
139
|
+
}
|
|
140
|
+
for (let i = commonAncestorLevel - 1; i > 0; i--) {
|
|
141
|
+
const nodeId = targetPath[i];
|
|
142
|
+
wayPoints.push(this._hierarchicalPoints[nodeId]);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
type: EDGE_TYPE.SPLINE_CURVE,
|
|
146
|
+
sourcePosition: sourceNodePos,
|
|
147
|
+
targetPosition: targetNodePos,
|
|
148
|
+
controlPoints: wayPoints
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
lockNodePosition = (node, x, y) => {
|
|
152
|
+
this._hierarchicalPoints[node.id] = [x, y];
|
|
153
|
+
this._onLayoutChange();
|
|
154
|
+
this._onLayoutDone();
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function rotate(cx, cy, x, y, angle) {
|
|
158
|
+
const radians = (Math.PI / 180) * angle;
|
|
159
|
+
const cos = Math.cos(radians);
|
|
160
|
+
const sin = Math.sin(radians);
|
|
161
|
+
const nx = cos * (x - cx) + sin * (y - cy) + cx;
|
|
162
|
+
const ny = cos * (y - cy) - sin * (x - cx) + cy;
|
|
163
|
+
return [nx, ny];
|
|
164
|
+
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export
|
|
1
|
+
import { GraphLayout, GraphLayoutOptions } from "../../core/graph-layout.js";
|
|
2
|
+
export type GPUForceLayoutOptions = GraphLayoutOptions & {
|
|
3
|
+
alpha?: number;
|
|
4
|
+
resumeAlpha?: number;
|
|
5
|
+
nBodyStrength?: number;
|
|
6
|
+
nBodyDistanceMin?: number;
|
|
7
|
+
nBodyDistanceMax?: number;
|
|
8
|
+
getCollisionRadius?: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* @todo this layout should be updated with the organizational and logic improvements made in d3-force
|
|
12
|
+
*/
|
|
13
|
+
export declare class GPUForceLayout extends GraphLayout<GPUForceLayoutOptions> {
|
|
14
|
+
static defaultOptions: Required<GPUForceLayoutOptions>;
|
|
3
15
|
protected readonly _name: string;
|
|
4
16
|
private _d3Graph;
|
|
5
17
|
private _nodeMap;
|
|
@@ -7,7 +19,7 @@ export declare class GPUForceLayout extends BaseLayout {
|
|
|
7
19
|
private _graph;
|
|
8
20
|
private _worker;
|
|
9
21
|
private _callbacks;
|
|
10
|
-
constructor(options
|
|
22
|
+
constructor(options?: GPUForceLayoutOptions);
|
|
11
23
|
initializeGraph(graph: any): void;
|
|
12
24
|
start(): void;
|
|
13
25
|
update(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gpu-force-layout.d.ts","sourceRoot":"","sources":["../../../src/layouts/gpu-force/gpu-force-layout.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"gpu-force-layout.d.ts","sourceRoot":"","sources":["../../../src/layouts/gpu-force/gpu-force-layout.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,WAAW,EAAE,kBAAkB,EAAC,mCAAgC;AAIxE,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,GAAG;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,qBAAa,cAAe,SAAQ,WAAW,CAAC,qBAAqB,CAAC;IACpE,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAOpD;IAEF,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAM;gBAEZ,OAAO,GAAE,qBAA0B;IAgB/C,eAAe,CAAC,KAAK,KAAA;IAuCrB,KAAK;IAIL,MAAM;IAIN,aAAa;IAiCb,MAAM,CAAC,IAAI,KAAA;IACX,KAAK,CAAC,IAAI,KAAA;IAMV,MAAM;IAGN,IAAI;IAKJ,WAAW,CAAC,KAAK,KAAA;IA0CjB,aAAa,CAAC,KAAK,KAAA;IA6BnB,eAAe,iBAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxC;IAEF,eAAe;;;;;MAkBb;IAEF,gBAAgB,sCAQd;IAEF,kBAAkB,sBAIhB;CACH"}
|