@flowmap.gl/data 8.0.0-alpha.2 → 8.0.0-alpha.22

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 (70) hide show
  1. package/dist/FlowmapAggregateAccessors.d.ts +16 -0
  2. package/dist/FlowmapAggregateAccessors.d.ts.map +1 -0
  3. package/dist/FlowmapAggregateAccessors.js +48 -0
  4. package/dist/FlowmapSelectors.d.ts +213 -0
  5. package/dist/FlowmapSelectors.d.ts.map +1 -0
  6. package/dist/FlowmapSelectors.js +861 -0
  7. package/dist/{FlowMapState.d.ts → FlowmapState.d.ts} +12 -8
  8. package/dist/FlowmapState.d.ts.map +1 -0
  9. package/dist/FlowmapState.js +2 -0
  10. package/dist/cluster/ClusterIndex.d.ts +3 -3
  11. package/dist/cluster/ClusterIndex.d.ts.map +1 -1
  12. package/dist/cluster/ClusterIndex.js +1 -1
  13. package/dist/cluster/cluster.d.ts +6 -5
  14. package/dist/cluster/cluster.d.ts.map +1 -1
  15. package/dist/cluster/cluster.js +76 -20
  16. package/dist/colors.d.ts +7 -7
  17. package/dist/colors.d.ts.map +1 -1
  18. package/dist/colors.js +55 -20
  19. package/dist/getViewStateForLocations.d.ts +18 -11
  20. package/dist/getViewStateForLocations.d.ts.map +1 -1
  21. package/dist/getViewStateForLocations.js +37 -23
  22. package/dist/index.d.ts +9 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -6
  25. package/dist/provider/FlowmapDataProvider.d.ts +21 -0
  26. package/dist/provider/FlowmapDataProvider.d.ts.map +1 -0
  27. package/dist/provider/FlowmapDataProvider.js +17 -0
  28. package/dist/provider/LocalFlowmapDataProvider.d.ts +25 -0
  29. package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -0
  30. package/dist/provider/LocalFlowmapDataProvider.js +111 -0
  31. package/dist/selector-functions.d.ts +10 -0
  32. package/dist/selector-functions.d.ts.map +1 -0
  33. package/dist/selector-functions.js +56 -0
  34. package/dist/types.d.ts +20 -16
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js +4 -4
  37. package/dist/util.d.ts +0 -1
  38. package/dist/util.d.ts.map +1 -1
  39. package/dist/util.js +1 -4
  40. package/package.json +10 -12
  41. package/src/FlowmapAggregateAccessors.ts +67 -0
  42. package/src/{FlowMapSelectors.ts → FlowmapSelectors.ts} +453 -398
  43. package/src/{FlowMapState.ts → FlowmapState.ts} +11 -7
  44. package/src/cluster/ClusterIndex.ts +19 -12
  45. package/src/cluster/cluster.ts +96 -35
  46. package/src/colors.ts +70 -28
  47. package/src/getViewStateForLocations.ts +56 -40
  48. package/src/index.ts +9 -6
  49. package/src/provider/FlowmapDataProvider.ts +75 -0
  50. package/src/provider/LocalFlowmapDataProvider.ts +143 -0
  51. package/src/selector-functions.ts +87 -0
  52. package/src/types.ts +23 -19
  53. package/src/util.ts +0 -4
  54. package/dist/FlowMapAggregateAccessors.d.ts +0 -15
  55. package/dist/FlowMapAggregateAccessors.d.ts.map +0 -1
  56. package/dist/FlowMapAggregateAccessors.js +0 -43
  57. package/dist/FlowMapSelectors.d.ts +0 -182
  58. package/dist/FlowMapSelectors.d.ts.map +0 -1
  59. package/dist/FlowMapSelectors.js +0 -834
  60. package/dist/FlowMapState.d.ts.map +0 -1
  61. package/dist/FlowMapState.js +0 -2
  62. package/dist/provider/FlowMapDataProvider.d.ts +0 -16
  63. package/dist/provider/FlowMapDataProvider.d.ts.map +0 -1
  64. package/dist/provider/FlowMapDataProvider.js +0 -17
  65. package/dist/provider/LocalFlowMapDataProvider.d.ts +0 -20
  66. package/dist/provider/LocalFlowMapDataProvider.d.ts.map +0 -1
  67. package/dist/provider/LocalFlowMapDataProvider.js +0 -87
  68. package/src/FlowMapAggregateAccessors.ts +0 -60
  69. package/src/provider/FlowMapDataProvider.ts +0 -63
  70. package/src/provider/LocalFlowMapDataProvider.ts +0 -105
@@ -16,10 +16,8 @@
16
16
  *
17
17
  */
18
18
 
19
- import {bounds} from '@mapbox/geo-viewport';
20
- import {ascending, descending, extent, min} from 'd3-array';
21
- import {nest} from 'd3-collection';
22
- import {ScaleLinear, scaleLinear, scaleSqrt} from 'd3-scale';
19
+ import {ascending, descending, extent, min, rollup} from 'd3-array';
20
+ import {ScaleLinear, scaleSqrt} from 'd3-scale';
23
21
  import KDBush from 'kdbush';
24
22
  import {
25
23
  createSelector,
@@ -33,6 +31,7 @@ import {
33
31
  buildIndex,
34
32
  ClusterIndex,
35
33
  findAppropriateZoomLevel,
34
+ LocationWeightGetter,
36
35
  makeLocationWeightGetter,
37
36
  } from './cluster/ClusterIndex';
38
37
  import getColors, {
@@ -44,8 +43,13 @@ import getColors, {
44
43
  isDiffColors,
45
44
  isDiffColorsRGBA,
46
45
  } from './colors';
47
- import FlowMapAggregateAccessors from './FlowMapAggregateAccessors';
48
- import {FlowMapState} from './FlowMapState';
46
+ import FlowmapAggregateAccessors from './FlowmapAggregateAccessors';
47
+ import {FlowmapState} from './FlowmapState';
48
+ import {
49
+ addClusterNames,
50
+ getFlowThicknessScale,
51
+ getViewportBoundingBox,
52
+ } from './selector-functions';
49
53
  import {
50
54
  getTimeGranularityByKey,
51
55
  getTimeGranularityByOrder,
@@ -55,126 +59,143 @@ import {
55
59
  import {
56
60
  AggregateFlow,
57
61
  Cluster,
62
+ ClusterLevels,
58
63
  ClusterNode,
59
64
  CountByTime,
60
65
  FlowAccessors,
61
66
  FlowCirclesLayerAttributes,
62
67
  FlowLinesLayerAttributes,
63
- FlowMapData,
64
- FlowMapDataAccessors,
68
+ FlowmapData,
69
+ FlowmapDataAccessors,
65
70
  isCluster,
66
71
  isLocationClusterNode,
67
72
  LayersData,
68
73
  LocationFilterMode,
69
74
  LocationTotals,
70
75
  } from './types';
71
- import {flatMap} from './util';
72
76
 
73
77
  const MAX_CLUSTER_ZOOM_LEVEL = 20;
74
- const NUMBER_OF_FLOWS_TO_DISPLAY = 5000;
75
78
  type KDBushTree = any;
76
79
 
77
80
  export type Selector<L, F, T> = ParametricSelector<
78
- FlowMapState,
79
- FlowMapData<L, F>,
81
+ FlowmapState,
82
+ FlowmapData<L, F>,
80
83
  T
81
84
  >;
82
85
 
83
- export default class FlowMapSelectors<L, F> {
84
- accessors: FlowMapAggregateAccessors<L, F>;
86
+ export default class FlowmapSelectors<L, F> {
87
+ accessors: FlowmapAggregateAccessors<L, F>;
85
88
 
86
- constructor(accessors: FlowMapDataAccessors<L, F>) {
87
- this.accessors = new FlowMapAggregateAccessors(accessors);
89
+ constructor(accessors: FlowmapDataAccessors<L, F>) {
90
+ this.accessors = new FlowmapAggregateAccessors(accessors);
88
91
  this.setAccessors(accessors);
89
92
  }
90
93
 
91
- setAccessors(accessors: FlowMapDataAccessors<L, F>) {
92
- this.accessors = new FlowMapAggregateAccessors(accessors);
94
+ setAccessors(accessors: FlowmapDataAccessors<L, F>) {
95
+ this.accessors = new FlowmapAggregateAccessors(accessors);
93
96
  }
94
97
 
95
- getFetchedFlows = (state: FlowMapState, props: FlowMapData<L, F>) =>
98
+ getFlowsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
96
99
  props.flows;
97
- getFetchedLocations = (state: FlowMapState, props: FlowMapData<L, F>) =>
100
+ getLocationsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
98
101
  props.locations;
99
- getSelectedLocations = (state: FlowMapState, props: FlowMapData<L, F>) =>
100
- state.filterState.selectedLocations;
101
- getLocationFilterMode = (state: FlowMapState, props: FlowMapData<L, F>) =>
102
- state.filterState.locationFilterMode;
103
- getClusteringEnabled = (state: FlowMapState, props: FlowMapData<L, F>) =>
104
- state.settingsState.clusteringEnabled;
105
- getLocationTotalsEnabled = (state: FlowMapState, props: FlowMapData<L, F>) =>
106
- state.settingsState.locationTotalsEnabled;
107
- getZoom = (state: FlowMapState, props: FlowMapData<L, F>) =>
102
+ getClusterLevelsFromProps = (
103
+ state: FlowmapState,
104
+ props: FlowmapData<L, F>,
105
+ ) => {
106
+ return props.clusterLevels;
107
+ };
108
+ getMaxTopFlowsDisplayNum = (state: FlowmapState, props: FlowmapData<L, F>) =>
109
+ state.settings.maxTopFlowsDisplayNum;
110
+ getSelectedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
111
+ state.filter?.selectedLocations;
112
+ getLocationFilterMode = (state: FlowmapState, props: FlowmapData<L, F>) =>
113
+ state.filter?.locationFilterMode;
114
+ getClusteringEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
115
+ state.settings.clusteringEnabled;
116
+ getLocationTotalsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
117
+ state.settings.locationTotalsEnabled;
118
+ getLocationLabelsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
119
+ state.settings.locationLabelsEnabled;
120
+ getZoom = (state: FlowmapState, props: FlowmapData<L, F>) =>
108
121
  state.viewport.zoom;
109
- getViewport = (state: FlowMapState, props: FlowMapData<L, F>) =>
122
+ getViewport = (state: FlowmapState, props: FlowmapData<L, F>) =>
110
123
  state.viewport;
111
- getSelectedTimeRange = (state: FlowMapState, props: FlowMapData<L, F>) =>
112
- state.filterState.selectedTimeRange;
124
+ getSelectedTimeRange = (state: FlowmapState, props: FlowmapData<L, F>) =>
125
+ state.filter?.selectedTimeRange;
113
126
 
114
- getColorSchemeKey: Selector<L, F, string | undefined> = (
115
- state: FlowMapState,
116
- props: FlowMapData<L, F>,
117
- ) => state.settingsState.colorScheme;
127
+ getColorScheme: Selector<L, F, string | string[] | undefined> = (
128
+ state: FlowmapState,
129
+ props: FlowmapData<L, F>,
130
+ ) => state.settings.colorScheme;
118
131
 
119
132
  getDarkMode: Selector<L, F, boolean> = (
120
- state: FlowMapState,
121
- props: FlowMapData<L, F>,
122
- ) => state.settingsState.darkMode;
133
+ state: FlowmapState,
134
+ props: FlowmapData<L, F>,
135
+ ) => state.settings.darkMode;
123
136
 
124
137
  getFadeEnabled: Selector<L, F, boolean> = (
125
- state: FlowMapState,
126
- props: FlowMapData<L, F>,
127
- ) => state.settingsState.fadeEnabled;
138
+ state: FlowmapState,
139
+ props: FlowmapData<L, F>,
140
+ ) => state.settings.fadeEnabled;
141
+
142
+ getFadeOpacityEnabled: Selector<L, F, boolean> = (
143
+ state: FlowmapState,
144
+ props: FlowmapData<L, F>,
145
+ ) => state.settings.fadeOpacityEnabled;
128
146
 
129
147
  getFadeAmount: Selector<L, F, number> = (
130
- state: FlowMapState,
131
- props: FlowMapData<L, F>,
132
- ) => state.settingsState.fadeAmount;
148
+ state: FlowmapState,
149
+ props: FlowmapData<L, F>,
150
+ ) => state.settings.fadeAmount;
133
151
 
134
152
  getAnimate: Selector<L, F, boolean> = (
135
- state: FlowMapState,
136
- props: FlowMapData<L, F>,
137
- ) => state.settingsState.animationEnabled;
153
+ state: FlowmapState,
154
+ props: FlowmapData<L, F>,
155
+ ) => state.settings.animationEnabled;
138
156
 
139
- getInvalidLocationIds: Selector<L, F, string[] | undefined> = createSelector(
140
- this.getFetchedLocations,
141
- (locations) => {
157
+ getInvalidLocationIds: Selector<L, F, (string | number)[] | undefined> =
158
+ createSelector(this.getLocationsFromProps, (locations) => {
142
159
  if (!locations) return undefined;
143
160
  const invalid = [];
144
161
  for (const location of locations) {
145
162
  const id = this.accessors.getLocationId(location);
146
- const [lon, lat] = this.accessors.getLocationCentroid(location) || [
147
- NaN,
148
- NaN,
149
- ];
163
+ const lon = this.accessors.getLocationLon(location);
164
+ const lat = this.accessors.getLocationLat(location);
150
165
  if (!(-90 <= lat && lat <= 90) || !(-180 <= lon && lon <= 180)) {
151
166
  invalid.push(id);
152
167
  }
153
168
  }
154
169
  return invalid.length > 0 ? invalid : undefined;
155
- },
156
- );
170
+ });
157
171
 
158
- getLocations: Selector<L, F, L[] | undefined> = createSelector(
159
- this.getFetchedLocations,
172
+ getLocations: Selector<L, F, Iterable<L> | undefined> = createSelector(
173
+ this.getLocationsFromProps,
160
174
  this.getInvalidLocationIds,
161
175
  (locations, invalidIds) => {
162
176
  if (!locations) return undefined;
163
177
  if (!invalidIds || invalidIds.length === 0) return locations;
164
178
  const invalid = new Set(invalidIds);
165
- return locations.filter(
166
- (location: L) => !invalid.has(this.accessors.getLocationId(location)),
167
- );
179
+ const filtered: L[] = [];
180
+ for (const location of locations) {
181
+ const id = this.accessors.getLocationId(location);
182
+ if (!invalid.has(id)) {
183
+ filtered.push(location);
184
+ }
185
+ }
186
+ return filtered;
168
187
  },
169
188
  );
170
189
 
171
- getLocationIds: Selector<L, F, Set<string> | undefined> = createSelector(
172
- this.getLocations,
173
- (locations) =>
174
- locations
175
- ? new Set(locations.map(this.accessors.getLocationId))
176
- : undefined,
177
- );
190
+ getLocationIds: Selector<L, F, Set<string | number> | undefined> =
191
+ createSelector(this.getLocations, (locations) => {
192
+ if (!locations) return undefined;
193
+ const ids = new Set<string | number>();
194
+ for (const id of locations) {
195
+ ids.add(this.accessors.getLocationId(id));
196
+ }
197
+ return ids;
198
+ });
178
199
 
179
200
  getSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
180
201
  createSelector(this.getSelectedLocations, (ids) =>
@@ -182,21 +203,27 @@ export default class FlowMapSelectors<L, F> {
182
203
  );
183
204
 
184
205
  getSortedFlowsForKnownLocations: Selector<L, F, F[] | undefined> =
185
- createSelector(this.getFetchedFlows, this.getLocationIds, (flows, ids) => {
186
- if (!ids || !flows) return undefined;
187
- return flows
188
- .filter(
189
- (flow: F) =>
190
- ids.has(this.accessors.getFlowOriginId(flow)) &&
191
- ids.has(this.accessors.getFlowDestId(flow)),
192
- )
193
- .sort((a: F, b: F) =>
206
+ createSelector(
207
+ this.getFlowsFromProps,
208
+ this.getLocationIds,
209
+ (flows, ids) => {
210
+ if (!ids || !flows) return undefined;
211
+ const filtered = [];
212
+ for (const flow of flows) {
213
+ const srcId = this.accessors.getFlowOriginId(flow);
214
+ const dstId = this.accessors.getFlowDestId(flow);
215
+ if (ids.has(srcId) && ids.has(dstId)) {
216
+ filtered.push(flow);
217
+ }
218
+ }
219
+ return filtered.sort((a: F, b: F) =>
194
220
  descending(
195
221
  Math.abs(this.accessors.getFlowMagnitude(a)),
196
222
  Math.abs(this.accessors.getFlowMagnitude(b)),
197
223
  ),
198
224
  );
199
- });
225
+ },
226
+ );
200
227
 
201
228
  getActualTimeExtent: Selector<L, F, [Date, Date] | undefined> =
202
229
  createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
@@ -268,93 +295,83 @@ export default class FlowMapSelectors<L, F> {
268
295
  },
269
296
  );
270
297
 
271
- getLocationsHavingFlows: Selector<L, F, L[] | undefined> = createSelector(
272
- this.getSortedFlowsForKnownLocations,
273
- this.getLocations,
274
- (flows, locations) => {
275
- if (!locations || !flows) return locations;
276
- const withFlows = new Set();
277
- for (const flow of flows) {
278
- withFlows.add(this.accessors.getFlowOriginId(flow));
279
- withFlows.add(this.accessors.getFlowDestId(flow));
280
- }
281
- return locations.filter((location: L) =>
282
- withFlows.has(this.accessors.getLocationId(location)),
283
- );
284
- },
285
- );
298
+ getLocationsHavingFlows: Selector<L, F, Iterable<L> | undefined> =
299
+ createSelector(
300
+ this.getSortedFlowsForKnownLocations,
301
+ this.getLocations,
302
+ (flows, locations) => {
303
+ if (!locations || !flows) return locations;
304
+ const withFlows = new Set();
305
+ for (const flow of flows) {
306
+ withFlows.add(this.accessors.getFlowOriginId(flow));
307
+ withFlows.add(this.accessors.getFlowDestId(flow));
308
+ }
309
+ const filtered = [];
310
+ for (const location of locations) {
311
+ if (withFlows.has(this.accessors.getLocationId(location))) {
312
+ filtered.push(location);
313
+ }
314
+ }
315
+ return filtered;
316
+ },
317
+ );
286
318
 
287
- getLocationsById: Selector<L, F, Map<string, L> | undefined> = createSelector(
288
- this.getLocationsHavingFlows,
289
- (locations) => {
319
+ getLocationsById: Selector<L, F, Map<string | number, L> | undefined> =
320
+ createSelector(this.getLocationsHavingFlows, (locations) => {
290
321
  if (!locations) return undefined;
291
- return nest<L, L>()
292
- .key((d: L) => this.accessors.getLocationId(d))
293
- .rollup(([d]) => d)
294
- .map(locations) as any as Map<string, L>;
295
- },
296
- );
297
-
298
- getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
299
- this.getLocationsHavingFlows,
300
- this.getLocationsById,
301
- this.getSortedFlowsForKnownLocations,
302
- (locations, locationsById, flows) => {
303
- if (!locations || !locationsById || !flows) return undefined;
322
+ const locationsById = new Map<string | number, L>();
323
+ for (const location of locations) {
324
+ locationsById.set(this.accessors.getLocationId(location), location);
325
+ }
326
+ return locationsById;
327
+ });
304
328
 
329
+ getLocationWeightGetter: Selector<L, F, LocationWeightGetter | undefined> =
330
+ createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
331
+ if (!flows) return undefined;
305
332
  const getLocationWeight = makeLocationWeightGetter(
306
333
  flows,
307
- this.accessors.getFlowMapDataAccessors(),
334
+ this.accessors.getFlowmapDataAccessors(),
308
335
  );
336
+ return getLocationWeight;
337
+ });
338
+
339
+ getClusterLevels: Selector<L, F, ClusterLevels | undefined> = createSelector(
340
+ this.getClusterLevelsFromProps,
341
+ this.getLocationsHavingFlows,
342
+ this.getLocationWeightGetter,
343
+ (clusterLevelsFromProps, locations, getLocationWeight) => {
344
+ if (clusterLevelsFromProps) return clusterLevelsFromProps;
345
+ if (!locations || !getLocationWeight) return undefined;
309
346
  const clusterLevels = clusterLocations(
310
347
  locations,
311
- this.accessors.getFlowMapDataAccessors(),
348
+ this.accessors.getFlowmapDataAccessors(),
312
349
  getLocationWeight,
313
350
  {
314
351
  maxZoom: MAX_CLUSTER_ZOOM_LEVEL,
315
352
  },
316
353
  );
317
- const clusterIndex = buildIndex<F>(clusterLevels);
318
- const {getLocationName, getLocationClusterName} =
319
- this.accessors.getFlowMapDataAccessors();
320
-
321
- // Adding meaningful names
322
- const getName = (id: string) => {
323
- const loc = locationsById.get(id);
324
- if (loc) {
325
- return getLocationName
326
- ? getLocationName(loc)
327
- : this.accessors.getLocationId(loc) || id;
328
- }
329
- return `"${id}"`;
330
- };
331
- for (const level of clusterLevels) {
332
- for (const node of level.nodes) {
333
- // Here mutating the nodes (adding names)
334
- if (isCluster(node)) {
335
- const leaves = clusterIndex.expandCluster(node);
336
-
337
- leaves.sort((a, b) =>
338
- descending(getLocationWeight(a), getLocationWeight(b)),
339
- );
354
+ return clusterLevels;
355
+ },
356
+ );
340
357
 
341
- if (getLocationClusterName) {
342
- node.name = getLocationClusterName(leaves);
343
- } else {
344
- const topId = leaves[0];
345
- const otherId = leaves.length === 2 ? leaves[1] : undefined;
346
- node.name = `"${getName(topId)}" and ${
347
- otherId
348
- ? `"${getName(otherId)}"`
349
- : `${leaves.length - 1} others`
350
- }`;
351
- }
352
- } else {
353
- (node as any).name = getName(node.id);
354
- }
355
- }
356
- }
358
+ getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
359
+ this.getLocationsById,
360
+ this.getLocationWeightGetter,
361
+ this.getClusterLevels,
362
+ (locationsById, getLocationWeight, clusterLevels) => {
363
+ if (!locationsById || !getLocationWeight || !clusterLevels)
364
+ return undefined;
357
365
 
366
+ const clusterIndex = buildIndex<F>(clusterLevels);
367
+ // Adding meaningful names
368
+ addClusterNames(
369
+ clusterIndex,
370
+ clusterLevels,
371
+ locationsById,
372
+ this.accessors.getFlowmapDataAccessors(),
373
+ getLocationWeight,
374
+ );
358
375
  return clusterIndex;
359
376
  },
360
377
  );
@@ -399,7 +416,7 @@ export default class FlowMapSelectors<L, F> {
399
416
  this.getAvailableClusterZoomLevels,
400
417
  (clusterIndex, mapZoom, availableClusterZoomLevels) => {
401
418
  if (!clusterIndex) return undefined;
402
- if (!availableClusterZoomLevels) {
419
+ if (!availableClusterZoomLevels || mapZoom == null) {
403
420
  return undefined;
404
421
  }
405
422
 
@@ -411,13 +428,13 @@ export default class FlowMapSelectors<L, F> {
411
428
  },
412
429
  );
413
430
 
414
- getClusterZoom = (state: FlowMapState, props: FlowMapData<L, F>) => {
415
- const {settingsState} = state;
416
- if (!settingsState.clusteringEnabled) return undefined;
417
- if (settingsState.clusteringAuto || settingsState.clusteringLevel == null) {
431
+ getClusterZoom = (state: FlowmapState, props: FlowmapData<L, F>) => {
432
+ const {settings} = state;
433
+ if (!settings.clusteringEnabled) return undefined;
434
+ if (settings.clusteringAuto || settings.clusteringLevel == null) {
418
435
  return this._getClusterZoom(state, props);
419
436
  }
420
- return settingsState.clusteringLevel;
437
+ return settings.clusteringLevel;
421
438
  };
422
439
 
423
440
  getLocationsForSearchBox: Selector<L, F, (L | Cluster)[] | undefined> =
@@ -435,7 +452,7 @@ export default class FlowMapSelectors<L, F> {
435
452
  clusterIndex,
436
453
  ) => {
437
454
  if (!locations) return undefined;
438
- let result: (L | Cluster)[] = locations;
455
+ let result: (L | Cluster)[] = Array.from(locations);
439
456
  // if (clusteringEnabled) {
440
457
  // if (clusterIndex) {
441
458
  // const zoomItems = clusterIndex.getClusterNodesFor(clusterZoom);
@@ -445,7 +462,7 @@ export default class FlowMapSelectors<L, F> {
445
462
  // }
446
463
  // }
447
464
 
448
- if (result && clusterIndex && selectedLocations) {
465
+ if (clusterIndex && selectedLocations) {
449
466
  const toAppend = [];
450
467
  for (const id of selectedLocations) {
451
468
  const cluster = clusterIndex.getClusterById(id);
@@ -470,58 +487,61 @@ export default class FlowMapSelectors<L, F> {
470
487
  );
471
488
 
472
489
  getDiffMode: Selector<L, F, boolean> = createSelector(
473
- this.getFetchedFlows,
490
+ this.getFlowsFromProps,
474
491
  (flows) => {
475
- if (
476
- flows &&
477
- flows.find((f: F) => this.accessors.getFlowMagnitude(f) < 0)
478
- ) {
479
- return true;
492
+ if (flows) {
493
+ for (const f of flows) {
494
+ if (this.accessors.getFlowMagnitude(f) < 0) {
495
+ return true;
496
+ }
497
+ }
480
498
  }
481
499
  return false;
482
500
  },
483
501
  );
484
502
 
485
- _getFlowMapColors = createSelector(
503
+ _getFlowmapColors = createSelector(
486
504
  this.getDiffMode,
487
- this.getColorSchemeKey,
505
+ this.getColorScheme,
488
506
  this.getDarkMode,
489
507
  this.getFadeEnabled,
508
+ this.getFadeOpacityEnabled,
490
509
  this.getFadeAmount,
491
510
  this.getAnimate,
492
511
  getColors,
493
512
  );
494
513
 
495
- getFlowMapColorsRGBA = createSelector(
496
- this._getFlowMapColors,
497
- (flowMapColors) => {
498
- return isDiffColors(flowMapColors)
499
- ? getDiffColorsRGBA(flowMapColors)
500
- : getColorsRGBA(flowMapColors);
514
+ getFlowmapColorsRGBA = createSelector(
515
+ this._getFlowmapColors,
516
+ (flowmapColors) => {
517
+ return isDiffColors(flowmapColors)
518
+ ? getDiffColorsRGBA(flowmapColors)
519
+ : getColorsRGBA(flowmapColors);
501
520
  },
502
521
  );
503
522
 
504
- getUnknownLocations: Selector<L, F, Set<string> | undefined> = createSelector(
505
- this.getLocationIds,
506
- this.getFetchedFlows,
507
- this.getSortedFlowsForKnownLocations,
508
- (ids, flows, flowsForKnownLocations) => {
509
- if (!ids || !flows) return undefined;
510
- if (
511
- flowsForKnownLocations &&
512
- flows.length === flowsForKnownLocations.length
513
- )
514
- return undefined;
515
- const missing = new Set<string>();
516
- for (const flow of flows) {
517
- if (!ids.has(this.accessors.getFlowOriginId(flow)))
518
- missing.add(this.accessors.getFlowOriginId(flow));
519
- if (!ids.has(this.accessors.getFlowDestId(flow)))
520
- missing.add(this.accessors.getFlowDestId(flow));
521
- }
522
- return missing;
523
- },
524
- );
523
+ getUnknownLocations: Selector<L, F, Set<string | number> | undefined> =
524
+ createSelector(
525
+ this.getLocationIds,
526
+ this.getFlowsFromProps,
527
+ this.getSortedFlowsForKnownLocations,
528
+ (ids, flows, flowsForKnownLocations) => {
529
+ if (!ids || !flows) return undefined;
530
+ if (
531
+ flowsForKnownLocations
532
+ // && flows.length === flowsForKnownLocations.length
533
+ )
534
+ return undefined;
535
+ const missing = new Set<string | number>();
536
+ for (const flow of flows) {
537
+ if (!ids.has(this.accessors.getFlowOriginId(flow)))
538
+ missing.add(this.accessors.getFlowOriginId(flow));
539
+ if (!ids.has(this.accessors.getFlowDestId(flow)))
540
+ missing.add(this.accessors.getFlowDestId(flow));
541
+ }
542
+ return missing;
543
+ },
544
+ );
525
545
 
526
546
  getSortedAggregatedFilteredFlows: Selector<
527
547
  L,
@@ -544,12 +564,12 @@ export default class FlowMapSelectors<L, F> {
544
564
  // : flows,
545
565
  flows,
546
566
  clusterZoom,
547
- this.accessors.getFlowMapDataAccessors(),
567
+ this.accessors.getFlowmapDataAccessors(),
548
568
  );
549
569
  } else {
550
570
  aggregated = aggregateFlows(
551
571
  flows,
552
- this.accessors.getFlowMapDataAccessors(),
572
+ this.accessors.getFlowmapDataAccessors(),
553
573
  );
554
574
  }
555
575
  aggregated.sort((a, b) =>
@@ -562,33 +582,6 @@ export default class FlowMapSelectors<L, F> {
562
582
  },
563
583
  );
564
584
 
565
- getFlowMagnitudeExtent: Selector<L, F, [number, number] | undefined> =
566
- createSelector(
567
- this.getSortedAggregatedFilteredFlows,
568
- this.getSelectedLocationsSet,
569
- this.getLocationFilterMode,
570
- (flows, selectedLocationsSet, locationFilterMode) => {
571
- if (!flows) return undefined;
572
- let rv: [number, number] | undefined = undefined;
573
- for (const f of flows) {
574
- if (
575
- this.accessors.getFlowOriginId(f) !==
576
- this.accessors.getFlowDestId(f) &&
577
- this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
578
- ) {
579
- const count = this.accessors.getFlowMagnitude(f);
580
- if (rv == null) {
581
- rv = [count, count];
582
- } else {
583
- if (count < rv[0]) rv[0] = count;
584
- if (count > rv[1]) rv[1] = count;
585
- }
586
- }
587
- }
588
- return rv;
589
- },
590
- );
591
-
592
585
  getExpandedSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
593
586
  createSelector(
594
587
  this.getClusteringEnabled,
@@ -668,18 +661,10 @@ export default class FlowMapSelectors<L, F> {
668
661
  createSelector(
669
662
  this.getViewport,
670
663
  this.getMaxLocationCircleSize,
671
- (viewport, maxLocationCircleSize) => {
672
- const pad = maxLocationCircleSize;
673
- return bounds(
674
- [viewport.longitude, viewport.latitude],
675
- viewport.zoom,
676
- [viewport.width + pad * 2, viewport.height + pad * 2],
677
- 512,
678
- );
679
- },
664
+ getViewportBoundingBox,
680
665
  );
681
666
 
682
- getLocationsForZoom: Selector<L, F, L[] | ClusterNode[] | undefined> =
667
+ getLocationsForZoom: Selector<L, F, Iterable<L> | ClusterNode[] | undefined> =
683
668
  createSelector(
684
669
  this.getClusteringEnabled,
685
670
  this.getLocationsHavingFlows,
@@ -694,47 +679,50 @@ export default class FlowMapSelectors<L, F> {
694
679
  },
695
680
  );
696
681
 
697
- getLocationTotals: Selector<L, F, Map<string, LocationTotals> | undefined> =
698
- createSelector(
699
- this.getLocationsForZoom,
700
- this.getSortedAggregatedFilteredFlows,
701
- this.getSelectedLocationsSet,
702
- this.getLocationFilterMode,
703
- (locations, flows, selectedLocationsSet, locationFilterMode) => {
704
- if (!flows) return undefined;
705
- const totals = new Map<string, LocationTotals>();
706
- const add = (
707
- id: string,
708
- d: Partial<LocationTotals>,
709
- ): LocationTotals => {
710
- const rv = totals.get(id) ?? {
711
- incomingCount: 0,
712
- outgoingCount: 0,
713
- internalCount: 0,
714
- };
715
- if (d.incomingCount != null) rv.incomingCount += d.incomingCount;
716
- if (d.outgoingCount != null) rv.outgoingCount += d.outgoingCount;
717
- if (d.internalCount != null) rv.internalCount += d.internalCount;
718
- return rv;
682
+ getLocationTotals: Selector<
683
+ L,
684
+ F,
685
+ Map<string | number, LocationTotals> | undefined
686
+ > = createSelector(
687
+ this.getLocationsForZoom,
688
+ this.getSortedAggregatedFilteredFlows,
689
+ this.getSelectedLocationsSet,
690
+ this.getLocationFilterMode,
691
+ (locations, flows, selectedLocationsSet, locationFilterMode) => {
692
+ if (!flows) return undefined;
693
+ const totals = new Map<string | number, LocationTotals>();
694
+ const add = (
695
+ id: string | number,
696
+ d: Partial<LocationTotals>,
697
+ ): LocationTotals => {
698
+ const rv = totals.get(id) ?? {
699
+ incomingCount: 0,
700
+ outgoingCount: 0,
701
+ internalCount: 0,
719
702
  };
720
- for (const f of flows) {
721
- if (
722
- this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
723
- ) {
724
- const originId = this.accessors.getFlowOriginId(f);
725
- const destId = this.accessors.getFlowDestId(f);
726
- const count = this.accessors.getFlowMagnitude(f);
727
- if (originId === destId) {
728
- totals.set(originId, add(originId, {internalCount: count}));
729
- } else {
730
- totals.set(originId, add(originId, {outgoingCount: count}));
731
- totals.set(destId, add(destId, {incomingCount: count}));
732
- }
703
+ if (d.incomingCount != null) rv.incomingCount += d.incomingCount;
704
+ if (d.outgoingCount != null) rv.outgoingCount += d.outgoingCount;
705
+ if (d.internalCount != null) rv.internalCount += d.internalCount;
706
+ return rv;
707
+ };
708
+ for (const f of flows) {
709
+ if (
710
+ this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
711
+ ) {
712
+ const originId = this.accessors.getFlowOriginId(f);
713
+ const destId = this.accessors.getFlowDestId(f);
714
+ const count = this.accessors.getFlowMagnitude(f);
715
+ if (originId === destId) {
716
+ totals.set(originId, add(originId, {internalCount: count}));
717
+ } else {
718
+ totals.set(originId, add(originId, {outgoingCount: count}));
719
+ totals.set(destId, add(destId, {incomingCount: count}));
733
720
  }
734
721
  }
735
- return totals;
736
- },
737
- );
722
+ }
723
+ return totals;
724
+ },
725
+ );
738
726
 
739
727
  getLocationsTree: Selector<L, F, KDBushTree> = createSelector(
740
728
  this.getLocationsForZoom,
@@ -746,17 +734,9 @@ export default class FlowMapSelectors<L, F> {
746
734
  // @ts-ignore
747
735
  locations,
748
736
  (location: L | ClusterNode) =>
749
- lngX(
750
- isLocationClusterNode(location)
751
- ? location.centroid[0]
752
- : this.accessors.getLocationCentroid(location)[0],
753
- ),
737
+ lngX(this.accessors.getLocationLon(location)),
754
738
  (location: L | ClusterNode) =>
755
- latY(
756
- isLocationClusterNode(location)
757
- ? location.centroid[1]
758
- : this.accessors.getLocationCentroid(location)[1],
759
- ),
739
+ latY(this.accessors.getLocationLat(location)),
760
740
  );
761
741
  },
762
742
  );
@@ -776,7 +756,7 @@ export default class FlowMapSelectors<L, F> {
776
756
  },
777
757
  );
778
758
 
779
- getLocationIdsInViewport: Selector<L, F, Set<string> | undefined> =
759
+ getLocationIdsInViewport: Selector<L, F, Set<string | number> | undefined> =
780
760
  createSelectorCreator(
781
761
  defaultMemoize,
782
762
  // @ts-ignore
@@ -845,27 +825,29 @@ export default class FlowMapSelectors<L, F> {
845
825
  );
846
826
 
847
827
  getLocationTotalsExtent = (
848
- state: FlowMapState,
849
- props: FlowMapData<L, F>,
828
+ state: FlowmapState,
829
+ props: FlowmapData<L, F>,
850
830
  ): [number, number] | undefined => {
851
- if (state.settingsState.adaptiveScalesEnabled) {
831
+ if (state.settings.adaptiveScalesEnabled) {
852
832
  return this._getLocationTotalsForViewportExtent(state, props);
853
833
  } else {
854
834
  return this._getLocationTotalsExtent(state, props);
855
835
  }
856
836
  };
857
837
 
858
- getFlowsForFlowMapLayer: Selector<L, F, (F | AggregateFlow)[] | undefined> =
838
+ getFlowsForFlowmapLayer: Selector<L, F, (F | AggregateFlow)[] | undefined> =
859
839
  createSelector(
860
840
  this.getSortedAggregatedFilteredFlows,
861
841
  this.getLocationIdsInViewport,
862
842
  this.getSelectedLocationsSet,
863
843
  this.getLocationFilterMode,
844
+ this.getMaxTopFlowsDisplayNum,
864
845
  (
865
846
  flows,
866
847
  locationIdsInViewport,
867
848
  selectedLocationsSet,
868
849
  locationFilterMode,
850
+ maxTopFlowsDisplayNum,
869
851
  ) => {
870
852
  if (!flows || !locationIdsInViewport) return undefined;
871
853
  const picked: (F | AggregateFlow)[] = [];
@@ -892,7 +874,7 @@ export default class FlowMapSelectors<L, F> {
892
874
  }
893
875
  }
894
876
  // Only keep top
895
- if (pickedCount > NUMBER_OF_FLOWS_TO_DISPLAY) break;
877
+ if (pickedCount > maxTopFlowsDisplayNum) break;
896
878
  }
897
879
  // assuming they are sorted in descending order,
898
880
  // we need ascending for rendering
@@ -900,20 +882,51 @@ export default class FlowMapSelectors<L, F> {
900
882
  },
901
883
  );
902
884
 
903
- _getFlowMagnitudeExtent = (
904
- state: FlowMapState,
905
- props: FlowMapData<L, F>,
885
+ _getFlowMagnitudeExtent: Selector<L, F, [number, number] | undefined> =
886
+ createSelector(
887
+ this.getSortedAggregatedFilteredFlows,
888
+ this.getSelectedLocationsSet,
889
+ this.getLocationFilterMode,
890
+ (flows, selectedLocationsSet, locationFilterMode) => {
891
+ if (!flows) return undefined;
892
+ let rv: [number, number] | undefined = undefined;
893
+ for (const f of flows) {
894
+ if (
895
+ this.accessors.getFlowOriginId(f) !==
896
+ this.accessors.getFlowDestId(f) &&
897
+ this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
898
+ ) {
899
+ const count = this.accessors.getFlowMagnitude(f);
900
+ if (rv == null) {
901
+ rv = [count, count];
902
+ } else {
903
+ if (count < rv[0]) rv[0] = count;
904
+ if (count > rv[1]) rv[1] = count;
905
+ }
906
+ }
907
+ }
908
+ return rv;
909
+ },
910
+ );
911
+
912
+ _getAdaptiveFlowMagnitudeExtent: Selector<
913
+ L,
914
+ F,
915
+ [number, number] | undefined
916
+ > = createSelector(this.getFlowsForFlowmapLayer, (flows) => {
917
+ if (!flows) return undefined;
918
+ const rv = extent(flows, this.accessors.getFlowMagnitude);
919
+ return rv[0] !== undefined && rv[1] !== undefined ? rv : undefined;
920
+ });
921
+
922
+ getFlowMagnitudeExtent = (
923
+ state: FlowmapState,
924
+ props: FlowmapData<L, F>,
906
925
  ): [number, number] | undefined => {
907
- if (state.settingsState.adaptiveScalesEnabled) {
908
- const flows = this.getFlowsForFlowMapLayer(state, props);
909
- if (flows) {
910
- const rv = extent(flows, this.accessors.getFlowMagnitude);
911
- return rv[0] !== undefined && rv[1] !== undefined ? rv : undefined;
912
- } else {
913
- return undefined;
914
- }
926
+ if (state.settings.adaptiveScalesEnabled) {
927
+ return this._getAdaptiveFlowMagnitudeExtent(state, props);
915
928
  } else {
916
- return this.getFlowMagnitudeExtent(state, props);
929
+ return this._getFlowMagnitudeExtent(state, props);
917
930
  }
918
931
  };
919
932
 
@@ -933,19 +946,7 @@ export default class FlowMapSelectors<L, F> {
933
946
 
934
947
  getFlowThicknessScale = createSelector(
935
948
  this.getFlowMagnitudeExtent,
936
- (magnitudeExtent) => {
937
- if (!magnitudeExtent) return undefined;
938
- return scaleLinear()
939
- .range([0.025, 0.5])
940
- .domain([
941
- 0,
942
- // should support diff mode too
943
- Math.max.apply(
944
- null,
945
- magnitudeExtent.map((x: number | undefined) => Math.abs(x || 0)),
946
- ),
947
- ]);
948
- },
949
+ getFlowThicknessScale,
949
950
  );
950
951
 
951
952
  getCircleSizeScale = createSelector(
@@ -976,7 +977,7 @@ export default class FlowMapSelectors<L, F> {
976
977
  this.getCircleSizeScale,
977
978
  this.getLocationTotals,
978
979
  (circleSizeScale, locationTotals) => {
979
- return (locationId: string) => {
980
+ return (locationId: string | number) => {
980
981
  const total = locationTotals?.get(locationId);
981
982
  if (total && circleSizeScale) {
982
983
  return (
@@ -994,7 +995,7 @@ export default class FlowMapSelectors<L, F> {
994
995
  this.getCircleSizeScale,
995
996
  this.getLocationTotals,
996
997
  (circleSizeScale, locationTotals) => {
997
- return (locationId: string) => {
998
+ return (locationId: string | number) => {
998
999
  const total = locationTotals?.get(locationId);
999
1000
  if (total && circleSizeScale) {
1000
1001
  return (
@@ -1027,7 +1028,7 @@ export default class FlowMapSelectors<L, F> {
1027
1028
  },
1028
1029
  );
1029
1030
 
1030
- getLocationsForFlowMapLayer: Selector<
1031
+ getLocationsForFlowmapLayer: Selector<
1031
1032
  L,
1032
1033
  F,
1033
1034
  Array<L | ClusterNode> | undefined
@@ -1057,11 +1058,11 @@ export default class FlowMapSelectors<L, F> {
1057
1058
  },
1058
1059
  );
1059
1060
 
1060
- getLocationsForFlowMapLayerById: Selector<
1061
+ getLocationsForFlowmapLayerById: Selector<
1061
1062
  L,
1062
1063
  F,
1063
1064
  Map<string, L | ClusterNode> | undefined
1064
- > = createSelector(this.getLocationsForFlowMapLayer, (locations) => {
1065
+ > = createSelector(this.getLocationsForFlowmapLayer, (locations) => {
1065
1066
  if (!locations) return undefined;
1066
1067
  return locations.reduce(
1067
1068
  (m, d) => (m.set(this.accessors.getLocationId(d), d), m),
@@ -1069,73 +1070,88 @@ export default class FlowMapSelectors<L, F> {
1069
1070
  );
1070
1071
  });
1071
1072
 
1073
+ getLocationOrClusterByIdGetter = createSelector(
1074
+ this.getClusterIndex,
1075
+ this.getLocationsById,
1076
+ (clusterIndex, locationsById) => {
1077
+ return (id: string | number) =>
1078
+ clusterIndex?.getClusterById(id) ?? locationsById?.get(id);
1079
+ },
1080
+ );
1081
+
1072
1082
  getLayersData: Selector<L, F, LayersData> = createSelector(
1073
- this.getLocationsForFlowMapLayer,
1074
- this.getFlowsForFlowMapLayer,
1075
- this.getFlowMapColorsRGBA,
1076
- this.getLocationsForFlowMapLayerById,
1083
+ this.getLocationsForFlowmapLayer,
1084
+ this.getFlowsForFlowmapLayer,
1085
+ this.getFlowmapColorsRGBA,
1086
+ this.getLocationsForFlowmapLayerById,
1077
1087
  this.getLocationIdsInViewport,
1078
1088
  this.getInCircleSizeGetter,
1079
1089
  this.getOutCircleSizeGetter,
1080
1090
  this.getFlowThicknessScale,
1081
1091
  this.getAnimate,
1092
+ this.getLocationLabelsEnabled,
1082
1093
  (
1083
1094
  locations,
1084
1095
  flows,
1085
- flowMapColors,
1096
+ flowmapColors,
1086
1097
  locationsById,
1087
1098
  locationIdsInViewport,
1088
1099
  getInCircleSize,
1089
1100
  getOutCircleSize,
1090
1101
  flowThicknessScale,
1091
1102
  animationEnabled,
1103
+ locationLabelsEnabled,
1092
1104
  ) => {
1093
1105
  return this._prepareLayersData(
1094
1106
  locations,
1095
1107
  flows,
1096
- flowMapColors,
1108
+ flowmapColors,
1097
1109
  locationsById,
1098
1110
  locationIdsInViewport,
1099
1111
  getInCircleSize,
1100
1112
  getOutCircleSize,
1101
1113
  flowThicknessScale,
1102
1114
  animationEnabled,
1115
+ locationLabelsEnabled,
1103
1116
  );
1104
1117
  },
1105
1118
  );
1106
1119
 
1107
- prepareLayersData(state: FlowMapState, props: FlowMapData<L, F>): LayersData {
1108
- const locations = this.getLocationsForFlowMapLayer(state, props) || [];
1109
- const flows = this.getFlowsForFlowMapLayer(state, props) || [];
1110
- const flowMapColors = this.getFlowMapColorsRGBA(state, props);
1111
- const locationsById = this.getLocationsForFlowMapLayerById(state, props);
1120
+ prepareLayersData(state: FlowmapState, props: FlowmapData<L, F>): LayersData {
1121
+ const locations = this.getLocationsForFlowmapLayer(state, props) || [];
1122
+ const flows = this.getFlowsForFlowmapLayer(state, props) || [];
1123
+ const flowmapColors = this.getFlowmapColorsRGBA(state, props);
1124
+ const locationsById = this.getLocationsForFlowmapLayerById(state, props);
1112
1125
  const locationIdsInViewport = this.getLocationIdsInViewport(state, props);
1113
1126
  const getInCircleSize = this.getInCircleSizeGetter(state, props);
1114
1127
  const getOutCircleSize = this.getOutCircleSizeGetter(state, props);
1115
1128
  const flowThicknessScale = this.getFlowThicknessScale(state, props);
1129
+ const locationLabelsEnabled = this.getLocationLabelsEnabled(state, props);
1116
1130
  return this._prepareLayersData(
1117
1131
  locations,
1118
1132
  flows,
1119
- flowMapColors,
1133
+ flowmapColors,
1120
1134
  locationsById,
1121
1135
  locationIdsInViewport,
1122
1136
  getInCircleSize,
1123
1137
  getOutCircleSize,
1124
1138
  flowThicknessScale,
1125
- state.settingsState.animationEnabled,
1139
+ state.settings.animationEnabled,
1140
+ locationLabelsEnabled,
1126
1141
  );
1127
1142
  }
1128
1143
 
1129
1144
  _prepareLayersData(
1130
1145
  locations: (L | ClusterNode)[] | undefined,
1131
1146
  flows: (F | AggregateFlow)[] | undefined,
1132
- flowMapColors: DiffColorsRGBA | ColorsRGBA,
1133
- locationsById: Map<string, L | ClusterNode> | undefined,
1134
- locationIdsInViewport: Set<string> | undefined,
1135
- getInCircleSize: (locationId: string) => number,
1136
- getOutCircleSize: (locationId: string) => number,
1147
+ flowmapColors: DiffColorsRGBA | ColorsRGBA,
1148
+ locationsById: Map<string | number, L | ClusterNode> | undefined,
1149
+ locationIdsInViewport: Set<string | number> | undefined,
1150
+ getInCircleSize: (locationId: string | number) => number,
1151
+ getOutCircleSize: (locationId: string | number) => number,
1137
1152
  flowThicknessScale: ScaleLinear<number, number, never> | undefined,
1138
1153
  animationEnabled: boolean,
1154
+ locationLabelsEnabled: boolean,
1139
1155
  ): LayersData {
1140
1156
  if (!locations) locations = [];
1141
1157
  if (!flows) flows = [];
@@ -1144,80 +1160,114 @@ export default class FlowMapSelectors<L, F> {
1144
1160
  getFlowDestId,
1145
1161
  getFlowMagnitude,
1146
1162
  getLocationId,
1147
- getLocationCentroid,
1163
+ getLocationLon,
1164
+ getLocationLat,
1165
+ getLocationName,
1148
1166
  } = this.accessors;
1149
1167
 
1150
- const getCentroid = (id: string) => {
1151
- const loc = locationsById?.get(id);
1152
- return loc ? getLocationCentroid(loc) : [0, 0];
1153
- };
1154
-
1155
1168
  const flowMagnitudeExtent = extent(flows, (f) => getFlowMagnitude(f)) as [
1156
1169
  number,
1157
1170
  number,
1158
1171
  ];
1159
1172
  const flowColorScale = getFlowColorScale(
1160
- flowMapColors,
1173
+ flowmapColors,
1161
1174
  flowMagnitudeExtent,
1162
1175
  false,
1163
1176
  );
1164
1177
 
1165
- const circlePositions = new Float32Array(
1166
- flatMap(locations, getLocationCentroid),
1178
+ // Using a generator here helps to avoid creating intermediary arrays
1179
+ const circlePositions = Float32Array.from(
1180
+ (function* () {
1181
+ for (const location of locations) {
1182
+ yield getLocationLon(location);
1183
+ yield getLocationLat(location);
1184
+ }
1185
+ })(),
1167
1186
  );
1168
1187
 
1169
1188
  // TODO: diff mode
1170
- const circleColor = isDiffColorsRGBA(flowMapColors)
1171
- ? flowMapColors.positive.locationCircles.inner
1172
- : flowMapColors.locationCircles.inner;
1173
-
1174
- const circleColors = new Uint8Array(flatMap(locations, (d) => circleColor));
1175
- const inCircleRadii = new Float32Array(
1176
- locations.map((loc) => {
1177
- const id = getLocationId(loc);
1178
- return locationIdsInViewport?.has(id) ? getInCircleSize(id) : 1.0;
1179
- }),
1189
+ const circleColor = isDiffColorsRGBA(flowmapColors)
1190
+ ? flowmapColors.positive.locationCircles.inner
1191
+ : flowmapColors.locationCircles.inner;
1192
+
1193
+ const circleColors = Uint8Array.from(
1194
+ (function* () {
1195
+ for (const location of locations) {
1196
+ yield* circleColor;
1197
+ }
1198
+ })(),
1180
1199
  );
1181
- const outCircleRadii = new Float32Array(
1182
- locations.map((loc) => {
1183
- const id = getLocationId(loc);
1184
- return locationIdsInViewport?.has(id) ? getOutCircleSize(id) : 1.0;
1185
- }),
1200
+
1201
+ const inCircleRadii = Float32Array.from(
1202
+ (function* () {
1203
+ for (const location of locations) {
1204
+ const id = getLocationId(location);
1205
+ yield locationIdsInViewport?.has(id) ? getInCircleSize(id) : 1.0;
1206
+ }
1207
+ })(),
1208
+ );
1209
+ const outCircleRadii = Float32Array.from(
1210
+ (function* () {
1211
+ for (const location of locations) {
1212
+ const id = getLocationId(location);
1213
+ yield locationIdsInViewport?.has(id) ? getOutCircleSize(id) : 1.0;
1214
+ }
1215
+ })(),
1186
1216
  );
1187
1217
 
1188
- const sourcePositions = new Float32Array(
1189
- flatMap(flows, (d: F | AggregateFlow) => getCentroid(getFlowOriginId(d))),
1218
+ const sourcePositions = Float32Array.from(
1219
+ (function* () {
1220
+ for (const flow of flows) {
1221
+ const loc = locationsById?.get(getFlowOriginId(flow));
1222
+ yield loc ? getLocationLon(loc) : 0;
1223
+ yield loc ? getLocationLat(loc) : 0;
1224
+ }
1225
+ })(),
1190
1226
  );
1191
- const targetPositions = new Float32Array(
1192
- flatMap(flows, (d: F | AggregateFlow) => getCentroid(getFlowDestId(d))),
1227
+ const targetPositions = Float32Array.from(
1228
+ (function* () {
1229
+ for (const flow of flows) {
1230
+ const loc = locationsById?.get(getFlowDestId(flow));
1231
+ yield loc ? getLocationLon(loc) : 0;
1232
+ yield loc ? getLocationLat(loc) : 0;
1233
+ }
1234
+ })(),
1193
1235
  );
1194
- const thicknesses = new Float32Array(
1195
- flows.map((d: F | AggregateFlow) =>
1196
- flowThicknessScale ? flowThicknessScale(getFlowMagnitude(d)) || 0 : 0,
1197
- ),
1236
+ const thicknesses = Float32Array.from(
1237
+ (function* () {
1238
+ for (const flow of flows) {
1239
+ yield flowThicknessScale
1240
+ ? flowThicknessScale(getFlowMagnitude(flow)) || 0
1241
+ : 0;
1242
+ }
1243
+ })(),
1198
1244
  );
1199
- const endpointOffsets = new Float32Array(
1200
- flatMap(flows, (d: F | AggregateFlow) => {
1201
- const originId = getFlowOriginId(d);
1202
- const destId = getFlowDestId(d);
1203
- return [
1204
- Math.max(getInCircleSize(originId), getOutCircleSize(originId)),
1205
- Math.max(getInCircleSize(destId), getOutCircleSize(destId)),
1206
- ];
1207
- }),
1245
+ const endpointOffsets = Float32Array.from(
1246
+ (function* () {
1247
+ for (const flow of flows) {
1248
+ const originId = getFlowOriginId(flow);
1249
+ const destId = getFlowDestId(flow);
1250
+ yield Math.max(getInCircleSize(originId), getOutCircleSize(originId));
1251
+ yield Math.max(getInCircleSize(destId), getOutCircleSize(destId));
1252
+ }
1253
+ })(),
1208
1254
  );
1209
- const flowLineColors = new Uint8Array(
1210
- flatMap(flows, (f: F | AggregateFlow) =>
1211
- flowColorScale(getFlowMagnitude(f)),
1212
- ),
1255
+ const flowLineColors = Uint8Array.from(
1256
+ (function* () {
1257
+ for (const flow of flows) {
1258
+ yield* flowColorScale(getFlowMagnitude(flow));
1259
+ }
1260
+ })(),
1213
1261
  );
1214
1262
 
1215
1263
  const staggeringValues = animationEnabled
1216
- ? new Float32Array(
1217
- flows.map((f: F | AggregateFlow) =>
1218
- // @ts-ignore
1219
- new alea(`${getFlowOriginId(f)}-${getFlowDestId(f)}`)(),
1220
- ),
1264
+ ? Float32Array.from(
1265
+ (function* () {
1266
+ for (const f of flows) {
1267
+ // @ts-ignore
1268
+ yield new alea(`${getFlowOriginId(f)}-${getFlowDestId(f)}`)();
1269
+ }
1270
+ })(),
1221
1271
  )
1222
1272
  : undefined;
1223
1273
 
@@ -1244,6 +1294,9 @@ export default class FlowMapSelectors<L, F> {
1244
1294
  : {}),
1245
1295
  },
1246
1296
  },
1297
+ ...(locationLabelsEnabled
1298
+ ? {locationLabels: locations.map(getLocationName)}
1299
+ : undefined),
1247
1300
  };
1248
1301
  }
1249
1302
 
@@ -1274,8 +1327,8 @@ export default class FlowMapSelectors<L, F> {
1274
1327
 
1275
1328
  isFlowInSelection(
1276
1329
  flow: F | AggregateFlow,
1277
- selectedLocationsSet: Set<string> | undefined,
1278
- locationFilterMode: LocationFilterMode,
1330
+ selectedLocationsSet: Set<string | number> | undefined,
1331
+ locationFilterMode?: LocationFilterMode,
1279
1332
  ) {
1280
1333
  const origin = this.accessors.getFlowOriginId(flow);
1281
1334
  const dest = this.accessors.getFlowDestId(flow);
@@ -1321,8 +1374,8 @@ export default class FlowMapSelectors<L, F> {
1321
1374
  }
1322
1375
 
1323
1376
  function calcLocationTotalsExtent(
1324
- locationTotals: Map<string, LocationTotals> | undefined,
1325
- locationIdsInViewport: Set<string> | undefined,
1377
+ locationTotals: Map<string | number, LocationTotals> | undefined,
1378
+ locationIdsInViewport: Set<string | number> | undefined,
1326
1379
  ) {
1327
1380
  if (!locationTotals) return undefined;
1328
1381
  let rv: [number, number] | undefined = undefined;
@@ -1368,10 +1421,9 @@ function aggregateFlows<F>(
1368
1421
  flowAccessors: FlowAccessors<F>,
1369
1422
  ): AggregateFlow[] {
1370
1423
  // Sum up flows with same origin, dest
1371
- const byOriginDest = nest<F, AggregateFlow>()
1372
- .key(flowAccessors.getFlowOriginId)
1373
- .key(flowAccessors.getFlowDestId)
1374
- .rollup((ff: F[]) => {
1424
+ const byOriginDest = rollup(
1425
+ flows,
1426
+ (ff: F[]) => {
1375
1427
  const origin = flowAccessors.getFlowOriginId(ff[0]);
1376
1428
  const dest = flowAccessors.getFlowDestId(ff[0]);
1377
1429
  // const color = ff[0].color;
@@ -1390,11 +1442,14 @@ function aggregateFlows<F>(
1390
1442
  };
1391
1443
  // if (color) rv.color = color;
1392
1444
  return rv;
1393
- })
1394
- .entries(flows);
1445
+ },
1446
+ flowAccessors.getFlowOriginId,
1447
+ flowAccessors.getFlowDestId,
1448
+ );
1449
+
1395
1450
  const rv: AggregateFlow[] = [];
1396
- for (const {values} of byOriginDest) {
1397
- for (const {value} of values) {
1451
+ for (const values of byOriginDest.values()) {
1452
+ for (const value of values.values()) {
1398
1453
  rv.push(value);
1399
1454
  }
1400
1455
  }
@@ -1414,7 +1469,7 @@ export function getOuterCircleRadiusByIndex(
1414
1469
  return Math.max(getInRadius.value[index], getOutRadius.value[index]);
1415
1470
  }
1416
1471
 
1417
- export function getLocationCentroidByIndex(
1472
+ export function getLocationCoordsByIndex(
1418
1473
  circleAttributes: FlowCirclesLayerAttributes,
1419
1474
  index: number,
1420
1475
  ): [number, number] {