@flowmap.gl/data 8.0.0-y.14 → 8.0.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 (58) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-dev.log +670 -0
  3. package/LICENSE +2 -2
  4. package/dist/FlowmapAggregateAccessors.d.ts +4 -4
  5. package/dist/FlowmapAggregateAccessors.d.ts.map +1 -1
  6. package/dist/FlowmapAggregateAccessors.js +16 -9
  7. package/dist/FlowmapSelectors.d.ts +39 -85
  8. package/dist/FlowmapSelectors.d.ts.map +1 -1
  9. package/dist/FlowmapSelectors.js +128 -144
  10. package/dist/FlowmapState.d.ts +7 -5
  11. package/dist/FlowmapState.d.ts.map +1 -1
  12. package/dist/FlowmapState.js +6 -1
  13. package/dist/cluster/ClusterIndex.d.ts +4 -4
  14. package/dist/cluster/ClusterIndex.d.ts.map +1 -1
  15. package/dist/cluster/ClusterIndex.js +5 -17
  16. package/dist/cluster/cluster.d.ts +20 -1
  17. package/dist/cluster/cluster.d.ts.map +1 -1
  18. package/dist/cluster/cluster.js +108 -52
  19. package/dist/colors.d.ts +3 -3
  20. package/dist/colors.d.ts.map +1 -1
  21. package/dist/colors.js +19 -8
  22. package/dist/getViewStateForLocations.d.ts +2 -2
  23. package/dist/getViewStateForLocations.d.ts.map +1 -1
  24. package/dist/getViewStateForLocations.js +18 -8
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +9 -1
  28. package/dist/provider/FlowmapDataProvider.d.ts +7 -2
  29. package/dist/provider/FlowmapDataProvider.d.ts.map +1 -1
  30. package/dist/provider/FlowmapDataProvider.js +11 -6
  31. package/dist/provider/LocalFlowmapDataProvider.d.ts +15 -4
  32. package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -1
  33. package/dist/provider/LocalFlowmapDataProvider.js +98 -81
  34. package/dist/selector-functions.d.ts +10 -0
  35. package/dist/selector-functions.d.ts.map +1 -0
  36. package/dist/selector-functions.js +65 -0
  37. package/dist/time.d.ts.map +1 -1
  38. package/dist/time.js +6 -1
  39. package/dist/types.d.ts +18 -16
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/types.js +9 -4
  42. package/dist/util.d.ts.map +1 -1
  43. package/dist/util.js +6 -1
  44. package/package.json +22 -23
  45. package/src/FlowmapAggregateAccessors.ts +21 -10
  46. package/src/FlowmapSelectors.ts +271 -264
  47. package/src/FlowmapState.ts +13 -5
  48. package/src/cluster/ClusterIndex.ts +23 -28
  49. package/src/cluster/cluster.ts +145 -56
  50. package/src/colors.ts +13 -9
  51. package/src/getViewStateForLocations.ts +6 -0
  52. package/src/index.ts +9 -0
  53. package/src/provider/FlowmapDataProvider.ts +23 -7
  54. package/src/provider/LocalFlowmapDataProvider.ts +68 -5
  55. package/src/selector-functions.ts +93 -0
  56. package/src/time.ts +6 -0
  57. package/src/types.ts +21 -13
  58. package/src/util.ts +6 -0
@@ -1,40 +1,29 @@
1
1
  /*
2
- * Copyright 2022 FlowmapBlue
3
- * Copyright 2018-2020 Teralytics, modified by FlowmapBlue
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- *
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
17
5
  */
18
6
 
19
- import {WebMercatorViewport} from '@math.gl/web-mercator';
20
- import {ascending, descending, extent, min} from 'd3-array';
21
- import {nest} from 'd3-collection';
22
- import {ScaleLinear, scaleLinear, scaleSqrt} from 'd3-scale';
7
+ import {ascending, descending, extent, min, rollup} from 'd3-array';
8
+ import {ScaleLinear, scaleSqrt} from 'd3-scale';
23
9
  import KDBush from 'kdbush';
24
10
  import {
11
+ ParametricSelector,
25
12
  createSelector,
26
13
  createSelectorCreator,
27
14
  defaultMemoize,
28
- ParametricSelector,
29
15
  } from 'reselect';
30
16
  import {alea} from 'seedrandom';
31
- import {clusterLocations} from './cluster/cluster';
17
+ import FlowmapAggregateAccessors from './FlowmapAggregateAccessors';
18
+ import {FlowmapState} from './FlowmapState';
32
19
  import {
33
- buildIndex,
34
20
  ClusterIndex,
21
+ LocationWeightGetter,
22
+ buildIndex,
35
23
  findAppropriateZoomLevel,
36
24
  makeLocationWeightGetter,
37
25
  } from './cluster/ClusterIndex';
26
+ import {clusterLocations} from './cluster/cluster';
38
27
  import getColors, {
39
28
  ColorsRGBA,
40
29
  DiffColorsRGBA,
@@ -44,17 +33,21 @@ import getColors, {
44
33
  isDiffColors,
45
34
  isDiffColorsRGBA,
46
35
  } from './colors';
47
- import FlowmapAggregateAccessors from './FlowmapAggregateAccessors';
48
- import {FlowmapState} from './FlowmapState';
49
36
  import {
37
+ addClusterNames,
38
+ getFlowThicknessScale,
39
+ getViewportBoundingBox,
40
+ } from './selector-functions';
41
+ import {
42
+ TimeGranularityKey,
50
43
  getTimeGranularityByKey,
51
44
  getTimeGranularityByOrder,
52
45
  getTimeGranularityForDate,
53
- TimeGranularityKey,
54
46
  } from './time';
55
47
  import {
56
48
  AggregateFlow,
57
49
  Cluster,
50
+ ClusterLevels,
58
51
  ClusterNode,
59
52
  CountByTime,
60
53
  FlowAccessors,
@@ -62,11 +55,10 @@ import {
62
55
  FlowLinesLayerAttributes,
63
56
  FlowmapData,
64
57
  FlowmapDataAccessors,
65
- isCluster,
66
- isLocationClusterNode,
67
58
  LayersData,
68
59
  LocationFilterMode,
69
60
  LocationTotals,
61
+ isLocationClusterNode,
70
62
  } from './types';
71
63
 
72
64
  const MAX_CLUSTER_ZOOM_LEVEL = 20;
@@ -78,7 +70,10 @@ export type Selector<L, F, T> = ParametricSelector<
78
70
  T
79
71
  >;
80
72
 
81
- export default class FlowmapSelectors<L, F> {
73
+ export default class FlowmapSelectors<
74
+ L extends Record<string, any>,
75
+ F extends Record<string, any>,
76
+ > {
82
77
  accessors: FlowmapAggregateAccessors<L, F>;
83
78
 
84
79
  constructor(accessors: FlowmapDataAccessors<L, F>) {
@@ -90,60 +85,71 @@ export default class FlowmapSelectors<L, F> {
90
85
  this.accessors = new FlowmapAggregateAccessors(accessors);
91
86
  }
92
87
 
93
- getFetchedFlows = (state: FlowmapState, props: FlowmapData<L, F>) =>
88
+ getAggregateAccessors(): FlowmapAggregateAccessors<L, F> {
89
+ return this.accessors;
90
+ }
91
+
92
+ getFlowsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
94
93
  props.flows;
95
- getFetchedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
94
+ getLocationsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
96
95
  props.locations;
96
+ getClusterLevelsFromProps = (
97
+ state: FlowmapState,
98
+ props: FlowmapData<L, F>,
99
+ ) => {
100
+ return props.clusterLevels;
101
+ };
97
102
  getMaxTopFlowsDisplayNum = (state: FlowmapState, props: FlowmapData<L, F>) =>
98
- state.settingsState.maxTopFlowsDisplayNum;
103
+ state.settings.maxTopFlowsDisplayNum;
99
104
  getSelectedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
100
- state.filterState.selectedLocations;
105
+ state.filter?.selectedLocations;
101
106
  getLocationFilterMode = (state: FlowmapState, props: FlowmapData<L, F>) =>
102
- state.filterState.locationFilterMode;
107
+ state.filter?.locationFilterMode;
103
108
  getClusteringEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
104
- state.settingsState.clusteringEnabled;
109
+ state.settings.clusteringEnabled;
105
110
  getLocationTotalsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
106
- state.settingsState.locationTotalsEnabled;
111
+ state.settings.locationTotalsEnabled;
112
+ getLocationLabelsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
113
+ state.settings.locationLabelsEnabled;
107
114
  getZoom = (state: FlowmapState, props: FlowmapData<L, F>) =>
108
115
  state.viewport.zoom;
109
116
  getViewport = (state: FlowmapState, props: FlowmapData<L, F>) =>
110
117
  state.viewport;
111
118
  getSelectedTimeRange = (state: FlowmapState, props: FlowmapData<L, F>) =>
112
- state.filterState.selectedTimeRange;
119
+ state.filter?.selectedTimeRange;
113
120
 
114
121
  getColorScheme: Selector<L, F, string | string[] | undefined> = (
115
122
  state: FlowmapState,
116
123
  props: FlowmapData<L, F>,
117
- ) => state.settingsState.colorScheme;
124
+ ) => state.settings.colorScheme;
118
125
 
119
126
  getDarkMode: Selector<L, F, boolean> = (
120
127
  state: FlowmapState,
121
128
  props: FlowmapData<L, F>,
122
- ) => state.settingsState.darkMode;
129
+ ) => state.settings.darkMode;
123
130
 
124
131
  getFadeEnabled: Selector<L, F, boolean> = (
125
132
  state: FlowmapState,
126
133
  props: FlowmapData<L, F>,
127
- ) => state.settingsState.fadeEnabled;
134
+ ) => state.settings.fadeEnabled;
128
135
 
129
136
  getFadeOpacityEnabled: Selector<L, F, boolean> = (
130
137
  state: FlowmapState,
131
138
  props: FlowmapData<L, F>,
132
- ) => state.settingsState.fadeOpacityEnabled;
139
+ ) => state.settings.fadeOpacityEnabled;
133
140
 
134
141
  getFadeAmount: Selector<L, F, number> = (
135
142
  state: FlowmapState,
136
143
  props: FlowmapData<L, F>,
137
- ) => state.settingsState.fadeAmount;
144
+ ) => state.settings.fadeAmount;
138
145
 
139
146
  getAnimate: Selector<L, F, boolean> = (
140
147
  state: FlowmapState,
141
148
  props: FlowmapData<L, F>,
142
- ) => state.settingsState.animationEnabled;
149
+ ) => state.settings.animationEnabled;
143
150
 
144
- getInvalidLocationIds: Selector<L, F, string[] | undefined> = createSelector(
145
- this.getFetchedLocations,
146
- (locations) => {
151
+ getInvalidLocationIds: Selector<L, F, (string | number)[] | undefined> =
152
+ createSelector(this.getLocationsFromProps, (locations) => {
147
153
  if (!locations) return undefined;
148
154
  const invalid = [];
149
155
  for (const location of locations) {
@@ -155,11 +161,10 @@ export default class FlowmapSelectors<L, F> {
155
161
  }
156
162
  }
157
163
  return invalid.length > 0 ? invalid : undefined;
158
- },
159
- );
164
+ });
160
165
 
161
166
  getLocations: Selector<L, F, Iterable<L> | undefined> = createSelector(
162
- this.getFetchedLocations,
167
+ this.getLocationsFromProps,
163
168
  this.getInvalidLocationIds,
164
169
  (locations, invalidIds) => {
165
170
  if (!locations) return undefined;
@@ -176,41 +181,43 @@ export default class FlowmapSelectors<L, F> {
176
181
  },
177
182
  );
178
183
 
179
- getLocationIds: Selector<L, F, Set<string> | undefined> = createSelector(
180
- this.getLocations,
181
- (locations) => {
184
+ getLocationIds: Selector<L, F, Set<string | number> | undefined> =
185
+ createSelector(this.getLocations, (locations) => {
182
186
  if (!locations) return undefined;
183
- const ids = new Set<string>();
187
+ const ids = new Set<string | number>();
184
188
  for (const id of locations) {
185
189
  ids.add(this.accessors.getLocationId(id));
186
190
  }
187
191
  return ids;
188
- },
189
- );
192
+ });
190
193
 
191
- getSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
194
+ getSelectedLocationsSet: Selector<L, F, Set<string | number> | undefined> =
192
195
  createSelector(this.getSelectedLocations, (ids) =>
193
196
  ids && ids.length > 0 ? new Set(ids) : undefined,
194
197
  );
195
198
 
196
199
  getSortedFlowsForKnownLocations: Selector<L, F, F[] | undefined> =
197
- createSelector(this.getFetchedFlows, this.getLocationIds, (flows, ids) => {
198
- if (!ids || !flows) return undefined;
199
- const filtered = [];
200
- for (const flow of flows) {
201
- const srcId = this.accessors.getFlowOriginId(flow);
202
- const dstId = this.accessors.getFlowDestId(flow);
203
- if (ids.has(srcId) && ids.has(dstId)) {
204
- filtered.push(flow);
200
+ createSelector(
201
+ this.getFlowsFromProps,
202
+ this.getLocationIds,
203
+ (flows, ids) => {
204
+ if (!ids || !flows) return undefined;
205
+ const filtered = [];
206
+ for (const flow of flows) {
207
+ const srcId = this.accessors.getFlowOriginId(flow);
208
+ const dstId = this.accessors.getFlowDestId(flow);
209
+ if (ids.has(srcId) && ids.has(dstId)) {
210
+ filtered.push(flow);
211
+ }
205
212
  }
206
- }
207
- return filtered.sort((a: F, b: F) =>
208
- descending(
209
- Math.abs(this.accessors.getFlowMagnitude(a)),
210
- Math.abs(this.accessors.getFlowMagnitude(b)),
211
- ),
212
- );
213
- });
213
+ return filtered.sort((a: F, b: F) =>
214
+ descending(
215
+ Math.abs(this.accessors.getFlowMagnitude(a)),
216
+ Math.abs(this.accessors.getFlowMagnitude(b)),
217
+ ),
218
+ );
219
+ },
220
+ );
214
221
 
215
222
  getActualTimeExtent: Selector<L, F, [Date, Date] | undefined> =
216
223
  createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
@@ -303,29 +310,33 @@ export default class FlowmapSelectors<L, F> {
303
310
  },
304
311
  );
305
312
 
306
- getLocationsById: Selector<L, F, Map<string, L> | undefined> = createSelector(
307
- this.getLocationsHavingFlows,
308
- (locations) => {
313
+ getLocationsById: Selector<L, F, Map<string | number, L> | undefined> =
314
+ createSelector(this.getLocationsHavingFlows, (locations) => {
309
315
  if (!locations) return undefined;
310
- const locationsById = new Map<string, L>();
316
+ const locationsById = new Map<string | number, L>();
311
317
  for (const location of locations) {
312
318
  locationsById.set(this.accessors.getLocationId(location), location);
313
319
  }
314
320
  return locationsById;
315
- },
316
- );
317
-
318
- getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
319
- this.getLocationsHavingFlows,
320
- this.getLocationsById,
321
- this.getSortedFlowsForKnownLocations,
322
- (locations, locationsById, flows) => {
323
- if (!locations || !locationsById || !flows) return undefined;
321
+ });
324
322
 
323
+ getLocationWeightGetter: Selector<L, F, LocationWeightGetter | undefined> =
324
+ createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
325
+ if (!flows) return undefined;
325
326
  const getLocationWeight = makeLocationWeightGetter(
326
327
  flows,
327
328
  this.accessors.getFlowmapDataAccessors(),
328
329
  );
330
+ return getLocationWeight;
331
+ });
332
+
333
+ getClusterLevels: Selector<L, F, ClusterLevels | undefined> = createSelector(
334
+ this.getClusterLevelsFromProps,
335
+ this.getLocationsHavingFlows,
336
+ this.getLocationWeightGetter,
337
+ (clusterLevelsFromProps, locations, getLocationWeight) => {
338
+ if (clusterLevelsFromProps) return clusterLevelsFromProps;
339
+ if (!locations || !getLocationWeight) return undefined;
329
340
  const clusterLevels = clusterLocations(
330
341
  locations,
331
342
  this.accessors.getFlowmapDataAccessors(),
@@ -334,47 +345,27 @@ export default class FlowmapSelectors<L, F> {
334
345
  maxZoom: MAX_CLUSTER_ZOOM_LEVEL,
335
346
  },
336
347
  );
337
- const clusterIndex = buildIndex<F>(clusterLevels);
338
- const {getLocationName, getLocationClusterName} =
339
- this.accessors.getFlowmapDataAccessors();
340
-
341
- // Adding meaningful names
342
- const getName = (id: string) => {
343
- const loc = locationsById.get(id);
344
- if (loc) {
345
- return getLocationName
346
- ? getLocationName(loc)
347
- : this.accessors.getLocationId(loc) || id;
348
- }
349
- return `"${id}"`;
350
- };
351
- for (const level of clusterLevels) {
352
- for (const node of level.nodes) {
353
- // Here mutating the nodes (adding names)
354
- if (isCluster(node)) {
355
- const leaves = clusterIndex.expandCluster(node);
356
-
357
- leaves.sort((a, b) =>
358
- descending(getLocationWeight(a), getLocationWeight(b)),
359
- );
348
+ return clusterLevels;
349
+ },
350
+ );
360
351
 
361
- if (getLocationClusterName) {
362
- node.name = getLocationClusterName(leaves);
363
- } else {
364
- const topId = leaves[0];
365
- const otherId = leaves.length === 2 ? leaves[1] : undefined;
366
- node.name = `"${getName(topId)}" and ${
367
- otherId
368
- ? `"${getName(otherId)}"`
369
- : `${leaves.length - 1} others`
370
- }`;
371
- }
372
- } else {
373
- (node as any).name = getName(node.id);
374
- }
375
- }
376
- }
352
+ getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
353
+ this.getLocationsById,
354
+ this.getLocationWeightGetter,
355
+ this.getClusterLevels,
356
+ (locationsById, getLocationWeight, clusterLevels) => {
357
+ if (!locationsById || !getLocationWeight || !clusterLevels)
358
+ return undefined;
377
359
 
360
+ const clusterIndex = buildIndex<F>(clusterLevels);
361
+ // Adding meaningful names
362
+ addClusterNames(
363
+ clusterIndex,
364
+ clusterLevels,
365
+ locationsById,
366
+ this.accessors.getFlowmapDataAccessors(),
367
+ getLocationWeight,
368
+ );
378
369
  return clusterIndex;
379
370
  },
380
371
  );
@@ -390,7 +381,7 @@ export default class FlowmapSelectors<L, F> {
390
381
  let maxZoom = Number.POSITIVE_INFINITY;
391
382
  let minZoom = Number.NEGATIVE_INFINITY;
392
383
 
393
- const adjust = (zoneId: string) => {
384
+ const adjust = (zoneId: string | number) => {
394
385
  const cluster = clusterIndex.getClusterById(zoneId);
395
386
  if (cluster) {
396
387
  minZoom = Math.max(minZoom, cluster.zoom);
@@ -419,7 +410,7 @@ export default class FlowmapSelectors<L, F> {
419
410
  this.getAvailableClusterZoomLevels,
420
411
  (clusterIndex, mapZoom, availableClusterZoomLevels) => {
421
412
  if (!clusterIndex) return undefined;
422
- if (!availableClusterZoomLevels) {
413
+ if (!availableClusterZoomLevels || mapZoom == null) {
423
414
  return undefined;
424
415
  }
425
416
 
@@ -432,12 +423,12 @@ export default class FlowmapSelectors<L, F> {
432
423
  );
433
424
 
434
425
  getClusterZoom = (state: FlowmapState, props: FlowmapData<L, F>) => {
435
- const {settingsState} = state;
436
- if (!settingsState.clusteringEnabled) return undefined;
437
- if (settingsState.clusteringAuto || settingsState.clusteringLevel == null) {
426
+ const {settings} = state;
427
+ if (!settings.clusteringEnabled) return undefined;
428
+ if (settings.clusteringAuto || settings.clusteringLevel == null) {
438
429
  return this._getClusterZoom(state, props);
439
430
  }
440
- return settingsState.clusteringLevel;
431
+ return settings.clusteringLevel;
441
432
  };
442
433
 
443
434
  getLocationsForSearchBox: Selector<L, F, (L | Cluster)[] | undefined> =
@@ -490,7 +481,7 @@ export default class FlowmapSelectors<L, F> {
490
481
  );
491
482
 
492
483
  getDiffMode: Selector<L, F, boolean> = createSelector(
493
- this.getFetchedFlows,
484
+ this.getFlowsFromProps,
494
485
  (flows) => {
495
486
  if (flows) {
496
487
  for (const f of flows) {
@@ -523,27 +514,28 @@ export default class FlowmapSelectors<L, F> {
523
514
  },
524
515
  );
525
516
 
526
- getUnknownLocations: Selector<L, F, Set<string> | undefined> = createSelector(
527
- this.getLocationIds,
528
- this.getFetchedFlows,
529
- this.getSortedFlowsForKnownLocations,
530
- (ids, flows, flowsForKnownLocations) => {
531
- if (!ids || !flows) return undefined;
532
- if (
533
- flowsForKnownLocations
534
- // && flows.length === flowsForKnownLocations.length
535
- )
536
- return undefined;
537
- const missing = new Set<string>();
538
- for (const flow of flows) {
539
- if (!ids.has(this.accessors.getFlowOriginId(flow)))
540
- missing.add(this.accessors.getFlowOriginId(flow));
541
- if (!ids.has(this.accessors.getFlowDestId(flow)))
542
- missing.add(this.accessors.getFlowDestId(flow));
543
- }
544
- return missing;
545
- },
546
- );
517
+ getUnknownLocations: Selector<L, F, Set<string | number> | undefined> =
518
+ createSelector(
519
+ this.getLocationIds,
520
+ this.getFlowsFromProps,
521
+ this.getSortedFlowsForKnownLocations,
522
+ (ids, flows, flowsForKnownLocations) => {
523
+ if (!ids || !flows) return undefined;
524
+ if (
525
+ flowsForKnownLocations
526
+ // && flows.length === flowsForKnownLocations.length
527
+ )
528
+ return undefined;
529
+ const missing = new Set<string | number>();
530
+ for (const flow of flows) {
531
+ if (!ids.has(this.accessors.getFlowOriginId(flow)))
532
+ missing.add(this.accessors.getFlowOriginId(flow));
533
+ if (!ids.has(this.accessors.getFlowDestId(flow)))
534
+ missing.add(this.accessors.getFlowDestId(flow));
535
+ }
536
+ return missing;
537
+ },
538
+ );
547
539
 
548
540
  getSortedAggregatedFilteredFlows: Selector<
549
541
  L,
@@ -584,31 +576,34 @@ export default class FlowmapSelectors<L, F> {
584
576
  },
585
577
  );
586
578
 
587
- getExpandedSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
588
- createSelector(
589
- this.getClusteringEnabled,
590
- this.getSelectedLocationsSet,
591
- this.getClusterIndex,
592
- (clusteringEnabled, selectedLocations, clusterIndex) => {
593
- if (!selectedLocations || !clusterIndex) {
594
- return selectedLocations;
595
- }
579
+ getExpandedSelectedLocationsSet: Selector<
580
+ L,
581
+ F,
582
+ Set<string | number> | undefined
583
+ > = createSelector(
584
+ this.getClusteringEnabled,
585
+ this.getSelectedLocationsSet,
586
+ this.getClusterIndex,
587
+ (clusteringEnabled, selectedLocations, clusterIndex) => {
588
+ if (!selectedLocations || !clusterIndex) {
589
+ return selectedLocations;
590
+ }
596
591
 
597
- const result = new Set<string>();
598
- for (const locationId of selectedLocations) {
599
- const cluster = clusterIndex.getClusterById(locationId);
600
- if (cluster) {
601
- const expanded = clusterIndex.expandCluster(cluster);
602
- for (const id of expanded) {
603
- result.add(id);
604
- }
605
- } else {
606
- result.add(locationId);
592
+ const result = new Set<string | number>();
593
+ for (const locationId of selectedLocations) {
594
+ const cluster = clusterIndex.getClusterById(locationId);
595
+ if (cluster) {
596
+ const expanded = clusterIndex.expandCluster(cluster);
597
+ for (const id of expanded) {
598
+ result.add(id);
607
599
  }
600
+ } else {
601
+ result.add(locationId);
608
602
  }
609
- return result;
610
- },
611
- );
603
+ }
604
+ return result;
605
+ },
606
+ );
612
607
 
613
608
  getTotalCountsByTime: Selector<L, F, CountByTime[] | undefined> =
614
609
  createSelector(
@@ -663,15 +658,7 @@ export default class FlowmapSelectors<L, F> {
663
658
  createSelector(
664
659
  this.getViewport,
665
660
  this.getMaxLocationCircleSize,
666
- (viewport, maxLocationCircleSize) => {
667
- const pad = maxLocationCircleSize;
668
- const bounds = new WebMercatorViewport({
669
- ...viewport,
670
- width: viewport.width + pad * 2,
671
- height: viewport.height + pad * 2,
672
- }).getBounds();
673
- return [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
674
- },
661
+ getViewportBoundingBox,
675
662
  );
676
663
 
677
664
  getLocationsForZoom: Selector<L, F, Iterable<L> | ClusterNode[] | undefined> =
@@ -689,47 +676,50 @@ export default class FlowmapSelectors<L, F> {
689
676
  },
690
677
  );
691
678
 
692
- getLocationTotals: Selector<L, F, Map<string, LocationTotals> | undefined> =
693
- createSelector(
694
- this.getLocationsForZoom,
695
- this.getSortedAggregatedFilteredFlows,
696
- this.getSelectedLocationsSet,
697
- this.getLocationFilterMode,
698
- (locations, flows, selectedLocationsSet, locationFilterMode) => {
699
- if (!flows) return undefined;
700
- const totals = new Map<string, LocationTotals>();
701
- const add = (
702
- id: string,
703
- d: Partial<LocationTotals>,
704
- ): LocationTotals => {
705
- const rv = totals.get(id) ?? {
706
- incomingCount: 0,
707
- outgoingCount: 0,
708
- internalCount: 0,
709
- };
710
- if (d.incomingCount != null) rv.incomingCount += d.incomingCount;
711
- if (d.outgoingCount != null) rv.outgoingCount += d.outgoingCount;
712
- if (d.internalCount != null) rv.internalCount += d.internalCount;
713
- return rv;
679
+ getLocationTotals: Selector<
680
+ L,
681
+ F,
682
+ Map<string | number, LocationTotals> | undefined
683
+ > = createSelector(
684
+ this.getLocationsForZoom,
685
+ this.getSortedAggregatedFilteredFlows,
686
+ this.getSelectedLocationsSet,
687
+ this.getLocationFilterMode,
688
+ (locations, flows, selectedLocationsSet, locationFilterMode) => {
689
+ if (!flows) return undefined;
690
+ const totals = new Map<string | number, LocationTotals>();
691
+ const add = (
692
+ id: string | number,
693
+ d: Partial<LocationTotals>,
694
+ ): LocationTotals => {
695
+ const rv = totals.get(id) ?? {
696
+ incomingCount: 0,
697
+ outgoingCount: 0,
698
+ internalCount: 0,
714
699
  };
715
- for (const f of flows) {
716
- if (
717
- this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
718
- ) {
719
- const originId = this.accessors.getFlowOriginId(f);
720
- const destId = this.accessors.getFlowDestId(f);
721
- const count = this.accessors.getFlowMagnitude(f);
722
- if (originId === destId) {
723
- totals.set(originId, add(originId, {internalCount: count}));
724
- } else {
725
- totals.set(originId, add(originId, {outgoingCount: count}));
726
- totals.set(destId, add(destId, {incomingCount: count}));
727
- }
700
+ if (d.incomingCount != null) rv.incomingCount += d.incomingCount;
701
+ if (d.outgoingCount != null) rv.outgoingCount += d.outgoingCount;
702
+ if (d.internalCount != null) rv.internalCount += d.internalCount;
703
+ return rv;
704
+ };
705
+ for (const f of flows) {
706
+ if (
707
+ this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
708
+ ) {
709
+ const originId = this.accessors.getFlowOriginId(f);
710
+ const destId = this.accessors.getFlowDestId(f);
711
+ const count = this.accessors.getFlowMagnitude(f);
712
+ if (originId === destId) {
713
+ totals.set(originId, add(originId, {internalCount: count}));
714
+ } else {
715
+ totals.set(originId, add(originId, {outgoingCount: count}));
716
+ totals.set(destId, add(destId, {incomingCount: count}));
728
717
  }
729
718
  }
730
- return totals;
731
- },
732
- );
719
+ }
720
+ return totals;
721
+ },
722
+ );
733
723
 
734
724
  getLocationsTree: Selector<L, F, KDBushTree> = createSelector(
735
725
  this.getLocationsForZoom,
@@ -737,14 +727,20 @@ export default class FlowmapSelectors<L, F> {
737
727
  if (!locations) {
738
728
  return undefined;
739
729
  }
740
- return new KDBush(
741
- // @ts-ignore
742
- locations,
743
- (location: L | ClusterNode) =>
744
- lngX(this.accessors.getLocationLon(location)),
745
- (location: L | ClusterNode) =>
746
- latY(this.accessors.getLocationLat(location)),
747
- );
730
+ const nodes = Array.isArray(locations)
731
+ ? locations
732
+ : Array.from(locations);
733
+ const bush = new KDBush(nodes.length, 64, Float32Array);
734
+ for (let i = 0; i < nodes.length; i++) {
735
+ const node = nodes[i];
736
+ bush.add(
737
+ lngX(this.accessors.getLocationLon(node)),
738
+ latY(this.accessors.getLocationLat(node)),
739
+ );
740
+ }
741
+ bush.finish();
742
+ bush.points = nodes;
743
+ return bush;
748
744
  },
749
745
  );
750
746
 
@@ -756,14 +752,16 @@ export default class FlowmapSelectors<L, F> {
756
752
  const ids = this._getLocationsInBboxIndices(tree, bbox);
757
753
  if (ids) {
758
754
  return new Set(
759
- ids.map((idx: number) => tree.points[idx].id) as Array<string>,
755
+ ids.map((idx: number) =>
756
+ this.accessors.getLocationId(tree.points[idx]),
757
+ ) as Array<string>,
760
758
  );
761
759
  }
762
760
  return undefined;
763
761
  },
764
762
  );
765
763
 
766
- getLocationIdsInViewport: Selector<L, F, Set<string> | undefined> =
764
+ getLocationIdsInViewport: Selector<L, F, Set<string | number> | undefined> =
767
765
  createSelectorCreator(
768
766
  defaultMemoize,
769
767
  // @ts-ignore
@@ -835,7 +833,7 @@ export default class FlowmapSelectors<L, F> {
835
833
  state: FlowmapState,
836
834
  props: FlowmapData<L, F>,
837
835
  ): [number, number] | undefined => {
838
- if (state.settingsState.adaptiveScalesEnabled) {
836
+ if (state.settings.adaptiveScalesEnabled) {
839
837
  return this._getLocationTotalsForViewportExtent(state, props);
840
838
  } else {
841
839
  return this._getLocationTotalsExtent(state, props);
@@ -930,7 +928,7 @@ export default class FlowmapSelectors<L, F> {
930
928
  state: FlowmapState,
931
929
  props: FlowmapData<L, F>,
932
930
  ): [number, number] | undefined => {
933
- if (state.settingsState.adaptiveScalesEnabled) {
931
+ if (state.settings.adaptiveScalesEnabled) {
934
932
  return this._getAdaptiveFlowMagnitudeExtent(state, props);
935
933
  } else {
936
934
  return this._getFlowMagnitudeExtent(state, props);
@@ -953,19 +951,7 @@ export default class FlowmapSelectors<L, F> {
953
951
 
954
952
  getFlowThicknessScale = createSelector(
955
953
  this.getFlowMagnitudeExtent,
956
- (magnitudeExtent) => {
957
- if (!magnitudeExtent) return undefined;
958
- return scaleLinear()
959
- .range([0.025, 0.5])
960
- .domain([
961
- 0,
962
- // should support diff mode too
963
- Math.max.apply(
964
- null,
965
- magnitudeExtent.map((x: number | undefined) => Math.abs(x || 0)),
966
- ),
967
- ]);
968
- },
954
+ getFlowThicknessScale,
969
955
  );
970
956
 
971
957
  getCircleSizeScale = createSelector(
@@ -996,7 +982,7 @@ export default class FlowmapSelectors<L, F> {
996
982
  this.getCircleSizeScale,
997
983
  this.getLocationTotals,
998
984
  (circleSizeScale, locationTotals) => {
999
- return (locationId: string) => {
985
+ return (locationId: string | number) => {
1000
986
  const total = locationTotals?.get(locationId);
1001
987
  if (total && circleSizeScale) {
1002
988
  return (
@@ -1014,7 +1000,7 @@ export default class FlowmapSelectors<L, F> {
1014
1000
  this.getCircleSizeScale,
1015
1001
  this.getLocationTotals,
1016
1002
  (circleSizeScale, locationTotals) => {
1017
- return (locationId: string) => {
1003
+ return (locationId: string | number) => {
1018
1004
  const total = locationTotals?.get(locationId);
1019
1005
  if (total && circleSizeScale) {
1020
1006
  return (
@@ -1089,6 +1075,15 @@ export default class FlowmapSelectors<L, F> {
1089
1075
  );
1090
1076
  });
1091
1077
 
1078
+ getLocationOrClusterByIdGetter = createSelector(
1079
+ this.getClusterIndex,
1080
+ this.getLocationsById,
1081
+ (clusterIndex, locationsById) => {
1082
+ return (id: string | number) =>
1083
+ clusterIndex?.getClusterById(id) ?? locationsById?.get(id);
1084
+ },
1085
+ );
1086
+
1092
1087
  getLayersData: Selector<L, F, LayersData> = createSelector(
1093
1088
  this.getLocationsForFlowmapLayer,
1094
1089
  this.getFlowsForFlowmapLayer,
@@ -1099,6 +1094,7 @@ export default class FlowmapSelectors<L, F> {
1099
1094
  this.getOutCircleSizeGetter,
1100
1095
  this.getFlowThicknessScale,
1101
1096
  this.getAnimate,
1097
+ this.getLocationLabelsEnabled,
1102
1098
  (
1103
1099
  locations,
1104
1100
  flows,
@@ -1109,6 +1105,7 @@ export default class FlowmapSelectors<L, F> {
1109
1105
  getOutCircleSize,
1110
1106
  flowThicknessScale,
1111
1107
  animationEnabled,
1108
+ locationLabelsEnabled,
1112
1109
  ) => {
1113
1110
  return this._prepareLayersData(
1114
1111
  locations,
@@ -1120,6 +1117,7 @@ export default class FlowmapSelectors<L, F> {
1120
1117
  getOutCircleSize,
1121
1118
  flowThicknessScale,
1122
1119
  animationEnabled,
1120
+ locationLabelsEnabled,
1123
1121
  );
1124
1122
  },
1125
1123
  );
@@ -1133,6 +1131,7 @@ export default class FlowmapSelectors<L, F> {
1133
1131
  const getInCircleSize = this.getInCircleSizeGetter(state, props);
1134
1132
  const getOutCircleSize = this.getOutCircleSizeGetter(state, props);
1135
1133
  const flowThicknessScale = this.getFlowThicknessScale(state, props);
1134
+ const locationLabelsEnabled = this.getLocationLabelsEnabled(state, props);
1136
1135
  return this._prepareLayersData(
1137
1136
  locations,
1138
1137
  flows,
@@ -1142,7 +1141,8 @@ export default class FlowmapSelectors<L, F> {
1142
1141
  getInCircleSize,
1143
1142
  getOutCircleSize,
1144
1143
  flowThicknessScale,
1145
- state.settingsState.animationEnabled,
1144
+ state.settings.animationEnabled,
1145
+ locationLabelsEnabled,
1146
1146
  );
1147
1147
  }
1148
1148
 
@@ -1150,12 +1150,13 @@ export default class FlowmapSelectors<L, F> {
1150
1150
  locations: (L | ClusterNode)[] | undefined,
1151
1151
  flows: (F | AggregateFlow)[] | undefined,
1152
1152
  flowmapColors: DiffColorsRGBA | ColorsRGBA,
1153
- locationsById: Map<string, L | ClusterNode> | undefined,
1154
- locationIdsInViewport: Set<string> | undefined,
1155
- getInCircleSize: (locationId: string) => number,
1156
- getOutCircleSize: (locationId: string) => number,
1153
+ locationsById: Map<string | number, L | ClusterNode> | undefined,
1154
+ locationIdsInViewport: Set<string | number> | undefined,
1155
+ getInCircleSize: (locationId: string | number) => number,
1156
+ getOutCircleSize: (locationId: string | number) => number,
1157
1157
  flowThicknessScale: ScaleLinear<number, number, never> | undefined,
1158
1158
  animationEnabled: boolean,
1159
+ locationLabelsEnabled: boolean,
1159
1160
  ): LayersData {
1160
1161
  if (!locations) locations = [];
1161
1162
  if (!flows) flows = [];
@@ -1166,6 +1167,7 @@ export default class FlowmapSelectors<L, F> {
1166
1167
  getLocationId,
1167
1168
  getLocationLon,
1168
1169
  getLocationLat,
1170
+ getLocationName,
1169
1171
  } = this.accessors;
1170
1172
 
1171
1173
  const flowMagnitudeExtent = extent(flows, (f) => getFlowMagnitude(f)) as [
@@ -1297,6 +1299,9 @@ export default class FlowmapSelectors<L, F> {
1297
1299
  : {}),
1298
1300
  },
1299
1301
  },
1302
+ ...(locationLabelsEnabled
1303
+ ? {locationLabels: locations.map(getLocationName)}
1304
+ : undefined),
1300
1305
  };
1301
1306
  }
1302
1307
 
@@ -1327,8 +1332,8 @@ export default class FlowmapSelectors<L, F> {
1327
1332
 
1328
1333
  isFlowInSelection(
1329
1334
  flow: F | AggregateFlow,
1330
- selectedLocationsSet: Set<string> | undefined,
1331
- locationFilterMode: LocationFilterMode,
1335
+ selectedLocationsSet: Set<string | number> | undefined,
1336
+ locationFilterMode?: LocationFilterMode,
1332
1337
  ) {
1333
1338
  const origin = this.accessors.getFlowOriginId(flow);
1334
1339
  const dest = this.accessors.getFlowDestId(flow);
@@ -1374,8 +1379,8 @@ export default class FlowmapSelectors<L, F> {
1374
1379
  }
1375
1380
 
1376
1381
  function calcLocationTotalsExtent(
1377
- locationTotals: Map<string, LocationTotals> | undefined,
1378
- locationIdsInViewport: Set<string> | undefined,
1382
+ locationTotals: Map<string | number, LocationTotals> | undefined,
1383
+ locationIdsInViewport: Set<string | number> | undefined,
1379
1384
  ) {
1380
1385
  if (!locationTotals) return undefined;
1381
1386
  let rv: [number, number] | undefined = undefined;
@@ -1421,10 +1426,9 @@ function aggregateFlows<F>(
1421
1426
  flowAccessors: FlowAccessors<F>,
1422
1427
  ): AggregateFlow[] {
1423
1428
  // Sum up flows with same origin, dest
1424
- const byOriginDest = nest<F, AggregateFlow>()
1425
- .key(flowAccessors.getFlowOriginId)
1426
- .key(flowAccessors.getFlowDestId)
1427
- .rollup((ff: F[]) => {
1429
+ const byOriginDest = rollup(
1430
+ flows,
1431
+ (ff: F[]) => {
1428
1432
  const origin = flowAccessors.getFlowOriginId(ff[0]);
1429
1433
  const dest = flowAccessors.getFlowDestId(ff[0]);
1430
1434
  // const color = ff[0].color;
@@ -1443,11 +1447,14 @@ function aggregateFlows<F>(
1443
1447
  };
1444
1448
  // if (color) rv.color = color;
1445
1449
  return rv;
1446
- })
1447
- .entries(flows);
1450
+ },
1451
+ flowAccessors.getFlowOriginId,
1452
+ flowAccessors.getFlowDestId,
1453
+ );
1454
+
1448
1455
  const rv: AggregateFlow[] = [];
1449
- for (const {values} of byOriginDest) {
1450
- for (const {value} of values) {
1456
+ for (const values of byOriginDest.values()) {
1457
+ for (const value of values.values()) {
1451
1458
  rv.push(value);
1452
1459
  }
1453
1460
  }