@deck.gl-community/graph-layers 9.1.0-beta.8 → 9.2.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 (252) hide show
  1. package/LICENSE +1 -1
  2. package/dist/_deprecated/old-constants.d.ts +107 -0
  3. package/dist/_deprecated/old-constants.d.ts.map +1 -0
  4. package/dist/_deprecated/old-constants.js +111 -0
  5. package/dist/_deprecated/old-constants.js.map +1 -0
  6. package/dist/core/cache.d.ts +0 -1
  7. package/dist/core/cache.js +0 -1
  8. package/dist/core/constants.d.ts +12 -100
  9. package/dist/core/constants.d.ts.map +1 -1
  10. package/dist/core/constants.js +3 -44
  11. package/dist/core/constants.js.map +1 -1
  12. package/dist/core/graph-engine.d.ts +12 -11
  13. package/dist/core/graph-engine.d.ts.map +1 -1
  14. package/dist/core/graph-engine.js +22 -11
  15. package/dist/core/graph-engine.js.map +1 -1
  16. package/dist/core/graph-layout.d.ts +48 -21
  17. package/dist/core/graph-layout.d.ts.map +1 -1
  18. package/dist/core/graph-layout.js +91 -24
  19. package/dist/core/graph-layout.js.map +1 -1
  20. package/dist/core/interaction-manager.d.ts +6 -4
  21. package/dist/core/interaction-manager.d.ts.map +1 -1
  22. package/dist/core/interaction-manager.js +59 -17
  23. package/dist/core/interaction-manager.js.map +1 -1
  24. package/dist/graph/edge.d.ts +7 -7
  25. package/dist/graph/edge.d.ts.map +1 -1
  26. package/dist/graph/edge.js +3 -6
  27. package/dist/graph/edge.js.map +1 -1
  28. package/dist/graph/graph.d.ts +2 -3
  29. package/dist/graph/graph.js +8 -9
  30. package/dist/graph/graph.js.map +1 -1
  31. package/dist/graph/node.d.ts +7 -8
  32. package/dist/graph/node.d.ts.map +1 -1
  33. package/dist/graph/node.js +3 -5
  34. package/dist/graph/node.js.map +1 -1
  35. package/dist/graph-style-schema.cdn.d.ts +2 -0
  36. package/dist/graph-style-schema.cdn.js +2 -0
  37. package/dist/graph-style-schema.json +12 -0
  38. package/dist/index.cjs +2821 -549
  39. package/dist/index.cjs.map +4 -4
  40. package/dist/index.d.ts +28 -22
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +25 -21
  43. package/dist/index.js.map +1 -1
  44. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-fragment.glsl.d.ts +0 -1
  45. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-fragment.glsl.js +0 -1
  46. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex-tf.glsl.d.ts +0 -1
  47. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex-tf.glsl.js +0 -1
  48. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex.glsl.d.ts +0 -1
  49. package/dist/layers/common-layers/flow-path-layer/flow-path-layer-vertex.glsl.js +0 -1
  50. package/dist/layers/common-layers/flow-path-layer/flow-path-layer.d.ts +0 -1
  51. package/dist/layers/common-layers/flow-path-layer/flow-path-layer.js +0 -1
  52. package/dist/layers/common-layers/marker-layer/atlas-data-url.d.ts +0 -1
  53. package/dist/layers/common-layers/marker-layer/atlas-data-url.js +0 -1
  54. package/dist/layers/common-layers/marker-layer/marker-layer.d.ts +0 -1
  55. package/dist/layers/common-layers/marker-layer/marker-layer.js +2 -3
  56. package/dist/layers/common-layers/marker-layer/marker-list.d.ts +2 -63
  57. package/dist/layers/common-layers/marker-layer/marker-list.d.ts.map +1 -1
  58. package/dist/layers/common-layers/marker-layer/marker-list.js +1 -65
  59. package/dist/layers/common-layers/marker-layer/marker-list.js.map +1 -1
  60. package/dist/layers/common-layers/marker-layer/marker-mapping.d.ts +0 -1
  61. package/dist/layers/common-layers/marker-layer/marker-mapping.js +0 -1
  62. package/dist/layers/common-layers/spline-layer/spline-layer.d.ts +0 -1
  63. package/dist/layers/common-layers/spline-layer/spline-layer.js +0 -1
  64. package/dist/layers/common-layers/zoomable-text-layer/zoomable-text-layer.d.ts +0 -1
  65. package/dist/layers/common-layers/zoomable-text-layer/zoomable-text-layer.js +0 -1
  66. package/dist/layers/edge-attachment-helper.d.ts +15 -0
  67. package/dist/layers/edge-attachment-helper.d.ts.map +1 -0
  68. package/dist/layers/edge-attachment-helper.js +230 -0
  69. package/dist/layers/edge-attachment-helper.js.map +1 -0
  70. package/dist/layers/edge-layer.d.ts +1 -5
  71. package/dist/layers/edge-layer.d.ts.map +1 -1
  72. package/dist/layers/edge-layer.js +9 -11
  73. package/dist/layers/edge-layer.js.map +1 -1
  74. package/dist/layers/edge-layers/arrow-2d-geometry.d.ts +4 -0
  75. package/dist/layers/edge-layers/arrow-2d-geometry.d.ts.map +1 -0
  76. package/dist/layers/edge-layers/arrow-2d-geometry.js +42 -0
  77. package/dist/layers/edge-layers/arrow-2d-geometry.js.map +1 -0
  78. package/dist/layers/edge-layers/curved-edge-layer.d.ts +1 -2
  79. package/dist/layers/edge-layers/curved-edge-layer.js +1 -2
  80. package/dist/layers/edge-layers/edge-arrow-layer.d.ts +21 -0
  81. package/dist/layers/edge-layers/edge-arrow-layer.d.ts.map +1 -0
  82. package/dist/layers/edge-layers/edge-arrow-layer.js +131 -0
  83. package/dist/layers/edge-layers/edge-arrow-layer.js.map +1 -0
  84. package/dist/layers/edge-layers/edge-label-layer.d.ts +1 -2
  85. package/dist/layers/edge-layers/edge-label-layer.js +1 -2
  86. package/dist/layers/edge-layers/flow-layer.d.ts +1 -2
  87. package/dist/layers/edge-layers/flow-layer.js +1 -2
  88. package/dist/layers/edge-layers/path-edge-layer.d.ts +0 -1
  89. package/dist/layers/edge-layers/path-edge-layer.js +0 -1
  90. package/dist/layers/edge-layers/straight-line-edge-layer.d.ts +0 -1
  91. package/dist/layers/edge-layers/straight-line-edge-layer.js +0 -1
  92. package/dist/layers/graph-layer.d.ts +22 -23
  93. package/dist/layers/graph-layer.d.ts.map +1 -1
  94. package/dist/layers/graph-layer.js +218 -62
  95. package/dist/layers/graph-layer.js.map +1 -1
  96. package/dist/layers/node-layers/circle-layer.d.ts +0 -1
  97. package/dist/layers/node-layers/circle-layer.js +0 -1
  98. package/dist/layers/node-layers/image-layer.d.ts +0 -1
  99. package/dist/layers/node-layers/image-layer.js +0 -1
  100. package/dist/layers/node-layers/label-layer.d.ts +1 -2
  101. package/dist/layers/node-layers/label-layer.js +1 -2
  102. package/dist/layers/node-layers/path-rounded-rectangle-layer.d.ts +0 -1
  103. package/dist/layers/node-layers/path-rounded-rectangle-layer.js +1 -2
  104. package/dist/layers/node-layers/rectangle-layer.d.ts +0 -1
  105. package/dist/layers/node-layers/rectangle-layer.js +0 -1
  106. package/dist/layers/node-layers/rounded-rectangle-layer-fragment.d.ts +0 -1
  107. package/dist/layers/node-layers/rounded-rectangle-layer-fragment.js +0 -1
  108. package/dist/layers/node-layers/rounded-rectangle-layer.d.ts +1 -2
  109. package/dist/layers/node-layers/rounded-rectangle-layer.js +2 -3
  110. package/dist/layers/node-layers/zoomable-marker-layer.d.ts +1 -2
  111. package/dist/layers/node-layers/zoomable-marker-layer.js +1 -2
  112. package/dist/layouts/d3-dag/d3-dag-layout.d.ts +117 -0
  113. package/dist/layouts/d3-dag/d3-dag-layout.d.ts.map +1 -0
  114. package/dist/layouts/d3-dag/d3-dag-layout.js +716 -0
  115. package/dist/layouts/d3-dag/d3-dag-layout.js.map +1 -0
  116. package/dist/layouts/d3-force/d3-force-layout.d.ts +4 -4
  117. package/dist/layouts/d3-force/d3-force-layout.d.ts.map +1 -1
  118. package/dist/layouts/d3-force/d3-force-layout.js +25 -10
  119. package/dist/layouts/d3-force/d3-force-layout.js.map +1 -1
  120. package/dist/layouts/d3-force/worker.d.ts +0 -1
  121. package/dist/layouts/d3-force/worker.js +0 -1
  122. package/dist/layouts/experimental/force-multi-graph-layout.d.ts +9 -8
  123. package/dist/layouts/experimental/force-multi-graph-layout.d.ts.map +1 -1
  124. package/dist/layouts/experimental/force-multi-graph-layout.js +15 -11
  125. package/dist/layouts/experimental/force-multi-graph-layout.js.map +1 -1
  126. package/dist/layouts/experimental/hive-plot-layout.d.ts +11 -8
  127. package/dist/layouts/experimental/hive-plot-layout.d.ts.map +1 -1
  128. package/dist/layouts/experimental/hive-plot-layout.js +12 -7
  129. package/dist/layouts/experimental/hive-plot-layout.js.map +1 -1
  130. package/dist/layouts/experimental/radial-layout.d.ts +10 -7
  131. package/dist/layouts/experimental/radial-layout.d.ts.map +1 -1
  132. package/dist/layouts/experimental/radial-layout.js +11 -6
  133. package/dist/layouts/experimental/radial-layout.js.map +1 -1
  134. package/dist/layouts/gpu-force/gpu-force-layout.d.ts +4 -4
  135. package/dist/layouts/gpu-force/gpu-force-layout.d.ts.map +1 -1
  136. package/dist/layouts/gpu-force/gpu-force-layout.js +18 -9
  137. package/dist/layouts/gpu-force/gpu-force-layout.js.map +1 -1
  138. package/dist/layouts/gpu-force/worker.d.ts +0 -1
  139. package/dist/layouts/gpu-force/worker.js +0 -1
  140. package/dist/layouts/simple-layout.d.ts +19 -12
  141. package/dist/layouts/simple-layout.d.ts.map +1 -1
  142. package/dist/layouts/simple-layout.js +26 -15
  143. package/dist/layouts/simple-layout.js.map +1 -1
  144. package/dist/loaders/create-graph.d.ts +1 -2
  145. package/dist/loaders/create-graph.js +3 -4
  146. package/dist/loaders/edge-parsers.d.ts +1 -2
  147. package/dist/loaders/edge-parsers.js +2 -3
  148. package/dist/loaders/edge-parsers.js.map +1 -1
  149. package/dist/loaders/json-loader.d.ts +2 -3
  150. package/dist/loaders/json-loader.d.ts.map +1 -1
  151. package/dist/loaders/json-loader.js +5 -6
  152. package/dist/loaders/json-loader.js.map +1 -1
  153. package/dist/loaders/node-parsers.d.ts +1 -2
  154. package/dist/loaders/node-parsers.js +2 -3
  155. package/dist/loaders/node-parsers.js.map +1 -1
  156. package/dist/loaders/simple-json-graph-loader.d.ts +0 -1
  157. package/dist/loaders/simple-json-graph-loader.d.ts.map +1 -1
  158. package/dist/loaders/simple-json-graph-loader.js +5 -6
  159. package/dist/loaders/simple-json-graph-loader.js.map +1 -1
  160. package/dist/loaders/table-graph-loader.d.ts +3 -4
  161. package/dist/loaders/table-graph-loader.js +5 -6
  162. package/dist/loaders/table-graph-loader.js.map +1 -1
  163. package/dist/style/graph-layer-stylesheet.d.ts +34 -0
  164. package/dist/style/graph-layer-stylesheet.d.ts.map +1 -0
  165. package/dist/style/graph-layer-stylesheet.js +39 -0
  166. package/dist/style/graph-layer-stylesheet.js.map +1 -0
  167. package/dist/style/graph-style-accessor-map.d.ts +93 -0
  168. package/dist/style/graph-style-accessor-map.d.ts.map +1 -0
  169. package/dist/style/graph-style-accessor-map.js +93 -0
  170. package/dist/style/graph-style-accessor-map.js.map +1 -0
  171. package/dist/style/graph-style-engine.d.ts +10 -0
  172. package/dist/style/graph-style-engine.d.ts.map +1 -0
  173. package/dist/style/graph-style-engine.js +163 -0
  174. package/dist/style/graph-style-engine.js.map +1 -0
  175. package/dist/style/graph-stylesheet.schema.d.ts +310 -0
  176. package/dist/style/graph-stylesheet.schema.d.ts.map +1 -0
  177. package/dist/style/graph-stylesheet.schema.js +237 -0
  178. package/dist/style/graph-stylesheet.schema.js.map +1 -0
  179. package/dist/style/style-engine.d.ts +33 -0
  180. package/dist/style/style-engine.d.ts.map +1 -0
  181. package/dist/style/style-engine.js +121 -0
  182. package/dist/style/style-engine.js.map +1 -0
  183. package/dist/style/style-property.d.ts +2 -3
  184. package/dist/style/style-property.d.ts.map +1 -1
  185. package/dist/style/style-property.js +224 -48
  186. package/dist/style/style-property.js.map +1 -1
  187. package/dist/utils/collapsed-chains.d.ts +17 -0
  188. package/dist/utils/collapsed-chains.d.ts.map +1 -0
  189. package/dist/utils/collapsed-chains.js +197 -0
  190. package/dist/utils/collapsed-chains.js.map +1 -0
  191. package/dist/utils/layer-utils.d.ts +0 -1
  192. package/dist/utils/layer-utils.d.ts.map +1 -1
  193. package/dist/utils/layer-utils.js +0 -1
  194. package/dist/utils/log.d.ts +2 -1
  195. package/dist/utils/log.d.ts.map +1 -1
  196. package/dist/utils/log.js +12 -2
  197. package/dist/utils/log.js.map +1 -1
  198. package/dist/utils/node-boundary.d.ts +10 -0
  199. package/dist/utils/node-boundary.d.ts.map +1 -0
  200. package/dist/utils/node-boundary.js +130 -0
  201. package/dist/utils/node-boundary.js.map +1 -0
  202. package/dist/utils/polygon-calculations.d.ts +0 -1
  203. package/dist/utils/polygon-calculations.js +0 -1
  204. package/dist/widgets/long-press-button.d.ts +0 -1
  205. package/dist/widgets/long-press-button.js +0 -1
  206. package/dist/widgets/view-control-widget.d.ts +4 -5
  207. package/dist/widgets/view-control-widget.d.ts.map +1 -1
  208. package/dist/widgets/view-control-widget.js +10 -8
  209. package/dist/widgets/view-control-widget.js.map +1 -1
  210. package/package.json +23 -7
  211. package/src/_deprecated/old-constants.ts +122 -0
  212. package/src/core/constants.ts +21 -43
  213. package/src/core/graph-engine.ts +24 -9
  214. package/src/core/graph-layout.ts +133 -28
  215. package/src/core/interaction-manager.ts +80 -20
  216. package/src/graph/edge.ts +6 -6
  217. package/src/graph/graph.ts +7 -7
  218. package/src/graph/node.ts +6 -6
  219. package/src/index.ts +31 -6
  220. package/src/layers/common-layers/marker-layer/marker-list.ts +62 -64
  221. package/src/layers/edge-attachment-helper.ts +355 -0
  222. package/src/layers/edge-layer.ts +6 -7
  223. package/src/layers/edge-layers/arrow-2d-geometry.ts +51 -0
  224. package/src/layers/edge-layers/edge-arrow-layer.ts +171 -0
  225. package/src/layers/graph-layer.ts +304 -86
  226. package/src/layouts/d3-dag/d3-dag-layout.ts +969 -0
  227. package/src/layouts/d3-force/d3-force-layout.ts +33 -11
  228. package/src/layouts/experimental/force-multi-graph-layout.ts +22 -13
  229. package/src/layouts/experimental/hive-plot-layout.ts +22 -10
  230. package/src/layouts/experimental/radial-layout.ts +20 -8
  231. package/src/layouts/gpu-force/gpu-force-layout.ts +22 -10
  232. package/src/layouts/simple-layout.ts +48 -25
  233. package/src/loaders/edge-parsers.ts +2 -2
  234. package/src/loaders/json-loader.ts +2 -2
  235. package/src/loaders/node-parsers.ts +2 -2
  236. package/src/loaders/simple-json-graph-loader.ts +2 -2
  237. package/src/loaders/table-graph-loader.ts +2 -2
  238. package/src/style/graph-layer-stylesheet.ts +99 -0
  239. package/src/style/graph-style-accessor-map.ts +103 -0
  240. package/src/style/graph-style-engine.ts +229 -0
  241. package/src/style/graph-stylesheet.schema.ts +344 -0
  242. package/src/style/style-engine.ts +168 -0
  243. package/src/style/style-property.ts +314 -51
  244. package/src/utils/collapsed-chains.ts +261 -0
  245. package/src/utils/log.ts +15 -1
  246. package/src/utils/node-boundary.ts +238 -0
  247. package/src/widgets/view-control-widget.tsx +15 -13
  248. package/dist/style/style-sheet.d.ts +0 -11
  249. package/dist/style/style-sheet.d.ts.map +0 -1
  250. package/dist/style/style-sheet.js +0 -253
  251. package/dist/style/style-sheet.js.map +0 -1
  252. package/src/style/style-sheet.ts +0 -277
@@ -3,7 +3,23 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import Color from 'color';
6
- import {log} from '../utils/log';
6
+ import {
7
+ scaleLinear,
8
+ scaleLog,
9
+ scaleOrdinal,
10
+ scalePow,
11
+ scaleQuantile,
12
+ scaleQuantize,
13
+ scaleSqrt
14
+ } from 'd3-scale';
15
+
16
+ import {warn} from '../utils/log';
17
+ import type {
18
+ GraphStyleAttributeReference,
19
+ GraphStyleLeafValue,
20
+ GraphStyleScale,
21
+ GraphStyleScaleType
22
+ } from './graph-style-engine';
7
23
 
8
24
  /* Utils for type check */
9
25
  function getColor(value) {
@@ -133,29 +149,292 @@ const DEFAULT_STYLES = {
133
149
  scaleWithZoom: true
134
150
  };
135
151
 
136
- // code generation: generate a function as a layer accessor
137
- function generateAccessor(key, value) {
152
+ /** Union of supported D3 scale implementations. */
153
+ type SupportedScale =
154
+ | ReturnType<typeof scaleLinear>
155
+ | ReturnType<typeof scaleLog>
156
+ | ReturnType<typeof scalePow>
157
+
158
+ | ReturnType<typeof scaleQuantize>
159
+ | ReturnType<typeof scaleQuantile>
160
+ | ReturnType<typeof scaleOrdinal>;
161
+
162
+ const SCALE_FACTORIES: Record<GraphStyleScaleType, () => SupportedScale> = {
163
+ linear: () => scaleLinear(),
164
+ log: () => scaleLog(),
165
+ pow: () => scalePow(),
166
+ sqrt: () => scaleSqrt(),
167
+ quantize: () => scaleQuantize(),
168
+ quantile: () => scaleQuantile(),
169
+ ordinal: () => scaleOrdinal()
170
+ };
171
+
172
+ /** Resolved attribute reference with guaranteed defaults. */
173
+ type NormalizedAttributeReference = {
174
+ attribute: string;
175
+ fallback: unknown;
176
+ scale?: (value: unknown) => unknown;
177
+ scaleConfig?: GraphStyleScale | ((value: unknown) => unknown);
178
+ };
179
+
180
+ /** Create a D3 scale instance based on a declarative configuration. */
181
+ /* eslint-disable-next-line complexity */
182
+ function createScaleFromConfig(config: GraphStyleScale): SupportedScale {
183
+ const type = config.type ?? 'linear';
184
+ const factory = SCALE_FACTORIES[type];
185
+ if (!factory) {
186
+ warn(`Invalid scale type: ${type}`);
187
+ throw new Error(`Invalid scale type: ${type}`);
188
+ }
189
+ const scale = (factory as () => SupportedScale)();
190
+ const anyScale = scale as any;
191
+ if (config.domain && 'domain' in scale) {
192
+ anyScale.domain(config.domain as never);
193
+ }
194
+ if (config.range && 'range' in scale) {
195
+ anyScale.range(config.range as never);
196
+ }
197
+ if (typeof config.clamp === 'boolean' && 'clamp' in scale && typeof anyScale.clamp === 'function') {
198
+ anyScale.clamp(config.clamp);
199
+ }
200
+ if (typeof config.nice !== 'undefined' && 'nice' in scale && typeof anyScale.nice === 'function') {
201
+ anyScale.nice(config.nice as never);
202
+ }
203
+ if (
204
+ type === 'pow' &&
205
+ typeof config.exponent === 'number' &&
206
+ 'exponent' in scale &&
207
+ typeof anyScale.exponent === 'function'
208
+ ) {
209
+ anyScale.exponent(config.exponent);
210
+ }
211
+ if (
212
+ type === 'log' &&
213
+ typeof config.base === 'number' &&
214
+ 'base' in scale &&
215
+ typeof anyScale.base === 'function'
216
+ ) {
217
+ anyScale.base(config.base);
218
+ }
219
+ if (
220
+ typeof config.unknown !== 'undefined' &&
221
+ 'unknown' in scale &&
222
+ typeof (scale as {unknown?: (value: unknown) => unknown}).unknown === 'function'
223
+ ) {
224
+ (scale as {unknown: (value: unknown) => unknown}).unknown(config.unknown);
225
+ }
226
+ return scale;
227
+ }
228
+
229
+ /** Normalize attribute reference definitions into a consistent structure. */
230
+ function normalizeAttributeReference(
231
+ key: string,
232
+ reference: GraphStyleAttributeReference
233
+ ): NormalizedAttributeReference {
234
+ if (typeof reference === 'string') {
235
+ const attribute = reference.startsWith('@') ? reference.slice(1) : reference;
236
+ if (!attribute) {
237
+ throw new Error(`Invalid attribute reference for ${key}: ${reference}`);
238
+ }
239
+ return {
240
+ attribute,
241
+ fallback: DEFAULT_STYLES[key]
242
+ };
243
+ }
244
+
245
+ const {attribute, fallback = DEFAULT_STYLES[key], scale} = reference;
246
+ if (!attribute) {
247
+ throw new Error(`Invalid attribute reference for ${key}: ${JSON.stringify(reference)}`);
248
+ }
249
+
250
+ let scaleFn: ((value: unknown) => unknown) | undefined;
251
+ let scaleConfig: GraphStyleScale | ((value: unknown) => unknown) | undefined;
252
+
253
+ if (scale) {
254
+ if (typeof scale === 'function') {
255
+ scaleFn = scale;
256
+ scaleConfig = scale;
257
+ } else {
258
+ scaleFn = createScaleFromConfig(scale);
259
+ scaleConfig = scale;
260
+ }
261
+ }
262
+
263
+ return {
264
+ attribute,
265
+ fallback,
266
+ scale: scaleFn,
267
+ scaleConfig
268
+ };
269
+ }
270
+
271
+ /** Determine whether a value points to a graph attribute reference. */
272
+ function isAttributeReference(value: unknown): value is GraphStyleAttributeReference {
273
+ if (typeof value === 'string') {
274
+ return value.startsWith('@');
275
+ }
276
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && 'attribute' in (value as Record<string, unknown>);
277
+ }
278
+
279
+ /** Determine whether a style value maps interaction states. */
280
+ function isStatefulValue(value: unknown): value is Record<string, GraphStyleLeafValue> {
281
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && !isAttributeReference(value);
282
+ }
283
+
284
+ /** Resolve an attribute from a datum or `Graph` entity. */
285
+ function getAttributeValue(datum: any, attribute: string) {
286
+ if (datum && typeof datum.getPropertyValue === 'function') {
287
+ return datum.getPropertyValue(attribute);
288
+ }
289
+ if (datum && typeof datum === 'object' && attribute in datum) {
290
+ return datum[attribute];
291
+ }
292
+ return undefined;
293
+ }
294
+
295
+ /** Combine Deck.gl update triggers while filtering falsey entries. */
296
+ function mergeUpdateTriggers(...triggers: unknown[]): unknown {
297
+ const filtered = triggers.filter(
298
+ (trigger) => !(trigger === false || trigger === undefined || trigger === null)
299
+ );
300
+ if (!filtered.length) {
301
+ return false;
302
+ }
303
+ if (filtered.length === 1) {
304
+ return filtered[0];
305
+ }
306
+ return filtered;
307
+ }
308
+
309
+ /** Build an accessor that reads and optionally scales an attribute. */
310
+ function createAttributeAccessor(
311
+ key: string,
312
+ attributeRef: NormalizedAttributeReference,
313
+ formatter: (value: unknown) => unknown
314
+ ) {
315
+ const accessor = (datum: any) => {
316
+ let raw = getAttributeValue(datum, attributeRef.attribute);
317
+ if (raw === undefined || raw === null) {
318
+ raw = attributeRef.fallback;
319
+ }
320
+ if (attributeRef.scale) {
321
+ raw = attributeRef.scale(raw);
322
+ }
323
+ const formatted = formatter(raw);
324
+ if (formatted === null) {
325
+ warn(`Invalid ${key} value: ${raw}`);
326
+ throw new Error(`Invalid ${key} value: ${raw}`);
327
+ }
328
+ return formatted;
329
+ };
330
+
331
+ const updateTrigger = {
332
+ attribute: attributeRef.attribute,
333
+ scale: attributeRef.scaleConfig ?? null
334
+ };
335
+
336
+ return {accessor, updateTrigger};
337
+ }
338
+
339
+ /** Result of parsing a leaf style value. */
340
+ type LeafParseResult = {
341
+ value: any;
342
+ isAccessor: boolean;
343
+ updateTrigger: unknown;
344
+ };
345
+
346
+ function describeStyleValue(value: unknown): string {
347
+ if (typeof value === 'string') {
348
+ return value;
349
+ }
350
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'undefined') {
351
+ return String(value);
352
+ }
353
+ if (value === null) {
354
+ return 'null';
355
+ }
356
+ if (typeof value === 'function') {
357
+ return value.name ? `[Function ${value.name}]` : '[Function]';
358
+ }
359
+ if (Array.isArray(value)) {
360
+ return `[${value.map((item) => describeStyleValue(item)).join(', ')}]`;
361
+ }
362
+ try {
363
+ return JSON.stringify(value);
364
+ } catch {
365
+ return String(value);
366
+ }
367
+ }
368
+
369
+ /** Parse a non-stateful style value into deck.gl compatible form. */
370
+ function parseLeafValue(key: string, value: GraphStyleLeafValue | undefined): LeafParseResult {
138
371
  const formatter = PROPERTY_FORMATTERS[key] || IDENTITY;
139
- // ex: key = 'fill', value = {defaut: 'red', hover: 'blue'}
140
- // valueMap => {defaut: [255, 0, 0], hover: [0, 0, 255]}
141
- const valueMap = Object.keys(value).reduce((res, key0) => {
142
- res[key0] = value[key0];
143
- return res;
144
- }, {}) as any;
145
-
146
- return (node) => {
147
- const statefulValue = valueMap[node.state];
148
- if (!node.state || typeof statefulValue === 'undefined') {
149
- return valueMap.default || DEFAULT_STYLES[key];
372
+
373
+ if (typeof value === 'undefined') {
374
+ const formatted = formatter(DEFAULT_STYLES[key]);
375
+ if (formatted === null) {
376
+ const description = describeStyleValue(value);
377
+ warn(`Invalid ${key} value: ${description}`);
378
+ throw new Error(`Invalid ${key} value: ${description}`);
150
379
  }
151
- // else has stateful value
152
- // check if the value is a function
153
- if (typeof statefulValue === 'function') {
154
- return formatter(statefulValue(node));
380
+ return {value: formatted, isAccessor: false, updateTrigger: false};
381
+ }
382
+
383
+ if (isAttributeReference(value)) {
384
+ const normalized = normalizeAttributeReference(key, value);
385
+ const {accessor, updateTrigger} = createAttributeAccessor(key, normalized, formatter);
386
+ return {value: accessor, isAccessor: true, updateTrigger};
387
+ }
388
+
389
+ if (typeof value === 'function') {
390
+ return {
391
+ value: (datum) => formatter(value(datum)),
392
+ isAccessor: true,
393
+ updateTrigger: value
394
+ };
395
+ }
396
+
397
+ const formatted = formatter(value);
398
+ if (formatted === null) {
399
+ const description = describeStyleValue(value);
400
+ warn(`Invalid ${key} value: ${description}`);
401
+ throw new Error(`Invalid ${key} value: ${description}`);
402
+ }
403
+
404
+ return {value: formatted, isAccessor: false, updateTrigger: false};
405
+ }
406
+
407
+ /**
408
+ * Create an accessor capable of handling interaction state overrides for a style property.
409
+ */
410
+ function createStatefulAccessor(
411
+ key: string,
412
+ value: Record<string, GraphStyleLeafValue>,
413
+ stateUpdateTrigger: unknown
414
+ ) {
415
+ const valueMap: Record<string, any> = {};
416
+ const attributeTriggers: unknown[] = [];
417
+
418
+ for (const state of Object.keys(value)) {
419
+ const parsed = parseLeafValue(key, value[state]);
420
+ valueMap[state] = parsed.value;
421
+ if (parsed.updateTrigger) {
422
+ attributeTriggers.push(parsed.updateTrigger);
155
423
  }
156
- // or just a plain value
157
- return formatter(statefulValue);
424
+ }
425
+
426
+ const defaultValue =
427
+ typeof valueMap.default !== 'undefined' ? valueMap.default : parseLeafValue(key, undefined).value;
428
+
429
+ const accessor = (datum: any) => {
430
+ const stateValue = datum?.state ? valueMap[datum.state] : undefined;
431
+ const candidate = typeof stateValue !== 'undefined' ? stateValue : defaultValue;
432
+ return typeof candidate === 'function' ? candidate(datum) : candidate;
158
433
  };
434
+
435
+ const updateTrigger = mergeUpdateTriggers(stateUpdateTrigger, ...attributeTriggers);
436
+
437
+ return {accessor, updateTrigger};
159
438
  }
160
439
 
161
440
  const VALUE_TYPE = {
@@ -165,7 +444,7 @@ const VALUE_TYPE = {
165
444
 
166
445
  export class StyleProperty {
167
446
  key: any;
168
- _updateTrigger: boolean;
447
+ _updateTrigger: unknown;
169
448
  _value: any;
170
449
  _valueType: any;
171
450
 
@@ -180,40 +459,24 @@ export class StyleProperty {
180
459
  this.key = key;
181
460
  this._updateTrigger = false;
182
461
 
183
- // statefule property, ex:
184
- // fill: {default: 'red', hover: 'blue'}
185
- // note that offset: [0, 1], the type of array is object, too.
186
- if (typeof value === 'object' && !Array.isArray(value)) {
187
- // generate accessor function
188
- this._value = generateAccessor(key, value);
462
+ if (isStatefulValue(value)) {
463
+ const {accessor, updateTrigger: triggers} = createStatefulAccessor(
464
+ key,
465
+ value,
466
+ updateTrigger
467
+ );
468
+ this._value = accessor;
189
469
  this._valueType = VALUE_TYPE.ACCESSOR;
190
- this._updateTrigger = updateTrigger;
191
- }
192
- // default state property, but value = accessor
193
- // fill: () => 'red'
194
- else if (typeof value === 'function') {
195
- const formatter = PROPERTY_FORMATTERS[key] || IDENTITY;
196
- // the output of the function should be formated by
197
- // the corresponding formatter again.
198
- // Ex: colorAccessor might return '#f00', which needs to
199
- // be formated as [255, 0, 0];
200
- this._value = (d) => formatter(value(d));
201
- this._valueType = VALUE_TYPE.ACCESSOR;
202
- this._updateTrigger = value;
203
- }
204
- // default state property with plain value:
205
- // fill: 'red'
206
- else {
207
- // format the value properly
208
- const formatter = PROPERTY_FORMATTERS[key] || IDENTITY;
209
- this._value = formatter(value);
210
- this._valueType = VALUE_TYPE.PLAIN_VALUE;
211
- this._updateTrigger = false;
470
+ this._updateTrigger = triggers;
471
+ } else {
472
+ const parsed = parseLeafValue(key, value as GraphStyleLeafValue | undefined);
473
+ this._value = parsed.value;
474
+ this._valueType = parsed.isAccessor ? VALUE_TYPE.ACCESSOR : VALUE_TYPE.PLAIN_VALUE;
475
+ this._updateTrigger = mergeUpdateTriggers(parsed.updateTrigger);
212
476
  }
213
477
 
214
- // sanity check
215
478
  if (this._value === null) {
216
- log.warn(`Invalid ${key} value: ${value}`)();
479
+ warn(`Invalid ${key} value: ${value}`);
217
480
  throw new Error(`Invalid ${key} value: ${value}`);
218
481
  }
219
482
  }
@@ -0,0 +1,261 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {GraphEngine} from '../core/graph-engine';
6
+ import type {Node} from '../graph/node';
7
+
8
+ const OUTLINE_PADDING = 24;
9
+ const OUTLINE_CORNER_RADIUS = 16;
10
+ const OUTLINE_CORNER_SEGMENTS = 6;
11
+
12
+ export type ChainInteractionSource =
13
+ | 'node'
14
+ | 'collapsed-marker'
15
+ | 'expanded-marker'
16
+ | 'collapsed-outline'
17
+ | 'expanded-outline';
18
+
19
+ function resolveLayerId(layer: any): string {
20
+ if (!layer) {
21
+ return '';
22
+ }
23
+ if (typeof layer.id === 'string') {
24
+ return layer.id;
25
+ }
26
+ if (typeof layer.props?.id === 'string') {
27
+ return layer.props.id;
28
+ }
29
+ return '';
30
+ }
31
+
32
+ function classifyChainLayer(layer: any): ChainInteractionSource | null {
33
+ let current = layer ?? null;
34
+ while (current) {
35
+ const layerId = resolveLayerId(current);
36
+ if (layerId.includes('collapsed-chain-markers')) {
37
+ return 'collapsed-marker';
38
+ }
39
+ if (layerId.includes('expanded-chain-markers')) {
40
+ return 'expanded-marker';
41
+ }
42
+ if (layerId.includes('collapsed-chain-outlines')) {
43
+ return 'collapsed-outline';
44
+ }
45
+ if (layerId.includes('expanded-chain-outlines')) {
46
+ return 'expanded-outline';
47
+ }
48
+ current = current.parent ?? null;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ export function resolveChainInteractionSource(info: any): ChainInteractionSource {
54
+ if (!info) {
55
+ return 'node';
56
+ }
57
+
58
+ const layersToCheck = [] as any[];
59
+ if (info.layer || info.sourceLayer) {
60
+ if (info.layer) {
61
+ layersToCheck.push(info.layer);
62
+ }
63
+ if (info.sourceLayer && info.sourceLayer !== info.layer) {
64
+ layersToCheck.push(info.sourceLayer);
65
+ }
66
+ } else {
67
+ layersToCheck.push(info);
68
+ }
69
+
70
+ for (const layer of layersToCheck) {
71
+ const classification = classifyChainLayer(layer);
72
+ if (classification) {
73
+ return classification;
74
+ }
75
+ }
76
+
77
+ return 'node';
78
+ }
79
+
80
+ function isChainRepresentative(node: Node): boolean {
81
+ const chainId = node.getPropertyValue('collapsedChainId');
82
+ const nodeIds = node.getPropertyValue('collapsedNodeIds');
83
+ const representativeId = node.getPropertyValue('collapsedChainRepresentativeId');
84
+
85
+ return (
86
+ Boolean(chainId) &&
87
+ Array.isArray(nodeIds) &&
88
+ nodeIds.length > 1 &&
89
+ representativeId === node.getId()
90
+ );
91
+ }
92
+
93
+ export function getRepresentativeNodes(engine: GraphEngine | null | undefined): Node[] {
94
+ if (!engine) {
95
+ return [];
96
+ }
97
+
98
+ return engine.getNodes().filter((node) => isChainRepresentative(node));
99
+ }
100
+
101
+ export type ChainOutlineGetter = (node: Node) => [number, number][] | null;
102
+
103
+ export function createChainOutlineGetter(engine: GraphEngine | null | undefined): ChainOutlineGetter {
104
+ if (!engine) {
105
+ return () => null;
106
+ }
107
+
108
+ const graph = engine.props.graph;
109
+ const cache = new Map<string, [number, number][] | null>();
110
+
111
+ // eslint-disable-next-line max-statements, complexity
112
+ return (node: Node): [number, number][] | null => {
113
+ const chainId = node.getPropertyValue('collapsedChainId');
114
+ if (!chainId) {
115
+ return null;
116
+ }
117
+
118
+ const cacheKey = String(chainId);
119
+ if (cache.has(cacheKey)) {
120
+ return cache.get(cacheKey) ?? null;
121
+ }
122
+
123
+ if (!graph) {
124
+ cache.set(cacheKey, null);
125
+ return null;
126
+ }
127
+
128
+ const collapsedNodeIds = node.getPropertyValue('collapsedNodeIds');
129
+ if (!Array.isArray(collapsedNodeIds) || collapsedNodeIds.length === 0) {
130
+ cache.set(cacheKey, null);
131
+ return null;
132
+ }
133
+
134
+ let minX = Number.POSITIVE_INFINITY;
135
+ let maxX = Number.NEGATIVE_INFINITY;
136
+ let minY = Number.POSITIVE_INFINITY;
137
+ let maxY = Number.NEGATIVE_INFINITY;
138
+
139
+ for (const nodeId of collapsedNodeIds) {
140
+ const chainNode = graph.findNode(nodeId);
141
+ if (chainNode) {
142
+ const position = engine.getNodePosition(chainNode);
143
+ if (position) {
144
+ const [x, y] = position;
145
+ minX = Math.min(minX, x);
146
+ maxX = Math.max(maxX, x);
147
+ minY = Math.min(minY, y);
148
+ maxY = Math.max(maxY, y);
149
+ }
150
+ }
151
+ }
152
+
153
+ if (
154
+ !Number.isFinite(minX) ||
155
+ !Number.isFinite(maxX) ||
156
+ !Number.isFinite(minY) ||
157
+ !Number.isFinite(maxY)
158
+ ) {
159
+ cache.set(cacheKey, null);
160
+ return null;
161
+ }
162
+
163
+ const paddedMinX = minX - OUTLINE_PADDING;
164
+ const paddedMaxX = maxX + OUTLINE_PADDING;
165
+ const paddedMinY = minY - OUTLINE_PADDING;
166
+ const paddedMaxY = maxY + OUTLINE_PADDING;
167
+
168
+ const width = paddedMaxX - paddedMinX;
169
+ const height = paddedMaxY - paddedMinY;
170
+
171
+ if (width <= 0 || height <= 0) {
172
+ cache.set(cacheKey, null);
173
+ return null;
174
+ }
175
+
176
+ const radius = Math.min(OUTLINE_CORNER_RADIUS, width / 2, height / 2);
177
+
178
+ if (radius <= 0) {
179
+ const polygon: [number, number][] = [
180
+ [paddedMinX, paddedMinY],
181
+ [paddedMinX, paddedMaxY],
182
+ [paddedMaxX, paddedMaxY],
183
+ [paddedMaxX, paddedMinY],
184
+ [paddedMinX, paddedMinY]
185
+ ];
186
+ cache.set(cacheKey, polygon);
187
+ return polygon;
188
+ }
189
+
190
+ const left = paddedMinX;
191
+ const right = paddedMaxX;
192
+ const top = paddedMinY;
193
+ const bottom = paddedMaxY;
194
+
195
+ const polygon: [number, number][] = [];
196
+ const pushArc = (cx: number, cy: number, startAngle: number, endAngle: number) => {
197
+ const step = (endAngle - startAngle) / OUTLINE_CORNER_SEGMENTS;
198
+ for (let i = 1; i <= OUTLINE_CORNER_SEGMENTS; i++) {
199
+ const angle = startAngle + step * i;
200
+ polygon.push([cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)]);
201
+ }
202
+ };
203
+
204
+ polygon.push([right - radius, top]);
205
+ pushArc(right - radius, top + radius, -Math.PI / 2, 0);
206
+ polygon.push([right, bottom - radius]);
207
+ pushArc(right - radius, bottom - radius, 0, Math.PI / 2);
208
+ polygon.push([left + radius, bottom]);
209
+ pushArc(left + radius, bottom - radius, Math.PI / 2, Math.PI);
210
+ polygon.push([left, top + radius]);
211
+ pushArc(left + radius, top + radius, Math.PI, (3 * Math.PI) / 2);
212
+ polygon.push(polygon[0]);
213
+
214
+ cache.set(cacheKey, polygon);
215
+ return polygon;
216
+ };
217
+ }
218
+
219
+ export interface CollapsedChainLayerData {
220
+ representativeNodes: Node[];
221
+ collapsedNodes: Node[];
222
+ collapsedOutlineNodes: Node[];
223
+ expandedNodes: Node[];
224
+ expandedOutlineNodes: Node[];
225
+ getChainOutlinePolygon: ChainOutlineGetter;
226
+ outlineUpdateTrigger: string;
227
+ }
228
+
229
+ export function buildCollapsedChainLayers(
230
+ engine: GraphEngine | null | undefined
231
+ ): CollapsedChainLayerData | null {
232
+ if (!engine) {
233
+ return null;
234
+ }
235
+
236
+ const representativeNodes = getRepresentativeNodes(engine);
237
+ if (representativeNodes.length === 0) {
238
+ return null;
239
+ }
240
+
241
+ const getChainOutlinePolygon = createChainOutlineGetter(engine);
242
+ const outlineUpdateTrigger = [engine.getLayoutLastUpdate(), engine.getLayoutState()].join();
243
+
244
+ const collapsedNodes = representativeNodes.filter((node) =>
245
+ Boolean(node.getPropertyValue('isCollapsedChain'))
246
+ );
247
+ const collapsedOutlineNodes = collapsedNodes.filter((node) => getChainOutlinePolygon(node));
248
+
249
+ const expandedNodes = representativeNodes.filter((node) => !node.getPropertyValue('isCollapsedChain'));
250
+ const expandedOutlineNodes = expandedNodes.filter((node) => getChainOutlinePolygon(node));
251
+
252
+ return {
253
+ representativeNodes,
254
+ collapsedNodes,
255
+ collapsedOutlineNodes,
256
+ expandedNodes,
257
+ expandedOutlineNodes,
258
+ getChainOutlinePolygon,
259
+ outlineUpdateTrigger
260
+ };
261
+ }
package/src/utils/log.ts CHANGED
@@ -6,4 +6,18 @@ import {Log, COLOR} from '@probe.gl/log';
6
6
 
7
7
  export const log = new Log({id: 'graph-layers'}).enable();
8
8
 
9
- log.log({color: COLOR.CYAN}, 'Initialize graph-layers logger.')();
9
+ log.log({color: COLOR.CYAN}, 'Initialize graph-layers logger.');
10
+
11
+ function invokeLogFunction(result: unknown) {
12
+ if (typeof result === 'function') {
13
+ result();
14
+ }
15
+ }
16
+
17
+ export function warn(message: string, ...args: unknown[]) {
18
+ invokeLogFunction(log.warn(message, ...args));
19
+ }
20
+
21
+ export function error(message: string, ...args: unknown[]) {
22
+ invokeLogFunction(log.error(message, ...args));
23
+ }