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

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 (63) hide show
  1. package/dist/FlowmapAggregateAccessors.d.ts +16 -0
  2. package/dist/FlowmapAggregateAccessors.d.ts.map +1 -0
  3. package/dist/FlowmapAggregateAccessors.js +46 -0
  4. package/dist/{FlowMapSelectors.d.ts → FlowmapSelectors.d.ts} +87 -70
  5. package/dist/FlowmapSelectors.d.ts.map +1 -0
  6. package/dist/FlowmapSelectors.js +888 -0
  7. package/dist/{FlowMapState.d.ts → FlowmapState.d.ts} +11 -8
  8. package/dist/FlowmapState.d.ts.map +1 -0
  9. package/dist/FlowmapState.js +2 -0
  10. package/dist/cluster/cluster.d.ts +6 -5
  11. package/dist/cluster/cluster.d.ts.map +1 -1
  12. package/dist/cluster/cluster.js +76 -20
  13. package/dist/colors.d.ts +7 -7
  14. package/dist/colors.d.ts.map +1 -1
  15. package/dist/colors.js +55 -20
  16. package/dist/getViewStateForLocations.d.ts +18 -11
  17. package/dist/getViewStateForLocations.d.ts.map +1 -1
  18. package/dist/getViewStateForLocations.js +37 -23
  19. package/dist/index.d.ts +9 -6
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +9 -6
  22. package/dist/provider/FlowmapDataProvider.d.ts +21 -0
  23. package/dist/provider/FlowmapDataProvider.d.ts.map +1 -0
  24. package/dist/provider/FlowmapDataProvider.js +17 -0
  25. package/dist/provider/LocalFlowmapDataProvider.d.ts +21 -0
  26. package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -0
  27. package/dist/provider/LocalFlowmapDataProvider.js +104 -0
  28. package/dist/selector-functions.d.ts +4 -0
  29. package/dist/selector-functions.d.ts.map +1 -0
  30. package/dist/selector-functions.js +20 -0
  31. package/dist/types.d.ts +12 -9
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js +4 -4
  34. package/dist/util.d.ts +0 -1
  35. package/dist/util.d.ts.map +1 -1
  36. package/dist/util.js +1 -4
  37. package/package.json +10 -12
  38. package/src/{FlowMapAggregateAccessors.ts → FlowmapAggregateAccessors.ts} +14 -9
  39. package/src/{FlowMapSelectors.ts → FlowmapSelectors.ts} +349 -282
  40. package/src/{FlowMapState.ts → FlowmapState.ts} +10 -7
  41. package/src/cluster/cluster.ts +95 -34
  42. package/src/colors.ts +70 -28
  43. package/src/getViewStateForLocations.ts +56 -40
  44. package/src/index.ts +9 -6
  45. package/src/provider/{FlowMapDataProvider.ts → FlowmapDataProvider.ts} +27 -17
  46. package/src/provider/LocalFlowmapDataProvider.ts +129 -0
  47. package/src/selector-functions.ts +34 -0
  48. package/src/types.ts +15 -12
  49. package/src/util.ts +0 -4
  50. package/dist/FlowMapAggregateAccessors.d.ts +0 -15
  51. package/dist/FlowMapAggregateAccessors.d.ts.map +0 -1
  52. package/dist/FlowMapAggregateAccessors.js +0 -43
  53. package/dist/FlowMapSelectors.d.ts.map +0 -1
  54. package/dist/FlowMapSelectors.js +0 -834
  55. package/dist/FlowMapState.d.ts.map +0 -1
  56. package/dist/FlowMapState.js +0 -2
  57. package/dist/provider/FlowMapDataProvider.d.ts +0 -16
  58. package/dist/provider/FlowMapDataProvider.d.ts.map +0 -1
  59. package/dist/provider/FlowMapDataProvider.js +0 -17
  60. package/dist/provider/LocalFlowMapDataProvider.d.ts +0 -20
  61. package/dist/provider/LocalFlowMapDataProvider.d.ts.map +0 -1
  62. package/dist/provider/LocalFlowMapDataProvider.js +0 -87
  63. 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,12 @@ 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
+ getFlowThicknessScale,
50
+ getViewportBoundingBox,
51
+ } from './selector-functions';
49
52
  import {
50
53
  getTimeGranularityByKey,
51
54
  getTimeGranularityByOrder,
@@ -55,98 +58,108 @@ import {
55
58
  import {
56
59
  AggregateFlow,
57
60
  Cluster,
61
+ ClusterLevels,
58
62
  ClusterNode,
59
63
  CountByTime,
60
64
  FlowAccessors,
61
65
  FlowCirclesLayerAttributes,
62
66
  FlowLinesLayerAttributes,
63
- FlowMapData,
64
- FlowMapDataAccessors,
67
+ FlowmapData,
68
+ FlowmapDataAccessors,
65
69
  isCluster,
66
70
  isLocationClusterNode,
67
71
  LayersData,
68
72
  LocationFilterMode,
69
73
  LocationTotals,
70
74
  } from './types';
71
- import {flatMap} from './util';
72
75
 
73
76
  const MAX_CLUSTER_ZOOM_LEVEL = 20;
74
- const NUMBER_OF_FLOWS_TO_DISPLAY = 5000;
75
77
  type KDBushTree = any;
76
78
 
77
79
  export type Selector<L, F, T> = ParametricSelector<
78
- FlowMapState,
79
- FlowMapData<L, F>,
80
+ FlowmapState,
81
+ FlowmapData<L, F>,
80
82
  T
81
83
  >;
82
84
 
83
- export default class FlowMapSelectors<L, F> {
84
- accessors: FlowMapAggregateAccessors<L, F>;
85
+ export default class FlowmapSelectors<L, F> {
86
+ accessors: FlowmapAggregateAccessors<L, F>;
85
87
 
86
- constructor(accessors: FlowMapDataAccessors<L, F>) {
87
- this.accessors = new FlowMapAggregateAccessors(accessors);
88
+ constructor(accessors: FlowmapDataAccessors<L, F>) {
89
+ this.accessors = new FlowmapAggregateAccessors(accessors);
88
90
  this.setAccessors(accessors);
89
91
  }
90
92
 
91
- setAccessors(accessors: FlowMapDataAccessors<L, F>) {
92
- this.accessors = new FlowMapAggregateAccessors(accessors);
93
+ setAccessors(accessors: FlowmapDataAccessors<L, F>) {
94
+ this.accessors = new FlowmapAggregateAccessors(accessors);
93
95
  }
94
96
 
95
- getFetchedFlows = (state: FlowMapState, props: FlowMapData<L, F>) =>
97
+ getFlowsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
96
98
  props.flows;
97
- getFetchedLocations = (state: FlowMapState, props: FlowMapData<L, F>) =>
99
+ getLocationsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
98
100
  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>) =>
101
+ getClusterLevelsFromProps = (
102
+ state: FlowmapState,
103
+ props: FlowmapData<L, F>,
104
+ ) => {
105
+ return props.clusterLevels;
106
+ };
107
+ getMaxTopFlowsDisplayNum = (state: FlowmapState, props: FlowmapData<L, F>) =>
108
+ state.settings.maxTopFlowsDisplayNum;
109
+ getSelectedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
110
+ state.filter?.selectedLocations;
111
+ getLocationFilterMode = (state: FlowmapState, props: FlowmapData<L, F>) =>
112
+ state.filter?.locationFilterMode;
113
+ getClusteringEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
114
+ state.settings.clusteringEnabled;
115
+ getLocationTotalsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
116
+ state.settings.locationTotalsEnabled;
117
+ getZoom = (state: FlowmapState, props: FlowmapData<L, F>) =>
108
118
  state.viewport.zoom;
109
- getViewport = (state: FlowMapState, props: FlowMapData<L, F>) =>
119
+ getViewport = (state: FlowmapState, props: FlowmapData<L, F>) =>
110
120
  state.viewport;
111
- getSelectedTimeRange = (state: FlowMapState, props: FlowMapData<L, F>) =>
112
- state.filterState.selectedTimeRange;
121
+ getSelectedTimeRange = (state: FlowmapState, props: FlowmapData<L, F>) =>
122
+ state.filter?.selectedTimeRange;
113
123
 
114
- getColorSchemeKey: Selector<L, F, string | undefined> = (
115
- state: FlowMapState,
116
- props: FlowMapData<L, F>,
117
- ) => state.settingsState.colorScheme;
124
+ getColorScheme: Selector<L, F, string | string[] | undefined> = (
125
+ state: FlowmapState,
126
+ props: FlowmapData<L, F>,
127
+ ) => state.settings.colorScheme;
118
128
 
119
129
  getDarkMode: Selector<L, F, boolean> = (
120
- state: FlowMapState,
121
- props: FlowMapData<L, F>,
122
- ) => state.settingsState.darkMode;
130
+ state: FlowmapState,
131
+ props: FlowmapData<L, F>,
132
+ ) => state.settings.darkMode;
123
133
 
124
134
  getFadeEnabled: Selector<L, F, boolean> = (
125
- state: FlowMapState,
126
- props: FlowMapData<L, F>,
127
- ) => state.settingsState.fadeEnabled;
135
+ state: FlowmapState,
136
+ props: FlowmapData<L, F>,
137
+ ) => state.settings.fadeEnabled;
138
+
139
+ getFadeOpacityEnabled: Selector<L, F, boolean> = (
140
+ state: FlowmapState,
141
+ props: FlowmapData<L, F>,
142
+ ) => state.settings.fadeOpacityEnabled;
128
143
 
129
144
  getFadeAmount: Selector<L, F, number> = (
130
- state: FlowMapState,
131
- props: FlowMapData<L, F>,
132
- ) => state.settingsState.fadeAmount;
145
+ state: FlowmapState,
146
+ props: FlowmapData<L, F>,
147
+ ) => state.settings.fadeAmount;
133
148
 
134
149
  getAnimate: Selector<L, F, boolean> = (
135
- state: FlowMapState,
136
- props: FlowMapData<L, F>,
137
- ) => state.settingsState.animationEnabled;
150
+ state: FlowmapState,
151
+ props: FlowmapData<L, F>,
152
+ ) => state.settings.animationEnabled;
138
153
 
139
154
  getInvalidLocationIds: Selector<L, F, string[] | undefined> = createSelector(
140
- this.getFetchedLocations,
155
+ this.getLocationsFromProps,
141
156
  (locations) => {
142
157
  if (!locations) return undefined;
143
158
  const invalid = [];
144
159
  for (const location of locations) {
145
160
  const id = this.accessors.getLocationId(location);
146
- const [lon, lat] = this.accessors.getLocationCentroid(location) || [
147
- NaN,
148
- NaN,
149
- ];
161
+ const lon = this.accessors.getLocationLon(location);
162
+ const lat = this.accessors.getLocationLat(location);
150
163
  if (!(-90 <= lat && lat <= 90) || !(-180 <= lon && lon <= 180)) {
151
164
  invalid.push(id);
152
165
  }
@@ -155,25 +168,34 @@ export default class FlowMapSelectors<L, F> {
155
168
  },
156
169
  );
157
170
 
158
- getLocations: Selector<L, F, L[] | undefined> = createSelector(
159
- this.getFetchedLocations,
171
+ getLocations: Selector<L, F, Iterable<L> | undefined> = createSelector(
172
+ this.getLocationsFromProps,
160
173
  this.getInvalidLocationIds,
161
174
  (locations, invalidIds) => {
162
175
  if (!locations) return undefined;
163
176
  if (!invalidIds || invalidIds.length === 0) return locations;
164
177
  const invalid = new Set(invalidIds);
165
- return locations.filter(
166
- (location: L) => !invalid.has(this.accessors.getLocationId(location)),
167
- );
178
+ const filtered: L[] = [];
179
+ for (const location of locations) {
180
+ const id = this.accessors.getLocationId(location);
181
+ if (!invalid.has(id)) {
182
+ filtered.push(location);
183
+ }
184
+ }
185
+ return filtered;
168
186
  },
169
187
  );
170
188
 
171
189
  getLocationIds: Selector<L, F, Set<string> | undefined> = createSelector(
172
190
  this.getLocations,
173
- (locations) =>
174
- locations
175
- ? new Set(locations.map(this.accessors.getLocationId))
176
- : undefined,
191
+ (locations) => {
192
+ if (!locations) return undefined;
193
+ const ids = new Set<string>();
194
+ for (const id of locations) {
195
+ ids.add(this.accessors.getLocationId(id));
196
+ }
197
+ return ids;
198
+ },
177
199
  );
178
200
 
179
201
  getSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
@@ -182,21 +204,27 @@ export default class FlowMapSelectors<L, F> {
182
204
  );
183
205
 
184
206
  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) =>
207
+ createSelector(
208
+ this.getFlowsFromProps,
209
+ this.getLocationIds,
210
+ (flows, ids) => {
211
+ if (!ids || !flows) return undefined;
212
+ const filtered = [];
213
+ for (const flow of flows) {
214
+ const srcId = this.accessors.getFlowOriginId(flow);
215
+ const dstId = this.accessors.getFlowDestId(flow);
216
+ if (ids.has(srcId) && ids.has(dstId)) {
217
+ filtered.push(flow);
218
+ }
219
+ }
220
+ return filtered.sort((a: F, b: F) =>
194
221
  descending(
195
222
  Math.abs(this.accessors.getFlowMagnitude(a)),
196
223
  Math.abs(this.accessors.getFlowMagnitude(b)),
197
224
  ),
198
225
  );
199
- });
226
+ },
227
+ );
200
228
 
201
229
  getActualTimeExtent: Selector<L, F, [Date, Date] | undefined> =
202
230
  createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
@@ -268,55 +296,79 @@ export default class FlowMapSelectors<L, F> {
268
296
  },
269
297
  );
270
298
 
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
- );
299
+ getLocationsHavingFlows: Selector<L, F, Iterable<L> | undefined> =
300
+ createSelector(
301
+ this.getSortedFlowsForKnownLocations,
302
+ this.getLocations,
303
+ (flows, locations) => {
304
+ if (!locations || !flows) return locations;
305
+ const withFlows = new Set();
306
+ for (const flow of flows) {
307
+ withFlows.add(this.accessors.getFlowOriginId(flow));
308
+ withFlows.add(this.accessors.getFlowDestId(flow));
309
+ }
310
+ const filtered = [];
311
+ for (const location of locations) {
312
+ if (withFlows.has(this.accessors.getLocationId(location))) {
313
+ filtered.push(location);
314
+ }
315
+ }
316
+ return filtered;
317
+ },
318
+ );
286
319
 
287
320
  getLocationsById: Selector<L, F, Map<string, L> | undefined> = createSelector(
288
321
  this.getLocationsHavingFlows,
289
322
  (locations) => {
290
323
  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>;
324
+ const locationsById = new Map<string, L>();
325
+ for (const location of locations) {
326
+ locationsById.set(this.accessors.getLocationId(location), location);
327
+ }
328
+ return locationsById;
295
329
  },
296
330
  );
297
331
 
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;
304
-
332
+ getLocationWeightGetter: Selector<L, F, LocationWeightGetter | undefined> =
333
+ createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
334
+ if (!flows) return undefined;
305
335
  const getLocationWeight = makeLocationWeightGetter(
306
336
  flows,
307
- this.accessors.getFlowMapDataAccessors(),
337
+ this.accessors.getFlowmapDataAccessors(),
308
338
  );
339
+ return getLocationWeight;
340
+ });
341
+
342
+ getClusterLevels: Selector<L, F, ClusterLevels | undefined> = createSelector(
343
+ this.getClusterLevelsFromProps,
344
+ this.getLocationsHavingFlows,
345
+ this.getLocationWeightGetter,
346
+ (clusterLevelsFromProps, locations, getLocationWeight) => {
347
+ if (clusterLevelsFromProps) return clusterLevelsFromProps;
348
+ if (!locations || !getLocationWeight) return undefined;
309
349
  const clusterLevels = clusterLocations(
310
350
  locations,
311
- this.accessors.getFlowMapDataAccessors(),
351
+ this.accessors.getFlowmapDataAccessors(),
312
352
  getLocationWeight,
313
353
  {
314
354
  maxZoom: MAX_CLUSTER_ZOOM_LEVEL,
315
355
  },
316
356
  );
357
+ return clusterLevels;
358
+ },
359
+ );
360
+
361
+ getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
362
+ this.getLocationsById,
363
+ this.getLocationWeightGetter,
364
+ this.getClusterLevels,
365
+ (locationsById, getLocationWeight, clusterLevels) => {
366
+ if (!locationsById || !getLocationWeight || !clusterLevels)
367
+ return undefined;
368
+
317
369
  const clusterIndex = buildIndex<F>(clusterLevels);
318
370
  const {getLocationName, getLocationClusterName} =
319
- this.accessors.getFlowMapDataAccessors();
371
+ this.accessors.getFlowmapDataAccessors();
320
372
 
321
373
  // Adding meaningful names
322
374
  const getName = (id: string) => {
@@ -399,7 +451,7 @@ export default class FlowMapSelectors<L, F> {
399
451
  this.getAvailableClusterZoomLevels,
400
452
  (clusterIndex, mapZoom, availableClusterZoomLevels) => {
401
453
  if (!clusterIndex) return undefined;
402
- if (!availableClusterZoomLevels) {
454
+ if (!availableClusterZoomLevels || mapZoom == null) {
403
455
  return undefined;
404
456
  }
405
457
 
@@ -411,13 +463,13 @@ export default class FlowMapSelectors<L, F> {
411
463
  },
412
464
  );
413
465
 
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) {
466
+ getClusterZoom = (state: FlowmapState, props: FlowmapData<L, F>) => {
467
+ const {settings} = state;
468
+ if (!settings.clusteringEnabled) return undefined;
469
+ if (settings.clusteringAuto || settings.clusteringLevel == null) {
418
470
  return this._getClusterZoom(state, props);
419
471
  }
420
- return settingsState.clusteringLevel;
472
+ return settings.clusteringLevel;
421
473
  };
422
474
 
423
475
  getLocationsForSearchBox: Selector<L, F, (L | Cluster)[] | undefined> =
@@ -435,7 +487,7 @@ export default class FlowMapSelectors<L, F> {
435
487
  clusterIndex,
436
488
  ) => {
437
489
  if (!locations) return undefined;
438
- let result: (L | Cluster)[] = locations;
490
+ let result: (L | Cluster)[] = Array.from(locations);
439
491
  // if (clusteringEnabled) {
440
492
  // if (clusterIndex) {
441
493
  // const zoomItems = clusterIndex.getClusterNodesFor(clusterZoom);
@@ -445,7 +497,7 @@ export default class FlowMapSelectors<L, F> {
445
497
  // }
446
498
  // }
447
499
 
448
- if (result && clusterIndex && selectedLocations) {
500
+ if (clusterIndex && selectedLocations) {
449
501
  const toAppend = [];
450
502
  for (const id of selectedLocations) {
451
503
  const cluster = clusterIndex.getClusterById(id);
@@ -470,46 +522,48 @@ export default class FlowMapSelectors<L, F> {
470
522
  );
471
523
 
472
524
  getDiffMode: Selector<L, F, boolean> = createSelector(
473
- this.getFetchedFlows,
525
+ this.getFlowsFromProps,
474
526
  (flows) => {
475
- if (
476
- flows &&
477
- flows.find((f: F) => this.accessors.getFlowMagnitude(f) < 0)
478
- ) {
479
- return true;
527
+ if (flows) {
528
+ for (const f of flows) {
529
+ if (this.accessors.getFlowMagnitude(f) < 0) {
530
+ return true;
531
+ }
532
+ }
480
533
  }
481
534
  return false;
482
535
  },
483
536
  );
484
537
 
485
- _getFlowMapColors = createSelector(
538
+ _getFlowmapColors = createSelector(
486
539
  this.getDiffMode,
487
- this.getColorSchemeKey,
540
+ this.getColorScheme,
488
541
  this.getDarkMode,
489
542
  this.getFadeEnabled,
543
+ this.getFadeOpacityEnabled,
490
544
  this.getFadeAmount,
491
545
  this.getAnimate,
492
546
  getColors,
493
547
  );
494
548
 
495
- getFlowMapColorsRGBA = createSelector(
496
- this._getFlowMapColors,
497
- (flowMapColors) => {
498
- return isDiffColors(flowMapColors)
499
- ? getDiffColorsRGBA(flowMapColors)
500
- : getColorsRGBA(flowMapColors);
549
+ getFlowmapColorsRGBA = createSelector(
550
+ this._getFlowmapColors,
551
+ (flowmapColors) => {
552
+ return isDiffColors(flowmapColors)
553
+ ? getDiffColorsRGBA(flowmapColors)
554
+ : getColorsRGBA(flowmapColors);
501
555
  },
502
556
  );
503
557
 
504
558
  getUnknownLocations: Selector<L, F, Set<string> | undefined> = createSelector(
505
559
  this.getLocationIds,
506
- this.getFetchedFlows,
560
+ this.getFlowsFromProps,
507
561
  this.getSortedFlowsForKnownLocations,
508
562
  (ids, flows, flowsForKnownLocations) => {
509
563
  if (!ids || !flows) return undefined;
510
564
  if (
511
- flowsForKnownLocations &&
512
- flows.length === flowsForKnownLocations.length
565
+ flowsForKnownLocations
566
+ // && flows.length === flowsForKnownLocations.length
513
567
  )
514
568
  return undefined;
515
569
  const missing = new Set<string>();
@@ -544,12 +598,12 @@ export default class FlowMapSelectors<L, F> {
544
598
  // : flows,
545
599
  flows,
546
600
  clusterZoom,
547
- this.accessors.getFlowMapDataAccessors(),
601
+ this.accessors.getFlowmapDataAccessors(),
548
602
  );
549
603
  } else {
550
604
  aggregated = aggregateFlows(
551
605
  flows,
552
- this.accessors.getFlowMapDataAccessors(),
606
+ this.accessors.getFlowmapDataAccessors(),
553
607
  );
554
608
  }
555
609
  aggregated.sort((a, b) =>
@@ -562,33 +616,6 @@ export default class FlowMapSelectors<L, F> {
562
616
  },
563
617
  );
564
618
 
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
619
  getExpandedSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
593
620
  createSelector(
594
621
  this.getClusteringEnabled,
@@ -668,18 +695,10 @@ export default class FlowMapSelectors<L, F> {
668
695
  createSelector(
669
696
  this.getViewport,
670
697
  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
- },
698
+ getViewportBoundingBox,
680
699
  );
681
700
 
682
- getLocationsForZoom: Selector<L, F, L[] | ClusterNode[] | undefined> =
701
+ getLocationsForZoom: Selector<L, F, Iterable<L> | ClusterNode[] | undefined> =
683
702
  createSelector(
684
703
  this.getClusteringEnabled,
685
704
  this.getLocationsHavingFlows,
@@ -746,17 +765,9 @@ export default class FlowMapSelectors<L, F> {
746
765
  // @ts-ignore
747
766
  locations,
748
767
  (location: L | ClusterNode) =>
749
- lngX(
750
- isLocationClusterNode(location)
751
- ? location.centroid[0]
752
- : this.accessors.getLocationCentroid(location)[0],
753
- ),
768
+ lngX(this.accessors.getLocationLon(location)),
754
769
  (location: L | ClusterNode) =>
755
- latY(
756
- isLocationClusterNode(location)
757
- ? location.centroid[1]
758
- : this.accessors.getLocationCentroid(location)[1],
759
- ),
770
+ latY(this.accessors.getLocationLat(location)),
760
771
  );
761
772
  },
762
773
  );
@@ -845,27 +856,29 @@ export default class FlowMapSelectors<L, F> {
845
856
  );
846
857
 
847
858
  getLocationTotalsExtent = (
848
- state: FlowMapState,
849
- props: FlowMapData<L, F>,
859
+ state: FlowmapState,
860
+ props: FlowmapData<L, F>,
850
861
  ): [number, number] | undefined => {
851
- if (state.settingsState.adaptiveScalesEnabled) {
862
+ if (state.settings.adaptiveScalesEnabled) {
852
863
  return this._getLocationTotalsForViewportExtent(state, props);
853
864
  } else {
854
865
  return this._getLocationTotalsExtent(state, props);
855
866
  }
856
867
  };
857
868
 
858
- getFlowsForFlowMapLayer: Selector<L, F, (F | AggregateFlow)[] | undefined> =
869
+ getFlowsForFlowmapLayer: Selector<L, F, (F | AggregateFlow)[] | undefined> =
859
870
  createSelector(
860
871
  this.getSortedAggregatedFilteredFlows,
861
872
  this.getLocationIdsInViewport,
862
873
  this.getSelectedLocationsSet,
863
874
  this.getLocationFilterMode,
875
+ this.getMaxTopFlowsDisplayNum,
864
876
  (
865
877
  flows,
866
878
  locationIdsInViewport,
867
879
  selectedLocationsSet,
868
880
  locationFilterMode,
881
+ maxTopFlowsDisplayNum,
869
882
  ) => {
870
883
  if (!flows || !locationIdsInViewport) return undefined;
871
884
  const picked: (F | AggregateFlow)[] = [];
@@ -892,7 +905,7 @@ export default class FlowMapSelectors<L, F> {
892
905
  }
893
906
  }
894
907
  // Only keep top
895
- if (pickedCount > NUMBER_OF_FLOWS_TO_DISPLAY) break;
908
+ if (pickedCount > maxTopFlowsDisplayNum) break;
896
909
  }
897
910
  // assuming they are sorted in descending order,
898
911
  // we need ascending for rendering
@@ -900,20 +913,51 @@ export default class FlowMapSelectors<L, F> {
900
913
  },
901
914
  );
902
915
 
903
- _getFlowMagnitudeExtent = (
904
- state: FlowMapState,
905
- props: FlowMapData<L, F>,
916
+ _getFlowMagnitudeExtent: Selector<L, F, [number, number] | undefined> =
917
+ createSelector(
918
+ this.getSortedAggregatedFilteredFlows,
919
+ this.getSelectedLocationsSet,
920
+ this.getLocationFilterMode,
921
+ (flows, selectedLocationsSet, locationFilterMode) => {
922
+ if (!flows) return undefined;
923
+ let rv: [number, number] | undefined = undefined;
924
+ for (const f of flows) {
925
+ if (
926
+ this.accessors.getFlowOriginId(f) !==
927
+ this.accessors.getFlowDestId(f) &&
928
+ this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
929
+ ) {
930
+ const count = this.accessors.getFlowMagnitude(f);
931
+ if (rv == null) {
932
+ rv = [count, count];
933
+ } else {
934
+ if (count < rv[0]) rv[0] = count;
935
+ if (count > rv[1]) rv[1] = count;
936
+ }
937
+ }
938
+ }
939
+ return rv;
940
+ },
941
+ );
942
+
943
+ _getAdaptiveFlowMagnitudeExtent: Selector<
944
+ L,
945
+ F,
946
+ [number, number] | undefined
947
+ > = createSelector(this.getFlowsForFlowmapLayer, (flows) => {
948
+ if (!flows) return undefined;
949
+ const rv = extent(flows, this.accessors.getFlowMagnitude);
950
+ return rv[0] !== undefined && rv[1] !== undefined ? rv : undefined;
951
+ });
952
+
953
+ getFlowMagnitudeExtent = (
954
+ state: FlowmapState,
955
+ props: FlowmapData<L, F>,
906
956
  ): [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
- }
957
+ if (state.settings.adaptiveScalesEnabled) {
958
+ return this._getAdaptiveFlowMagnitudeExtent(state, props);
915
959
  } else {
916
- return this.getFlowMagnitudeExtent(state, props);
960
+ return this._getFlowMagnitudeExtent(state, props);
917
961
  }
918
962
  };
919
963
 
@@ -933,19 +977,7 @@ export default class FlowMapSelectors<L, F> {
933
977
 
934
978
  getFlowThicknessScale = createSelector(
935
979
  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
- },
980
+ getFlowThicknessScale,
949
981
  );
950
982
 
951
983
  getCircleSizeScale = createSelector(
@@ -1027,7 +1059,7 @@ export default class FlowMapSelectors<L, F> {
1027
1059
  },
1028
1060
  );
1029
1061
 
1030
- getLocationsForFlowMapLayer: Selector<
1062
+ getLocationsForFlowmapLayer: Selector<
1031
1063
  L,
1032
1064
  F,
1033
1065
  Array<L | ClusterNode> | undefined
@@ -1057,11 +1089,11 @@ export default class FlowMapSelectors<L, F> {
1057
1089
  },
1058
1090
  );
1059
1091
 
1060
- getLocationsForFlowMapLayerById: Selector<
1092
+ getLocationsForFlowmapLayerById: Selector<
1061
1093
  L,
1062
1094
  F,
1063
1095
  Map<string, L | ClusterNode> | undefined
1064
- > = createSelector(this.getLocationsForFlowMapLayer, (locations) => {
1096
+ > = createSelector(this.getLocationsForFlowmapLayer, (locations) => {
1065
1097
  if (!locations) return undefined;
1066
1098
  return locations.reduce(
1067
1099
  (m, d) => (m.set(this.accessors.getLocationId(d), d), m),
@@ -1070,10 +1102,10 @@ export default class FlowMapSelectors<L, F> {
1070
1102
  });
1071
1103
 
1072
1104
  getLayersData: Selector<L, F, LayersData> = createSelector(
1073
- this.getLocationsForFlowMapLayer,
1074
- this.getFlowsForFlowMapLayer,
1075
- this.getFlowMapColorsRGBA,
1076
- this.getLocationsForFlowMapLayerById,
1105
+ this.getLocationsForFlowmapLayer,
1106
+ this.getFlowsForFlowmapLayer,
1107
+ this.getFlowmapColorsRGBA,
1108
+ this.getLocationsForFlowmapLayerById,
1077
1109
  this.getLocationIdsInViewport,
1078
1110
  this.getInCircleSizeGetter,
1079
1111
  this.getOutCircleSizeGetter,
@@ -1082,7 +1114,7 @@ export default class FlowMapSelectors<L, F> {
1082
1114
  (
1083
1115
  locations,
1084
1116
  flows,
1085
- flowMapColors,
1117
+ flowmapColors,
1086
1118
  locationsById,
1087
1119
  locationIdsInViewport,
1088
1120
  getInCircleSize,
@@ -1093,7 +1125,7 @@ export default class FlowMapSelectors<L, F> {
1093
1125
  return this._prepareLayersData(
1094
1126
  locations,
1095
1127
  flows,
1096
- flowMapColors,
1128
+ flowmapColors,
1097
1129
  locationsById,
1098
1130
  locationIdsInViewport,
1099
1131
  getInCircleSize,
@@ -1104,11 +1136,11 @@ export default class FlowMapSelectors<L, F> {
1104
1136
  },
1105
1137
  );
1106
1138
 
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);
1139
+ prepareLayersData(state: FlowmapState, props: FlowmapData<L, F>): LayersData {
1140
+ const locations = this.getLocationsForFlowmapLayer(state, props) || [];
1141
+ const flows = this.getFlowsForFlowmapLayer(state, props) || [];
1142
+ const flowmapColors = this.getFlowmapColorsRGBA(state, props);
1143
+ const locationsById = this.getLocationsForFlowmapLayerById(state, props);
1112
1144
  const locationIdsInViewport = this.getLocationIdsInViewport(state, props);
1113
1145
  const getInCircleSize = this.getInCircleSizeGetter(state, props);
1114
1146
  const getOutCircleSize = this.getOutCircleSizeGetter(state, props);
@@ -1116,20 +1148,20 @@ export default class FlowMapSelectors<L, F> {
1116
1148
  return this._prepareLayersData(
1117
1149
  locations,
1118
1150
  flows,
1119
- flowMapColors,
1151
+ flowmapColors,
1120
1152
  locationsById,
1121
1153
  locationIdsInViewport,
1122
1154
  getInCircleSize,
1123
1155
  getOutCircleSize,
1124
1156
  flowThicknessScale,
1125
- state.settingsState.animationEnabled,
1157
+ state.settings.animationEnabled,
1126
1158
  );
1127
1159
  }
1128
1160
 
1129
1161
  _prepareLayersData(
1130
1162
  locations: (L | ClusterNode)[] | undefined,
1131
1163
  flows: (F | AggregateFlow)[] | undefined,
1132
- flowMapColors: DiffColorsRGBA | ColorsRGBA,
1164
+ flowmapColors: DiffColorsRGBA | ColorsRGBA,
1133
1165
  locationsById: Map<string, L | ClusterNode> | undefined,
1134
1166
  locationIdsInViewport: Set<string> | undefined,
1135
1167
  getInCircleSize: (locationId: string) => number,
@@ -1144,80 +1176,113 @@ export default class FlowMapSelectors<L, F> {
1144
1176
  getFlowDestId,
1145
1177
  getFlowMagnitude,
1146
1178
  getLocationId,
1147
- getLocationCentroid,
1179
+ getLocationLon,
1180
+ getLocationLat,
1148
1181
  } = this.accessors;
1149
1182
 
1150
- const getCentroid = (id: string) => {
1151
- const loc = locationsById?.get(id);
1152
- return loc ? getLocationCentroid(loc) : [0, 0];
1153
- };
1154
-
1155
1183
  const flowMagnitudeExtent = extent(flows, (f) => getFlowMagnitude(f)) as [
1156
1184
  number,
1157
1185
  number,
1158
1186
  ];
1159
1187
  const flowColorScale = getFlowColorScale(
1160
- flowMapColors,
1188
+ flowmapColors,
1161
1189
  flowMagnitudeExtent,
1162
1190
  false,
1163
1191
  );
1164
1192
 
1165
- const circlePositions = new Float32Array(
1166
- flatMap(locations, getLocationCentroid),
1193
+ // Using a generator here helps to avoid creating intermediary arrays
1194
+ const circlePositions = Float32Array.from(
1195
+ (function* () {
1196
+ for (const location of locations) {
1197
+ yield getLocationLon(location);
1198
+ yield getLocationLat(location);
1199
+ }
1200
+ })(),
1167
1201
  );
1168
1202
 
1169
1203
  // 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
- }),
1204
+ const circleColor = isDiffColorsRGBA(flowmapColors)
1205
+ ? flowmapColors.positive.locationCircles.inner
1206
+ : flowmapColors.locationCircles.inner;
1207
+
1208
+ const circleColors = Uint8Array.from(
1209
+ (function* () {
1210
+ for (const location of locations) {
1211
+ yield* circleColor;
1212
+ }
1213
+ })(),
1180
1214
  );
1181
- const outCircleRadii = new Float32Array(
1182
- locations.map((loc) => {
1183
- const id = getLocationId(loc);
1184
- return locationIdsInViewport?.has(id) ? getOutCircleSize(id) : 1.0;
1185
- }),
1215
+
1216
+ const inCircleRadii = Float32Array.from(
1217
+ (function* () {
1218
+ for (const location of locations) {
1219
+ const id = getLocationId(location);
1220
+ yield locationIdsInViewport?.has(id) ? getInCircleSize(id) : 1.0;
1221
+ }
1222
+ })(),
1223
+ );
1224
+ const outCircleRadii = Float32Array.from(
1225
+ (function* () {
1226
+ for (const location of locations) {
1227
+ const id = getLocationId(location);
1228
+ yield locationIdsInViewport?.has(id) ? getOutCircleSize(id) : 1.0;
1229
+ }
1230
+ })(),
1186
1231
  );
1187
1232
 
1188
- const sourcePositions = new Float32Array(
1189
- flatMap(flows, (d: F | AggregateFlow) => getCentroid(getFlowOriginId(d))),
1233
+ const sourcePositions = Float32Array.from(
1234
+ (function* () {
1235
+ for (const flow of flows) {
1236
+ const loc = locationsById?.get(getFlowOriginId(flow));
1237
+ yield loc ? getLocationLon(loc) : 0;
1238
+ yield loc ? getLocationLat(loc) : 0;
1239
+ }
1240
+ })(),
1190
1241
  );
1191
- const targetPositions = new Float32Array(
1192
- flatMap(flows, (d: F | AggregateFlow) => getCentroid(getFlowDestId(d))),
1242
+ const targetPositions = Float32Array.from(
1243
+ (function* () {
1244
+ for (const flow of flows) {
1245
+ const loc = locationsById?.get(getFlowDestId(flow));
1246
+ yield loc ? getLocationLon(loc) : 0;
1247
+ yield loc ? getLocationLat(loc) : 0;
1248
+ }
1249
+ })(),
1193
1250
  );
1194
- const thicknesses = new Float32Array(
1195
- flows.map((d: F | AggregateFlow) =>
1196
- flowThicknessScale ? flowThicknessScale(getFlowMagnitude(d)) || 0 : 0,
1197
- ),
1251
+ const thicknesses = Float32Array.from(
1252
+ (function* () {
1253
+ for (const flow of flows) {
1254
+ yield flowThicknessScale
1255
+ ? flowThicknessScale(getFlowMagnitude(flow)) || 0
1256
+ : 0;
1257
+ }
1258
+ })(),
1198
1259
  );
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
- }),
1260
+ const endpointOffsets = Float32Array.from(
1261
+ (function* () {
1262
+ for (const flow of flows) {
1263
+ const originId = getFlowOriginId(flow);
1264
+ const destId = getFlowDestId(flow);
1265
+ yield Math.max(getInCircleSize(originId), getOutCircleSize(originId));
1266
+ yield Math.max(getInCircleSize(destId), getOutCircleSize(destId));
1267
+ }
1268
+ })(),
1208
1269
  );
1209
- const flowLineColors = new Uint8Array(
1210
- flatMap(flows, (f: F | AggregateFlow) =>
1211
- flowColorScale(getFlowMagnitude(f)),
1212
- ),
1270
+ const flowLineColors = Uint8Array.from(
1271
+ (function* () {
1272
+ for (const flow of flows) {
1273
+ yield* flowColorScale(getFlowMagnitude(flow));
1274
+ }
1275
+ })(),
1213
1276
  );
1214
1277
 
1215
1278
  const staggeringValues = animationEnabled
1216
- ? new Float32Array(
1217
- flows.map((f: F | AggregateFlow) =>
1218
- // @ts-ignore
1219
- new alea(`${getFlowOriginId(f)}-${getFlowDestId(f)}`)(),
1220
- ),
1279
+ ? Float32Array.from(
1280
+ (function* () {
1281
+ for (const f of flows) {
1282
+ // @ts-ignore
1283
+ yield new alea(`${getFlowOriginId(f)}-${getFlowDestId(f)}`)();
1284
+ }
1285
+ })(),
1221
1286
  )
1222
1287
  : undefined;
1223
1288
 
@@ -1275,7 +1340,7 @@ export default class FlowMapSelectors<L, F> {
1275
1340
  isFlowInSelection(
1276
1341
  flow: F | AggregateFlow,
1277
1342
  selectedLocationsSet: Set<string> | undefined,
1278
- locationFilterMode: LocationFilterMode,
1343
+ locationFilterMode?: LocationFilterMode,
1279
1344
  ) {
1280
1345
  const origin = this.accessors.getFlowOriginId(flow);
1281
1346
  const dest = this.accessors.getFlowDestId(flow);
@@ -1368,10 +1433,9 @@ function aggregateFlows<F>(
1368
1433
  flowAccessors: FlowAccessors<F>,
1369
1434
  ): AggregateFlow[] {
1370
1435
  // 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[]) => {
1436
+ const byOriginDest = rollup(
1437
+ flows,
1438
+ (ff: F[]) => {
1375
1439
  const origin = flowAccessors.getFlowOriginId(ff[0]);
1376
1440
  const dest = flowAccessors.getFlowDestId(ff[0]);
1377
1441
  // const color = ff[0].color;
@@ -1390,11 +1454,14 @@ function aggregateFlows<F>(
1390
1454
  };
1391
1455
  // if (color) rv.color = color;
1392
1456
  return rv;
1393
- })
1394
- .entries(flows);
1457
+ },
1458
+ flowAccessors.getFlowOriginId,
1459
+ flowAccessors.getFlowDestId,
1460
+ );
1461
+
1395
1462
  const rv: AggregateFlow[] = [];
1396
- for (const {values} of byOriginDest) {
1397
- for (const {value} of values) {
1463
+ for (const values of byOriginDest.values()) {
1464
+ for (const value of values.values()) {
1398
1465
  rv.push(value);
1399
1466
  }
1400
1467
  }
@@ -1414,7 +1481,7 @@ export function getOuterCircleRadiusByIndex(
1414
1481
  return Math.max(getInRadius.value[index], getOutRadius.value[index]);
1415
1482
  }
1416
1483
 
1417
- export function getLocationCentroidByIndex(
1484
+ export function getLocationCoordsByIndex(
1418
1485
  circleAttributes: FlowCirclesLayerAttributes,
1419
1486
  index: number,
1420
1487
  ): [number, number] {