@flowmap.gl/data 8.0.0-alpha.9 → 8.0.1

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/.turbo/turbo-build.log +3 -0
  2. package/.turbo/turbo-dev.log +6 -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 +41 -87
  8. package/dist/FlowmapSelectors.d.ts.map +1 -1
  9. package/dist/FlowmapSelectors.js +174 -161
  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 +25 -5
  17. package/dist/cluster/cluster.d.ts.map +1 -1
  18. package/dist/cluster/cluster.js +115 -57
  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 +3 -3
  23. package/dist/getViewStateForLocations.d.ts.map +1 -1
  24. package/dist/getViewStateForLocations.js +33 -12
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +9 -4
  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 +20 -18
  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 -27
  45. package/src/FlowmapAggregateAccessors.ts +21 -10
  46. package/src/FlowmapSelectors.ts +304 -280
  47. package/src/FlowmapState.ts +13 -5
  48. package/src/cluster/ClusterIndex.ts +23 -28
  49. package/src/cluster/cluster.ts +165 -73
  50. package/src/colors.ts +13 -9
  51. package/src/getViewStateForLocations.ts +23 -7
  52. package/src/index.ts +9 -3
  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 +23 -15
  58. package/src/util.ts +6 -0
  59. package/dist/provider/WorkerFlowmapDataProvider.d.ts +0 -42
  60. package/dist/provider/WorkerFlowmapDataProvider.d.ts.map +0 -1
  61. package/dist/provider/WorkerFlowmapDataProvider.js +0 -80
  62. package/dist/provider/WorkerFlowmapDataProviderWorker.d.ts +0 -2
  63. package/dist/provider/WorkerFlowmapDataProviderWorker.d.ts.map +0 -1
  64. package/dist/provider/WorkerFlowmapDataProviderWorker.js +0 -4
  65. package/dist/provider/createWorkerDataProvider.d.ts +0 -3
  66. package/dist/provider/createWorkerDataProvider.d.ts.map +0 -1
  67. package/dist/provider/createWorkerDataProvider.js +0 -21
  68. package/src/provider/WorkerFlowmapDataProvider.ts +0 -121
  69. package/src/provider/WorkerFlowmapDataProviderWorker.ts +0 -4
  70. package/src/provider/createWorkerDataProvider.ts +0 -18
@@ -1,25 +1,11 @@
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 {
25
11
  createSelector,
@@ -33,6 +19,7 @@ import {
33
19
  buildIndex,
34
20
  ClusterIndex,
35
21
  findAppropriateZoomLevel,
22
+ LocationWeightGetter,
36
23
  makeLocationWeightGetter,
37
24
  } from './cluster/ClusterIndex';
38
25
  import getColors, {
@@ -46,6 +33,11 @@ import getColors, {
46
33
  } from './colors';
47
34
  import FlowmapAggregateAccessors from './FlowmapAggregateAccessors';
48
35
  import {FlowmapState} from './FlowmapState';
36
+ import {
37
+ addClusterNames,
38
+ getFlowThicknessScale,
39
+ getViewportBoundingBox,
40
+ } from './selector-functions';
49
41
  import {
50
42
  getTimeGranularityByKey,
51
43
  getTimeGranularityByOrder,
@@ -55,6 +47,7 @@ import {
55
47
  import {
56
48
  AggregateFlow,
57
49
  Cluster,
50
+ ClusterLevels,
58
51
  ClusterNode,
59
52
  CountByTime,
60
53
  FlowAccessors,
@@ -78,7 +71,10 @@ export type Selector<L, F, T> = ParametricSelector<
78
71
  T
79
72
  >;
80
73
 
81
- export default class FlowmapSelectors<L, F> {
74
+ export default class FlowmapSelectors<
75
+ L extends Record<string, any>,
76
+ F extends Record<string, any>,
77
+ > {
82
78
  accessors: FlowmapAggregateAccessors<L, F>;
83
79
 
84
80
  constructor(accessors: FlowmapDataAccessors<L, F>) {
@@ -90,60 +86,71 @@ export default class FlowmapSelectors<L, F> {
90
86
  this.accessors = new FlowmapAggregateAccessors(accessors);
91
87
  }
92
88
 
93
- getFetchedFlows = (state: FlowmapState, props: FlowmapData<L, F>) =>
89
+ getAggregateAccessors(): FlowmapAggregateAccessors<L, F> {
90
+ return this.accessors;
91
+ }
92
+
93
+ getFlowsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
94
94
  props.flows;
95
- getFetchedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
95
+ getLocationsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
96
96
  props.locations;
97
+ getClusterLevelsFromProps = (
98
+ state: FlowmapState,
99
+ props: FlowmapData<L, F>,
100
+ ) => {
101
+ return props.clusterLevels;
102
+ };
97
103
  getMaxTopFlowsDisplayNum = (state: FlowmapState, props: FlowmapData<L, F>) =>
98
- state.settingsState.maxTopFlowsDisplayNum;
104
+ state.settings.maxTopFlowsDisplayNum;
99
105
  getSelectedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
100
- state.filterState.selectedLocations;
106
+ state.filter?.selectedLocations;
101
107
  getLocationFilterMode = (state: FlowmapState, props: FlowmapData<L, F>) =>
102
- state.filterState.locationFilterMode;
108
+ state.filter?.locationFilterMode;
103
109
  getClusteringEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
104
- state.settingsState.clusteringEnabled;
110
+ state.settings.clusteringEnabled;
105
111
  getLocationTotalsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
106
- state.settingsState.locationTotalsEnabled;
112
+ state.settings.locationTotalsEnabled;
113
+ getLocationLabelsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
114
+ state.settings.locationLabelsEnabled;
107
115
  getZoom = (state: FlowmapState, props: FlowmapData<L, F>) =>
108
116
  state.viewport.zoom;
109
117
  getViewport = (state: FlowmapState, props: FlowmapData<L, F>) =>
110
118
  state.viewport;
111
119
  getSelectedTimeRange = (state: FlowmapState, props: FlowmapData<L, F>) =>
112
- state.filterState.selectedTimeRange;
120
+ state.filter?.selectedTimeRange;
113
121
 
114
122
  getColorScheme: Selector<L, F, string | string[] | undefined> = (
115
123
  state: FlowmapState,
116
124
  props: FlowmapData<L, F>,
117
- ) => state.settingsState.colorScheme;
125
+ ) => state.settings.colorScheme;
118
126
 
119
127
  getDarkMode: Selector<L, F, boolean> = (
120
128
  state: FlowmapState,
121
129
  props: FlowmapData<L, F>,
122
- ) => state.settingsState.darkMode;
130
+ ) => state.settings.darkMode;
123
131
 
124
132
  getFadeEnabled: Selector<L, F, boolean> = (
125
133
  state: FlowmapState,
126
134
  props: FlowmapData<L, F>,
127
- ) => state.settingsState.fadeEnabled;
135
+ ) => state.settings.fadeEnabled;
128
136
 
129
137
  getFadeOpacityEnabled: Selector<L, F, boolean> = (
130
138
  state: FlowmapState,
131
139
  props: FlowmapData<L, F>,
132
- ) => state.settingsState.fadeOpacityEnabled;
140
+ ) => state.settings.fadeOpacityEnabled;
133
141
 
134
142
  getFadeAmount: Selector<L, F, number> = (
135
143
  state: FlowmapState,
136
144
  props: FlowmapData<L, F>,
137
- ) => state.settingsState.fadeAmount;
145
+ ) => state.settings.fadeAmount;
138
146
 
139
147
  getAnimate: Selector<L, F, boolean> = (
140
148
  state: FlowmapState,
141
149
  props: FlowmapData<L, F>,
142
- ) => state.settingsState.animationEnabled;
150
+ ) => state.settings.animationEnabled;
143
151
 
144
- getInvalidLocationIds: Selector<L, F, string[] | undefined> = createSelector(
145
- this.getFetchedLocations,
146
- (locations) => {
152
+ getInvalidLocationIds: Selector<L, F, (string | number)[] | undefined> =
153
+ createSelector(this.getLocationsFromProps, (locations) => {
147
154
  if (!locations) return undefined;
148
155
  const invalid = [];
149
156
  for (const location of locations) {
@@ -155,51 +162,63 @@ export default class FlowmapSelectors<L, F> {
155
162
  }
156
163
  }
157
164
  return invalid.length > 0 ? invalid : undefined;
158
- },
159
- );
165
+ });
160
166
 
161
- getLocations: Selector<L, F, L[] | undefined> = createSelector(
162
- this.getFetchedLocations,
167
+ getLocations: Selector<L, F, Iterable<L> | undefined> = createSelector(
168
+ this.getLocationsFromProps,
163
169
  this.getInvalidLocationIds,
164
170
  (locations, invalidIds) => {
165
171
  if (!locations) return undefined;
166
172
  if (!invalidIds || invalidIds.length === 0) return locations;
167
173
  const invalid = new Set(invalidIds);
168
- return locations.filter(
169
- (location: L) => !invalid.has(this.accessors.getLocationId(location)),
170
- );
174
+ const filtered: L[] = [];
175
+ for (const location of locations) {
176
+ const id = this.accessors.getLocationId(location);
177
+ if (!invalid.has(id)) {
178
+ filtered.push(location);
179
+ }
180
+ }
181
+ return filtered;
171
182
  },
172
183
  );
173
184
 
174
- getLocationIds: Selector<L, F, Set<string> | undefined> = createSelector(
175
- this.getLocations,
176
- (locations) =>
177
- locations
178
- ? new Set(locations.map(this.accessors.getLocationId))
179
- : undefined,
180
- );
185
+ getLocationIds: Selector<L, F, Set<string | number> | undefined> =
186
+ createSelector(this.getLocations, (locations) => {
187
+ if (!locations) return undefined;
188
+ const ids = new Set<string | number>();
189
+ for (const id of locations) {
190
+ ids.add(this.accessors.getLocationId(id));
191
+ }
192
+ return ids;
193
+ });
181
194
 
182
- getSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
195
+ getSelectedLocationsSet: Selector<L, F, Set<string | number> | undefined> =
183
196
  createSelector(this.getSelectedLocations, (ids) =>
184
197
  ids && ids.length > 0 ? new Set(ids) : undefined,
185
198
  );
186
199
 
187
200
  getSortedFlowsForKnownLocations: Selector<L, F, F[] | undefined> =
188
- createSelector(this.getFetchedFlows, this.getLocationIds, (flows, ids) => {
189
- if (!ids || !flows) return undefined;
190
- return flows
191
- .filter(
192
- (flow: F) =>
193
- ids.has(this.accessors.getFlowOriginId(flow)) &&
194
- ids.has(this.accessors.getFlowDestId(flow)),
195
- )
196
- .sort((a: F, b: F) =>
201
+ createSelector(
202
+ this.getFlowsFromProps,
203
+ this.getLocationIds,
204
+ (flows, ids) => {
205
+ if (!ids || !flows) return undefined;
206
+ const filtered = [];
207
+ for (const flow of flows) {
208
+ const srcId = this.accessors.getFlowOriginId(flow);
209
+ const dstId = this.accessors.getFlowDestId(flow);
210
+ if (ids.has(srcId) && ids.has(dstId)) {
211
+ filtered.push(flow);
212
+ }
213
+ }
214
+ return filtered.sort((a: F, b: F) =>
197
215
  descending(
198
216
  Math.abs(this.accessors.getFlowMagnitude(a)),
199
217
  Math.abs(this.accessors.getFlowMagnitude(b)),
200
218
  ),
201
219
  );
202
- });
220
+ },
221
+ );
203
222
 
204
223
  getActualTimeExtent: Selector<L, F, [Date, Date] | undefined> =
205
224
  createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
@@ -271,44 +290,54 @@ export default class FlowmapSelectors<L, F> {
271
290
  },
272
291
  );
273
292
 
274
- getLocationsHavingFlows: Selector<L, F, L[] | undefined> = createSelector(
275
- this.getSortedFlowsForKnownLocations,
276
- this.getLocations,
277
- (flows, locations) => {
278
- if (!locations || !flows) return locations;
279
- const withFlows = new Set();
280
- for (const flow of flows) {
281
- withFlows.add(this.accessors.getFlowOriginId(flow));
282
- withFlows.add(this.accessors.getFlowDestId(flow));
283
- }
284
- return locations.filter((location: L) =>
285
- withFlows.has(this.accessors.getLocationId(location)),
286
- );
287
- },
288
- );
293
+ getLocationsHavingFlows: Selector<L, F, Iterable<L> | undefined> =
294
+ createSelector(
295
+ this.getSortedFlowsForKnownLocations,
296
+ this.getLocations,
297
+ (flows, locations) => {
298
+ if (!locations || !flows) return locations;
299
+ const withFlows = new Set();
300
+ for (const flow of flows) {
301
+ withFlows.add(this.accessors.getFlowOriginId(flow));
302
+ withFlows.add(this.accessors.getFlowDestId(flow));
303
+ }
304
+ const filtered = [];
305
+ for (const location of locations) {
306
+ if (withFlows.has(this.accessors.getLocationId(location))) {
307
+ filtered.push(location);
308
+ }
309
+ }
310
+ return filtered;
311
+ },
312
+ );
289
313
 
290
- getLocationsById: Selector<L, F, Map<string, L> | undefined> = createSelector(
291
- this.getLocationsHavingFlows,
292
- (locations) => {
314
+ getLocationsById: Selector<L, F, Map<string | number, L> | undefined> =
315
+ createSelector(this.getLocationsHavingFlows, (locations) => {
293
316
  if (!locations) return undefined;
294
- return nest<L, L>()
295
- .key((d: L) => this.accessors.getLocationId(d))
296
- .rollup(([d]) => d)
297
- .map(locations) as any as Map<string, L>;
298
- },
299
- );
300
-
301
- getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
302
- this.getLocationsHavingFlows,
303
- this.getLocationsById,
304
- this.getSortedFlowsForKnownLocations,
305
- (locations, locationsById, flows) => {
306
- if (!locations || !locationsById || !flows) return undefined;
317
+ const locationsById = new Map<string | number, L>();
318
+ for (const location of locations) {
319
+ locationsById.set(this.accessors.getLocationId(location), location);
320
+ }
321
+ return locationsById;
322
+ });
307
323
 
324
+ getLocationWeightGetter: Selector<L, F, LocationWeightGetter | undefined> =
325
+ createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
326
+ if (!flows) return undefined;
308
327
  const getLocationWeight = makeLocationWeightGetter(
309
328
  flows,
310
329
  this.accessors.getFlowmapDataAccessors(),
311
330
  );
331
+ return getLocationWeight;
332
+ });
333
+
334
+ getClusterLevels: Selector<L, F, ClusterLevels | undefined> = createSelector(
335
+ this.getClusterLevelsFromProps,
336
+ this.getLocationsHavingFlows,
337
+ this.getLocationWeightGetter,
338
+ (clusterLevelsFromProps, locations, getLocationWeight) => {
339
+ if (clusterLevelsFromProps) return clusterLevelsFromProps;
340
+ if (!locations || !getLocationWeight) return undefined;
312
341
  const clusterLevels = clusterLocations(
313
342
  locations,
314
343
  this.accessors.getFlowmapDataAccessors(),
@@ -317,47 +346,27 @@ export default class FlowmapSelectors<L, F> {
317
346
  maxZoom: MAX_CLUSTER_ZOOM_LEVEL,
318
347
  },
319
348
  );
320
- const clusterIndex = buildIndex<F>(clusterLevels);
321
- const {getLocationName, getLocationClusterName} =
322
- this.accessors.getFlowmapDataAccessors();
323
-
324
- // Adding meaningful names
325
- const getName = (id: string) => {
326
- const loc = locationsById.get(id);
327
- if (loc) {
328
- return getLocationName
329
- ? getLocationName(loc)
330
- : this.accessors.getLocationId(loc) || id;
331
- }
332
- return `"${id}"`;
333
- };
334
- for (const level of clusterLevels) {
335
- for (const node of level.nodes) {
336
- // Here mutating the nodes (adding names)
337
- if (isCluster(node)) {
338
- const leaves = clusterIndex.expandCluster(node);
339
-
340
- leaves.sort((a, b) =>
341
- descending(getLocationWeight(a), getLocationWeight(b)),
342
- );
349
+ return clusterLevels;
350
+ },
351
+ );
343
352
 
344
- if (getLocationClusterName) {
345
- node.name = getLocationClusterName(leaves);
346
- } else {
347
- const topId = leaves[0];
348
- const otherId = leaves.length === 2 ? leaves[1] : undefined;
349
- node.name = `"${getName(topId)}" and ${
350
- otherId
351
- ? `"${getName(otherId)}"`
352
- : `${leaves.length - 1} others`
353
- }`;
354
- }
355
- } else {
356
- (node as any).name = getName(node.id);
357
- }
358
- }
359
- }
353
+ getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
354
+ this.getLocationsById,
355
+ this.getLocationWeightGetter,
356
+ this.getClusterLevels,
357
+ (locationsById, getLocationWeight, clusterLevels) => {
358
+ if (!locationsById || !getLocationWeight || !clusterLevels)
359
+ return undefined;
360
360
 
361
+ const clusterIndex = buildIndex<F>(clusterLevels);
362
+ // Adding meaningful names
363
+ addClusterNames(
364
+ clusterIndex,
365
+ clusterLevels,
366
+ locationsById,
367
+ this.accessors.getFlowmapDataAccessors(),
368
+ getLocationWeight,
369
+ );
361
370
  return clusterIndex;
362
371
  },
363
372
  );
@@ -373,7 +382,7 @@ export default class FlowmapSelectors<L, F> {
373
382
  let maxZoom = Number.POSITIVE_INFINITY;
374
383
  let minZoom = Number.NEGATIVE_INFINITY;
375
384
 
376
- const adjust = (zoneId: string) => {
385
+ const adjust = (zoneId: string | number) => {
377
386
  const cluster = clusterIndex.getClusterById(zoneId);
378
387
  if (cluster) {
379
388
  minZoom = Math.max(minZoom, cluster.zoom);
@@ -402,7 +411,7 @@ export default class FlowmapSelectors<L, F> {
402
411
  this.getAvailableClusterZoomLevels,
403
412
  (clusterIndex, mapZoom, availableClusterZoomLevels) => {
404
413
  if (!clusterIndex) return undefined;
405
- if (!availableClusterZoomLevels) {
414
+ if (!availableClusterZoomLevels || mapZoom == null) {
406
415
  return undefined;
407
416
  }
408
417
 
@@ -415,12 +424,12 @@ export default class FlowmapSelectors<L, F> {
415
424
  );
416
425
 
417
426
  getClusterZoom = (state: FlowmapState, props: FlowmapData<L, F>) => {
418
- const {settingsState} = state;
419
- if (!settingsState.clusteringEnabled) return undefined;
420
- if (settingsState.clusteringAuto || settingsState.clusteringLevel == null) {
427
+ const {settings} = state;
428
+ if (!settings.clusteringEnabled) return undefined;
429
+ if (settings.clusteringAuto || settings.clusteringLevel == null) {
421
430
  return this._getClusterZoom(state, props);
422
431
  }
423
- return settingsState.clusteringLevel;
432
+ return settings.clusteringLevel;
424
433
  };
425
434
 
426
435
  getLocationsForSearchBox: Selector<L, F, (L | Cluster)[] | undefined> =
@@ -438,7 +447,7 @@ export default class FlowmapSelectors<L, F> {
438
447
  clusterIndex,
439
448
  ) => {
440
449
  if (!locations) return undefined;
441
- let result: (L | Cluster)[] = locations;
450
+ let result: (L | Cluster)[] = Array.from(locations);
442
451
  // if (clusteringEnabled) {
443
452
  // if (clusterIndex) {
444
453
  // const zoomItems = clusterIndex.getClusterNodesFor(clusterZoom);
@@ -448,7 +457,7 @@ export default class FlowmapSelectors<L, F> {
448
457
  // }
449
458
  // }
450
459
 
451
- if (result && clusterIndex && selectedLocations) {
460
+ if (clusterIndex && selectedLocations) {
452
461
  const toAppend = [];
453
462
  for (const id of selectedLocations) {
454
463
  const cluster = clusterIndex.getClusterById(id);
@@ -473,13 +482,14 @@ export default class FlowmapSelectors<L, F> {
473
482
  );
474
483
 
475
484
  getDiffMode: Selector<L, F, boolean> = createSelector(
476
- this.getFetchedFlows,
485
+ this.getFlowsFromProps,
477
486
  (flows) => {
478
- if (
479
- flows &&
480
- flows.find((f: F) => this.accessors.getFlowMagnitude(f) < 0)
481
- ) {
482
- return true;
487
+ if (flows) {
488
+ for (const f of flows) {
489
+ if (this.accessors.getFlowMagnitude(f) < 0) {
490
+ return true;
491
+ }
492
+ }
483
493
  }
484
494
  return false;
485
495
  },
@@ -505,27 +515,28 @@ export default class FlowmapSelectors<L, F> {
505
515
  },
506
516
  );
507
517
 
508
- getUnknownLocations: Selector<L, F, Set<string> | undefined> = createSelector(
509
- this.getLocationIds,
510
- this.getFetchedFlows,
511
- this.getSortedFlowsForKnownLocations,
512
- (ids, flows, flowsForKnownLocations) => {
513
- if (!ids || !flows) return undefined;
514
- if (
515
- flowsForKnownLocations &&
516
- flows.length === flowsForKnownLocations.length
517
- )
518
- return undefined;
519
- const missing = new Set<string>();
520
- for (const flow of flows) {
521
- if (!ids.has(this.accessors.getFlowOriginId(flow)))
522
- missing.add(this.accessors.getFlowOriginId(flow));
523
- if (!ids.has(this.accessors.getFlowDestId(flow)))
524
- missing.add(this.accessors.getFlowDestId(flow));
525
- }
526
- return missing;
527
- },
528
- );
518
+ getUnknownLocations: Selector<L, F, Set<string | number> | undefined> =
519
+ createSelector(
520
+ this.getLocationIds,
521
+ this.getFlowsFromProps,
522
+ this.getSortedFlowsForKnownLocations,
523
+ (ids, flows, flowsForKnownLocations) => {
524
+ if (!ids || !flows) return undefined;
525
+ if (
526
+ flowsForKnownLocations
527
+ // && flows.length === flowsForKnownLocations.length
528
+ )
529
+ return undefined;
530
+ const missing = new Set<string | number>();
531
+ for (const flow of flows) {
532
+ if (!ids.has(this.accessors.getFlowOriginId(flow)))
533
+ missing.add(this.accessors.getFlowOriginId(flow));
534
+ if (!ids.has(this.accessors.getFlowDestId(flow)))
535
+ missing.add(this.accessors.getFlowDestId(flow));
536
+ }
537
+ return missing;
538
+ },
539
+ );
529
540
 
530
541
  getSortedAggregatedFilteredFlows: Selector<
531
542
  L,
@@ -566,31 +577,34 @@ export default class FlowmapSelectors<L, F> {
566
577
  },
567
578
  );
568
579
 
569
- getExpandedSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
570
- createSelector(
571
- this.getClusteringEnabled,
572
- this.getSelectedLocationsSet,
573
- this.getClusterIndex,
574
- (clusteringEnabled, selectedLocations, clusterIndex) => {
575
- if (!selectedLocations || !clusterIndex) {
576
- return selectedLocations;
577
- }
580
+ getExpandedSelectedLocationsSet: Selector<
581
+ L,
582
+ F,
583
+ Set<string | number> | undefined
584
+ > = createSelector(
585
+ this.getClusteringEnabled,
586
+ this.getSelectedLocationsSet,
587
+ this.getClusterIndex,
588
+ (clusteringEnabled, selectedLocations, clusterIndex) => {
589
+ if (!selectedLocations || !clusterIndex) {
590
+ return selectedLocations;
591
+ }
578
592
 
579
- const result = new Set<string>();
580
- for (const locationId of selectedLocations) {
581
- const cluster = clusterIndex.getClusterById(locationId);
582
- if (cluster) {
583
- const expanded = clusterIndex.expandCluster(cluster);
584
- for (const id of expanded) {
585
- result.add(id);
586
- }
587
- } else {
588
- result.add(locationId);
593
+ const result = new Set<string | number>();
594
+ for (const locationId of selectedLocations) {
595
+ const cluster = clusterIndex.getClusterById(locationId);
596
+ if (cluster) {
597
+ const expanded = clusterIndex.expandCluster(cluster);
598
+ for (const id of expanded) {
599
+ result.add(id);
589
600
  }
601
+ } else {
602
+ result.add(locationId);
590
603
  }
591
- return result;
592
- },
593
- );
604
+ }
605
+ return result;
606
+ },
607
+ );
594
608
 
595
609
  getTotalCountsByTime: Selector<L, F, CountByTime[] | undefined> =
596
610
  createSelector(
@@ -645,18 +659,10 @@ export default class FlowmapSelectors<L, F> {
645
659
  createSelector(
646
660
  this.getViewport,
647
661
  this.getMaxLocationCircleSize,
648
- (viewport, maxLocationCircleSize) => {
649
- const pad = maxLocationCircleSize;
650
- const bounds = new WebMercatorViewport({
651
- ...viewport,
652
- width: viewport.width + pad * 2,
653
- height: viewport.height + pad * 2,
654
- }).getBounds();
655
- return [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
656
- },
662
+ getViewportBoundingBox,
657
663
  );
658
664
 
659
- getLocationsForZoom: Selector<L, F, L[] | ClusterNode[] | undefined> =
665
+ getLocationsForZoom: Selector<L, F, Iterable<L> | ClusterNode[] | undefined> =
660
666
  createSelector(
661
667
  this.getClusteringEnabled,
662
668
  this.getLocationsHavingFlows,
@@ -671,47 +677,50 @@ export default class FlowmapSelectors<L, F> {
671
677
  },
672
678
  );
673
679
 
674
- getLocationTotals: Selector<L, F, Map<string, LocationTotals> | undefined> =
675
- createSelector(
676
- this.getLocationsForZoom,
677
- this.getSortedAggregatedFilteredFlows,
678
- this.getSelectedLocationsSet,
679
- this.getLocationFilterMode,
680
- (locations, flows, selectedLocationsSet, locationFilterMode) => {
681
- if (!flows) return undefined;
682
- const totals = new Map<string, LocationTotals>();
683
- const add = (
684
- id: string,
685
- d: Partial<LocationTotals>,
686
- ): LocationTotals => {
687
- const rv = totals.get(id) ?? {
688
- incomingCount: 0,
689
- outgoingCount: 0,
690
- internalCount: 0,
691
- };
692
- if (d.incomingCount != null) rv.incomingCount += d.incomingCount;
693
- if (d.outgoingCount != null) rv.outgoingCount += d.outgoingCount;
694
- if (d.internalCount != null) rv.internalCount += d.internalCount;
695
- return rv;
680
+ getLocationTotals: Selector<
681
+ L,
682
+ F,
683
+ Map<string | number, LocationTotals> | undefined
684
+ > = createSelector(
685
+ this.getLocationsForZoom,
686
+ this.getSortedAggregatedFilteredFlows,
687
+ this.getSelectedLocationsSet,
688
+ this.getLocationFilterMode,
689
+ (locations, flows, selectedLocationsSet, locationFilterMode) => {
690
+ if (!flows) return undefined;
691
+ const totals = new Map<string | number, LocationTotals>();
692
+ const add = (
693
+ id: string | number,
694
+ d: Partial<LocationTotals>,
695
+ ): LocationTotals => {
696
+ const rv = totals.get(id) ?? {
697
+ incomingCount: 0,
698
+ outgoingCount: 0,
699
+ internalCount: 0,
696
700
  };
697
- for (const f of flows) {
698
- if (
699
- this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
700
- ) {
701
- const originId = this.accessors.getFlowOriginId(f);
702
- const destId = this.accessors.getFlowDestId(f);
703
- const count = this.accessors.getFlowMagnitude(f);
704
- if (originId === destId) {
705
- totals.set(originId, add(originId, {internalCount: count}));
706
- } else {
707
- totals.set(originId, add(originId, {outgoingCount: count}));
708
- totals.set(destId, add(destId, {incomingCount: count}));
709
- }
701
+ if (d.incomingCount != null) rv.incomingCount += d.incomingCount;
702
+ if (d.outgoingCount != null) rv.outgoingCount += d.outgoingCount;
703
+ if (d.internalCount != null) rv.internalCount += d.internalCount;
704
+ return rv;
705
+ };
706
+ for (const f of flows) {
707
+ if (
708
+ this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
709
+ ) {
710
+ const originId = this.accessors.getFlowOriginId(f);
711
+ const destId = this.accessors.getFlowDestId(f);
712
+ const count = this.accessors.getFlowMagnitude(f);
713
+ if (originId === destId) {
714
+ totals.set(originId, add(originId, {internalCount: count}));
715
+ } else {
716
+ totals.set(originId, add(originId, {outgoingCount: count}));
717
+ totals.set(destId, add(destId, {incomingCount: count}));
710
718
  }
711
719
  }
712
- return totals;
713
- },
714
- );
720
+ }
721
+ return totals;
722
+ },
723
+ );
715
724
 
716
725
  getLocationsTree: Selector<L, F, KDBushTree> = createSelector(
717
726
  this.getLocationsForZoom,
@@ -719,14 +728,20 @@ export default class FlowmapSelectors<L, F> {
719
728
  if (!locations) {
720
729
  return undefined;
721
730
  }
722
- return new KDBush(
723
- // @ts-ignore
724
- locations,
725
- (location: L | ClusterNode) =>
726
- lngX(this.accessors.getLocationLon(location)),
727
- (location: L | ClusterNode) =>
728
- latY(this.accessors.getLocationLat(location)),
729
- );
731
+ const nodes = Array.isArray(locations)
732
+ ? locations
733
+ : Array.from(locations);
734
+ const bush = new KDBush(nodes.length, 64, Float32Array);
735
+ for (let i = 0; i < nodes.length; i++) {
736
+ const node = nodes[i];
737
+ bush.add(
738
+ lngX(this.accessors.getLocationLon(node)),
739
+ latY(this.accessors.getLocationLat(node)),
740
+ );
741
+ }
742
+ bush.finish();
743
+ bush.points = nodes;
744
+ return bush;
730
745
  },
731
746
  );
732
747
 
@@ -745,7 +760,7 @@ export default class FlowmapSelectors<L, F> {
745
760
  },
746
761
  );
747
762
 
748
- getLocationIdsInViewport: Selector<L, F, Set<string> | undefined> =
763
+ getLocationIdsInViewport: Selector<L, F, Set<string | number> | undefined> =
749
764
  createSelectorCreator(
750
765
  defaultMemoize,
751
766
  // @ts-ignore
@@ -817,7 +832,7 @@ export default class FlowmapSelectors<L, F> {
817
832
  state: FlowmapState,
818
833
  props: FlowmapData<L, F>,
819
834
  ): [number, number] | undefined => {
820
- if (state.settingsState.adaptiveScalesEnabled) {
835
+ if (state.settings.adaptiveScalesEnabled) {
821
836
  return this._getLocationTotalsForViewportExtent(state, props);
822
837
  } else {
823
838
  return this._getLocationTotalsExtent(state, props);
@@ -912,7 +927,7 @@ export default class FlowmapSelectors<L, F> {
912
927
  state: FlowmapState,
913
928
  props: FlowmapData<L, F>,
914
929
  ): [number, number] | undefined => {
915
- if (state.settingsState.adaptiveScalesEnabled) {
930
+ if (state.settings.adaptiveScalesEnabled) {
916
931
  return this._getAdaptiveFlowMagnitudeExtent(state, props);
917
932
  } else {
918
933
  return this._getFlowMagnitudeExtent(state, props);
@@ -935,19 +950,7 @@ export default class FlowmapSelectors<L, F> {
935
950
 
936
951
  getFlowThicknessScale = createSelector(
937
952
  this.getFlowMagnitudeExtent,
938
- (magnitudeExtent) => {
939
- if (!magnitudeExtent) return undefined;
940
- return scaleLinear()
941
- .range([0.025, 0.5])
942
- .domain([
943
- 0,
944
- // should support diff mode too
945
- Math.max.apply(
946
- null,
947
- magnitudeExtent.map((x: number | undefined) => Math.abs(x || 0)),
948
- ),
949
- ]);
950
- },
953
+ getFlowThicknessScale,
951
954
  );
952
955
 
953
956
  getCircleSizeScale = createSelector(
@@ -978,7 +981,7 @@ export default class FlowmapSelectors<L, F> {
978
981
  this.getCircleSizeScale,
979
982
  this.getLocationTotals,
980
983
  (circleSizeScale, locationTotals) => {
981
- return (locationId: string) => {
984
+ return (locationId: string | number) => {
982
985
  const total = locationTotals?.get(locationId);
983
986
  if (total && circleSizeScale) {
984
987
  return (
@@ -996,7 +999,7 @@ export default class FlowmapSelectors<L, F> {
996
999
  this.getCircleSizeScale,
997
1000
  this.getLocationTotals,
998
1001
  (circleSizeScale, locationTotals) => {
999
- return (locationId: string) => {
1002
+ return (locationId: string | number) => {
1000
1003
  const total = locationTotals?.get(locationId);
1001
1004
  if (total && circleSizeScale) {
1002
1005
  return (
@@ -1071,6 +1074,15 @@ export default class FlowmapSelectors<L, F> {
1071
1074
  );
1072
1075
  });
1073
1076
 
1077
+ getLocationOrClusterByIdGetter = createSelector(
1078
+ this.getClusterIndex,
1079
+ this.getLocationsById,
1080
+ (clusterIndex, locationsById) => {
1081
+ return (id: string | number) =>
1082
+ clusterIndex?.getClusterById(id) ?? locationsById?.get(id);
1083
+ },
1084
+ );
1085
+
1074
1086
  getLayersData: Selector<L, F, LayersData> = createSelector(
1075
1087
  this.getLocationsForFlowmapLayer,
1076
1088
  this.getFlowsForFlowmapLayer,
@@ -1081,6 +1093,7 @@ export default class FlowmapSelectors<L, F> {
1081
1093
  this.getOutCircleSizeGetter,
1082
1094
  this.getFlowThicknessScale,
1083
1095
  this.getAnimate,
1096
+ this.getLocationLabelsEnabled,
1084
1097
  (
1085
1098
  locations,
1086
1099
  flows,
@@ -1091,6 +1104,7 @@ export default class FlowmapSelectors<L, F> {
1091
1104
  getOutCircleSize,
1092
1105
  flowThicknessScale,
1093
1106
  animationEnabled,
1107
+ locationLabelsEnabled,
1094
1108
  ) => {
1095
1109
  return this._prepareLayersData(
1096
1110
  locations,
@@ -1102,6 +1116,7 @@ export default class FlowmapSelectors<L, F> {
1102
1116
  getOutCircleSize,
1103
1117
  flowThicknessScale,
1104
1118
  animationEnabled,
1119
+ locationLabelsEnabled,
1105
1120
  );
1106
1121
  },
1107
1122
  );
@@ -1115,6 +1130,7 @@ export default class FlowmapSelectors<L, F> {
1115
1130
  const getInCircleSize = this.getInCircleSizeGetter(state, props);
1116
1131
  const getOutCircleSize = this.getOutCircleSizeGetter(state, props);
1117
1132
  const flowThicknessScale = this.getFlowThicknessScale(state, props);
1133
+ const locationLabelsEnabled = this.getLocationLabelsEnabled(state, props);
1118
1134
  return this._prepareLayersData(
1119
1135
  locations,
1120
1136
  flows,
@@ -1124,7 +1140,8 @@ export default class FlowmapSelectors<L, F> {
1124
1140
  getInCircleSize,
1125
1141
  getOutCircleSize,
1126
1142
  flowThicknessScale,
1127
- state.settingsState.animationEnabled,
1143
+ state.settings.animationEnabled,
1144
+ locationLabelsEnabled,
1128
1145
  );
1129
1146
  }
1130
1147
 
@@ -1132,12 +1149,13 @@ export default class FlowmapSelectors<L, F> {
1132
1149
  locations: (L | ClusterNode)[] | undefined,
1133
1150
  flows: (F | AggregateFlow)[] | undefined,
1134
1151
  flowmapColors: DiffColorsRGBA | ColorsRGBA,
1135
- locationsById: Map<string, L | ClusterNode> | undefined,
1136
- locationIdsInViewport: Set<string> | undefined,
1137
- getInCircleSize: (locationId: string) => number,
1138
- getOutCircleSize: (locationId: string) => number,
1152
+ locationsById: Map<string | number, L | ClusterNode> | undefined,
1153
+ locationIdsInViewport: Set<string | number> | undefined,
1154
+ getInCircleSize: (locationId: string | number) => number,
1155
+ getOutCircleSize: (locationId: string | number) => number,
1139
1156
  flowThicknessScale: ScaleLinear<number, number, never> | undefined,
1140
1157
  animationEnabled: boolean,
1158
+ locationLabelsEnabled: boolean,
1141
1159
  ): LayersData {
1142
1160
  if (!locations) locations = [];
1143
1161
  if (!flows) flows = [];
@@ -1148,6 +1166,7 @@ export default class FlowmapSelectors<L, F> {
1148
1166
  getLocationId,
1149
1167
  getLocationLon,
1150
1168
  getLocationLat,
1169
+ getLocationName,
1151
1170
  } = this.accessors;
1152
1171
 
1153
1172
  const flowMagnitudeExtent = extent(flows, (f) => getFlowMagnitude(f)) as [
@@ -1279,6 +1298,9 @@ export default class FlowmapSelectors<L, F> {
1279
1298
  : {}),
1280
1299
  },
1281
1300
  },
1301
+ ...(locationLabelsEnabled
1302
+ ? {locationLabels: locations.map(getLocationName)}
1303
+ : undefined),
1282
1304
  };
1283
1305
  }
1284
1306
 
@@ -1309,8 +1331,8 @@ export default class FlowmapSelectors<L, F> {
1309
1331
 
1310
1332
  isFlowInSelection(
1311
1333
  flow: F | AggregateFlow,
1312
- selectedLocationsSet: Set<string> | undefined,
1313
- locationFilterMode: LocationFilterMode,
1334
+ selectedLocationsSet: Set<string | number> | undefined,
1335
+ locationFilterMode?: LocationFilterMode,
1314
1336
  ) {
1315
1337
  const origin = this.accessors.getFlowOriginId(flow);
1316
1338
  const dest = this.accessors.getFlowDestId(flow);
@@ -1356,8 +1378,8 @@ export default class FlowmapSelectors<L, F> {
1356
1378
  }
1357
1379
 
1358
1380
  function calcLocationTotalsExtent(
1359
- locationTotals: Map<string, LocationTotals> | undefined,
1360
- locationIdsInViewport: Set<string> | undefined,
1381
+ locationTotals: Map<string | number, LocationTotals> | undefined,
1382
+ locationIdsInViewport: Set<string | number> | undefined,
1361
1383
  ) {
1362
1384
  if (!locationTotals) return undefined;
1363
1385
  let rv: [number, number] | undefined = undefined;
@@ -1403,10 +1425,9 @@ function aggregateFlows<F>(
1403
1425
  flowAccessors: FlowAccessors<F>,
1404
1426
  ): AggregateFlow[] {
1405
1427
  // Sum up flows with same origin, dest
1406
- const byOriginDest = nest<F, AggregateFlow>()
1407
- .key(flowAccessors.getFlowOriginId)
1408
- .key(flowAccessors.getFlowDestId)
1409
- .rollup((ff: F[]) => {
1428
+ const byOriginDest = rollup(
1429
+ flows,
1430
+ (ff: F[]) => {
1410
1431
  const origin = flowAccessors.getFlowOriginId(ff[0]);
1411
1432
  const dest = flowAccessors.getFlowDestId(ff[0]);
1412
1433
  // const color = ff[0].color;
@@ -1425,11 +1446,14 @@ function aggregateFlows<F>(
1425
1446
  };
1426
1447
  // if (color) rv.color = color;
1427
1448
  return rv;
1428
- })
1429
- .entries(flows);
1449
+ },
1450
+ flowAccessors.getFlowOriginId,
1451
+ flowAccessors.getFlowDestId,
1452
+ );
1453
+
1430
1454
  const rv: AggregateFlow[] = [];
1431
- for (const {values} of byOriginDest) {
1432
- for (const {value} of values) {
1455
+ for (const values of byOriginDest.values()) {
1456
+ for (const value of values.values()) {
1433
1457
  rv.push(value);
1434
1458
  }
1435
1459
  }