@flowmap.gl/data 8.0.0-y.14 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-dev.log +670 -0
  3. package/LICENSE +2 -2
  4. package/dist/FlowmapAggregateAccessors.d.ts +4 -4
  5. package/dist/FlowmapAggregateAccessors.d.ts.map +1 -1
  6. package/dist/FlowmapAggregateAccessors.js +16 -9
  7. package/dist/FlowmapSelectors.d.ts +39 -85
  8. package/dist/FlowmapSelectors.d.ts.map +1 -1
  9. package/dist/FlowmapSelectors.js +128 -144
  10. package/dist/FlowmapState.d.ts +7 -5
  11. package/dist/FlowmapState.d.ts.map +1 -1
  12. package/dist/FlowmapState.js +6 -1
  13. package/dist/cluster/ClusterIndex.d.ts +4 -4
  14. package/dist/cluster/ClusterIndex.d.ts.map +1 -1
  15. package/dist/cluster/ClusterIndex.js +5 -17
  16. package/dist/cluster/cluster.d.ts +20 -1
  17. package/dist/cluster/cluster.d.ts.map +1 -1
  18. package/dist/cluster/cluster.js +108 -52
  19. package/dist/colors.d.ts +3 -3
  20. package/dist/colors.d.ts.map +1 -1
  21. package/dist/colors.js +19 -8
  22. package/dist/getViewStateForLocations.d.ts +2 -2
  23. package/dist/getViewStateForLocations.d.ts.map +1 -1
  24. package/dist/getViewStateForLocations.js +18 -8
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +9 -1
  28. package/dist/provider/FlowmapDataProvider.d.ts +7 -2
  29. package/dist/provider/FlowmapDataProvider.d.ts.map +1 -1
  30. package/dist/provider/FlowmapDataProvider.js +11 -6
  31. package/dist/provider/LocalFlowmapDataProvider.d.ts +15 -4
  32. package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -1
  33. package/dist/provider/LocalFlowmapDataProvider.js +98 -81
  34. package/dist/selector-functions.d.ts +10 -0
  35. package/dist/selector-functions.d.ts.map +1 -0
  36. package/dist/selector-functions.js +65 -0
  37. package/dist/time.d.ts.map +1 -1
  38. package/dist/time.js +6 -1
  39. package/dist/types.d.ts +18 -16
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/types.js +9 -4
  42. package/dist/util.d.ts.map +1 -1
  43. package/dist/util.js +6 -1
  44. package/package.json +22 -23
  45. package/src/FlowmapAggregateAccessors.ts +21 -10
  46. package/src/FlowmapSelectors.ts +271 -264
  47. package/src/FlowmapState.ts +13 -5
  48. package/src/cluster/ClusterIndex.ts +23 -28
  49. package/src/cluster/cluster.ts +145 -56
  50. package/src/colors.ts +13 -9
  51. package/src/getViewStateForLocations.ts +6 -0
  52. package/src/index.ts +9 -0
  53. package/src/provider/FlowmapDataProvider.ts +23 -7
  54. package/src/provider/LocalFlowmapDataProvider.ts +68 -5
  55. package/src/selector-functions.ts +93 -0
  56. package/src/time.ts +6 -0
  57. package/src/types.ts +21 -13
  58. package/src/util.ts +6 -0
@@ -1,16 +1,24 @@
1
+ /*
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  import {LocationFilterMode, ViewportProps} from './types';
2
8
 
3
9
  export interface FilterState {
4
- selectedLocations: string[] | undefined;
5
- selectedTimeRange: [Date, Date] | undefined;
6
- locationFilterMode: LocationFilterMode;
10
+ selectedLocations?: (string | number)[];
11
+ locationFilterMode?: LocationFilterMode;
12
+ selectedTimeRange?: [Date, Date];
7
13
  }
8
14
 
9
15
  export interface SettingsState {
10
16
  animationEnabled: boolean;
11
17
  fadeEnabled: boolean;
12
18
  fadeOpacityEnabled: boolean;
19
+ locationsEnabled: boolean;
13
20
  locationTotalsEnabled: boolean;
21
+ locationLabelsEnabled: boolean;
14
22
  adaptiveScalesEnabled: boolean;
15
23
  clusteringEnabled: boolean;
16
24
  clusteringAuto: boolean;
@@ -23,7 +31,7 @@ export interface SettingsState {
23
31
  }
24
32
 
25
33
  export interface FlowmapState {
26
- filterState: FilterState;
27
- settingsState: SettingsState;
34
+ filter?: FilterState;
35
+ settings: SettingsState;
28
36
  viewport: ViewportProps;
29
37
  }
@@ -1,19 +1,7 @@
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
7
  import {
@@ -27,14 +15,14 @@ import {
27
15
  } from './../types';
28
16
  import {ascending, bisectLeft, extent} from 'd3-array';
29
17
 
30
- export type LocationWeightGetter = (id: string) => number;
18
+ export type LocationWeightGetter = (id: string | number) => number;
31
19
 
32
20
  /**
33
21
  * A data structure representing the cluster levels for efficient flow aggregation.
34
22
  */
35
23
  export interface ClusterIndex<F> {
36
24
  availableZoomLevels: number[];
37
- getClusterById: (clusterId: string) => Cluster | undefined;
25
+ getClusterById: (clusterId: string | number) => Cluster | undefined;
38
26
  /**
39
27
  * List the nodes on the given zoom level.
40
28
  */
@@ -42,7 +30,7 @@ export interface ClusterIndex<F> {
42
30
  /**
43
31
  * Get the min zoom level on which the location is not clustered.
44
32
  */
45
- getMinZoomForLocation: (locationId: string) => number;
33
+ getMinZoomForLocation: (locationId: string | number) => number;
46
34
  /**
47
35
  * List the IDs of all locations in the cluster (leaves of the subtree starting in the cluster).
48
36
  */
@@ -50,7 +38,10 @@ export interface ClusterIndex<F> {
50
38
  /**
51
39
  * Find the cluster the given location is residing in on the specified zoom level.
52
40
  */
53
- findClusterFor: (locationId: string, zoom: number) => string | undefined;
41
+ findClusterFor: (
42
+ locationId: string | number,
43
+ zoom: number,
44
+ ) => string | number | undefined;
54
45
  /**
55
46
  * Aggregate flows for the specified zoom level.
56
47
  */
@@ -69,8 +60,8 @@ export interface ClusterIndex<F> {
69
60
  */
70
61
  export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
71
62
  const nodesByZoom = new Map<number, ClusterNode[]>();
72
- const clustersById = new Map<string, Cluster>();
73
- const minZoomByLocationId = new Map<string, number>();
63
+ const clustersById = new Map<string | number, Cluster>();
64
+ const minZoomByLocationId = new Map<string | number, number>();
74
65
  for (const {zoom, nodes} of clusterLevels) {
75
66
  nodesByZoom.set(zoom, nodes);
76
67
  for (const node of nodes) {
@@ -91,7 +82,10 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
91
82
  throw new Error('Could not determine minZoom or maxZoom');
92
83
  }
93
84
 
94
- const leavesToClustersByZoom = new Map<number, Map<string, Cluster>>();
85
+ const leavesToClustersByZoom = new Map<
86
+ number,
87
+ Map<string | number, Cluster>
88
+ >();
95
89
 
96
90
  for (const cluster of clustersById.values()) {
97
91
  const {zoom} = cluster;
@@ -118,7 +112,7 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
118
112
 
119
113
  const expandCluster = (cluster: Cluster, targetZoom: number = maxZoom) => {
120
114
  const ids: string[] = [];
121
- const visit = (c: Cluster, expandedIds: string[]) => {
115
+ const visit = (c: Cluster, expandedIds: (string | number)[]) => {
122
116
  if (targetZoom > c.zoom) {
123
117
  for (const childId of c.children) {
124
118
  const child = clustersById.get(childId);
@@ -136,7 +130,7 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
136
130
  return ids;
137
131
  };
138
132
 
139
- function findClusterFor(locationId: string, zoom: number) {
133
+ function findClusterFor(locationId: string | number, zoom: number) {
140
134
  const leavesToClusters = leavesToClustersByZoom.get(zoom);
141
135
  if (!leavesToClusters) {
142
136
  return undefined;
@@ -179,7 +173,8 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
179
173
  }
180
174
  const result: (F | AggregateFlow)[] = [];
181
175
  const aggFlowsByKey = new Map<string, AggregateFlow>();
182
- const makeKey = (origin: string, dest: string) => `${origin}:${dest}`;
176
+ const makeKey = (origin: string | number, dest: string | number) =>
177
+ `${origin}:${dest}`;
183
178
  const {
184
179
  flowCountsMapReduce = {
185
180
  map: getFlowMagnitude,
@@ -223,8 +218,8 @@ export function makeLocationWeightGetter<F>(
223
218
  {getFlowOriginId, getFlowDestId, getFlowMagnitude}: FlowAccessors<F>,
224
219
  ): LocationWeightGetter {
225
220
  const locationTotals = {
226
- incoming: new Map<string, number>(),
227
- outgoing: new Map<string, number>(),
221
+ incoming: new Map<string | number, number>(),
222
+ outgoing: new Map<string | number, number>(),
228
223
  };
229
224
  for (const flow of flows) {
230
225
  const origin = getFlowOriginId(flow);
@@ -239,7 +234,7 @@ export function makeLocationWeightGetter<F>(
239
234
  (locationTotals.outgoing.get(origin) || 0) + count,
240
235
  );
241
236
  }
242
- return (id: string) =>
237
+ return (id: string | number) =>
243
238
  Math.max(
244
239
  Math.abs(locationTotals.incoming.get(id) || 0),
245
240
  Math.abs(locationTotals.outgoing.get(id) || 0),
@@ -1,46 +1,34 @@
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
- *
17
- */
18
-
19
- /**
20
- * The code in this file is a based on https://github.com/mapbox/supercluster
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
21
5
  */
22
6
 
23
- // ISC License
24
- //
25
- // Copyright (c) 2016, Mapbox
26
- //
27
- // Permission to use, copy, modify, and/or distribute this software for any purpose
28
- // with or without fee is hereby granted, provided that the above copyright notice
29
- // and this permission notice appear in all copies.
30
- //
31
- // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
32
- // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
33
- // FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
34
- // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
35
- // OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
36
- // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
37
- // THIS SOFTWARE.
38
-
39
- import {rollup} from 'd3-array';
7
+ import {min, rollup} from 'd3-array';
40
8
  import KDBush from 'kdbush';
41
9
  import {LocationWeightGetter} from './ClusterIndex';
42
10
  import {Cluster, ClusterLevel, ClusterNode, LocationAccessors} from '../types';
43
11
 
12
+ /**
13
+ * The code in this file is a based on https://github.com/mapbox/supercluster
14
+ *
15
+ * ISC License
16
+ *
17
+ * Copyright (c) 2016, Mapbox
18
+ *
19
+ * Permission to use, copy, modify, and/or distribute this software for any purpose
20
+ * with or without fee is hereby granted, provided that the above copyright notice
21
+ * and this permission notice appear in all copies.
22
+ *
23
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
24
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
25
+ * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
26
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
27
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
28
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
29
+ * THIS SOFTWARE.
30
+ */
31
+
44
32
  export interface Options {
45
33
  minZoom: number; // min zoom to generate clusters on
46
34
  maxZoom: number; // max zoom level to cluster the points on
@@ -110,7 +98,7 @@ export function clusterLocations<L>(
110
98
 
111
99
  // generate a cluster object for each point and index input points into a KD-tree
112
100
  let clusters = new Array<Point<L>>();
113
- let i = 0;
101
+ let locationsCount = 0;
114
102
  for (const location of locations) {
115
103
  const x = getLocationLon(location);
116
104
  const y = getLocationLat(location);
@@ -119,41 +107,88 @@ export function clusterLocations<L>(
119
107
  y: latY(y),
120
108
  weight: getLocationWeight(getLocationId(location)),
121
109
  zoom: Infinity, // the last zoom the point was processed at
122
- index: i, // index of the source feature in the original input array,
110
+ index: locationsCount, // index of the source feature in the original input array,
123
111
  parentId: -1, // parent cluster id
124
112
  location,
125
113
  });
126
- i++;
114
+ locationsCount++;
127
115
  }
128
- trees[maxZoom + 1] = new KDBush(clusters, getX, getY, nodeSize, Float32Array);
116
+
117
+ const makeBush = (points: Point<L>[]) => {
118
+ const bush = new KDBush(points.length, nodeSize, Float32Array);
119
+ for (let i = 0; i < points.length; i++) {
120
+ bush.add(points[i].x, points[i].y);
121
+ }
122
+ bush.finish();
123
+ bush.points = points;
124
+ return bush;
125
+ };
129
126
 
130
127
  // cluster points on max zoom, then cluster the results on previous zoom, etc.;
131
128
  // results in a cluster hierarchy across zoom levels
129
+ trees[maxZoom + 1] = makeBush(clusters);
130
+ let prevZoom = maxZoom + 1;
131
+
132
132
  for (let z = maxZoom; z >= minZoom; z--) {
133
133
  // create a new set of clusters for the zoom and index them with a KD-tree
134
- clusters = cluster(clusters, z, trees[z + 1], opts);
135
- trees[z] = new KDBush(clusters, getX, getY, nodeSize, Float32Array);
134
+ const _clusters = cluster(clusters, z, trees[prevZoom], opts);
135
+ if (_clusters.length === clusters.length) {
136
+ // same number of clusters => move the higher level clusters up
137
+ // no need to keep the same data on multiple levels
138
+ trees[z] = trees[prevZoom];
139
+ trees[prevZoom] = undefined;
140
+ prevZoom = z;
141
+ clusters = _clusters;
142
+ } else {
143
+ prevZoom = z;
144
+ clusters = _clusters;
145
+ trees[z] = makeBush(clusters);
146
+ }
136
147
  }
137
148
 
138
149
  if (trees.length === 0) {
139
150
  return [];
140
151
  }
141
- const numbersOfClusters = trees.map((d) => d.points.length);
142
- const maxAvailZoom = numbersOfClusters.indexOf(
143
- numbersOfClusters[numbersOfClusters.length - 1],
144
- );
152
+
153
+ const numbersOfClusters: number[] = trees.map((d) => d?.points.length);
154
+ const minClusters = min(numbersOfClusters.filter((d) => d > 0));
155
+
156
+ let maxAvailZoom =
157
+ findIndexOfMax(numbersOfClusters) ?? numbersOfClusters.length - 1;
158
+
159
+ const numUniqueLocations = countUniqueLocations(locations, locationAccessors);
160
+ if (numUniqueLocations < locationsCount) {
161
+ // Duplicate locations would be clustered together at any zoom level which can lead to having too many zooms.
162
+ // To avoid that, we need to find the max zoom level that has less or equal clusters than unique locations
163
+ // and drop all zoom levels beyond that (except the unclustered level).
164
+ const maxClustersZoom = findLastIndex(
165
+ numbersOfClusters,
166
+ (d) => d <= numUniqueLocations,
167
+ );
168
+ if (maxClustersZoom >= 0) {
169
+ // Now, move the unclustered points to the next zoom level to avoid having a gap
170
+ if (maxClustersZoom < maxAvailZoom) {
171
+ trees[maxClustersZoom + 1] = trees[maxAvailZoom];
172
+ trees.splice(maxClustersZoom + 2); // Remove all zoom levels beyond maxClustersZoom
173
+ }
174
+ maxAvailZoom = maxClustersZoom + 1;
175
+ }
176
+ }
177
+
145
178
  const minAvailZoom = Math.min(
146
179
  maxAvailZoom,
147
- numbersOfClusters.lastIndexOf(numbersOfClusters[0]),
180
+ minClusters ? numbersOfClusters.lastIndexOf(minClusters) : maxAvailZoom,
148
181
  );
149
182
 
150
183
  const clusterLevels = new Array<ClusterLevel>();
151
- for (let zoom = minAvailZoom; zoom <= maxAvailZoom; zoom++) {
152
- let childrenByParent: Map<number, string[]> | undefined;
184
+ prevZoom = NaN;
185
+ for (let zoom = maxAvailZoom; zoom >= minAvailZoom; zoom--) {
186
+ let childrenByParent: Map<number, (string | number)[]> | undefined;
153
187
  const tree = trees[zoom];
154
- if (zoom < maxAvailZoom) {
155
- childrenByParent = rollup<Point<L>, string[], number>(
156
- trees[zoom + 1].points,
188
+ if (!tree) continue;
189
+ if (trees[prevZoom] && zoom < maxAvailZoom) {
190
+ childrenByParent = rollup(
191
+ trees[prevZoom].points,
157
192
  (points: any[]) =>
158
193
  points.map((p: any) =>
159
194
  p.id ? makeClusterId(p.id) : getLocationId(p.location),
@@ -176,22 +211,26 @@ export function clusterLocations<L>(
176
211
  const {id} = point;
177
212
  const children = childrenByParent && childrenByParent.get(id);
178
213
  if (!children) {
179
- throw new Error(`Cluster ${id} doesn't have children`);
214
+ // Might happen if there are multiple locations with same coordinates
215
+ console.warn(`Omitting cluster with no children, point:`, point);
216
+ continue;
180
217
  }
181
- nodes.push({
218
+ const cluster = {
182
219
  id: makeClusterId(id),
183
220
  name: makeClusterName(id, numPoints),
184
221
  zoom,
185
222
  lat: yLat(y),
186
223
  lon: xLng(x),
187
- children,
188
- } as Cluster);
224
+ children: children ?? [],
225
+ } as Cluster;
226
+ nodes.push(cluster);
189
227
  }
190
228
  }
191
229
  clusterLevels.push({
192
230
  zoom,
193
231
  nodes,
194
232
  });
233
+ prevZoom = zoom;
195
234
  }
196
235
  return clusterLevels;
197
236
  }
@@ -303,3 +342,53 @@ function getX<L>(p: Point<L>) {
303
342
  function getY<L>(p: Point<L>) {
304
343
  return p.y;
305
344
  }
345
+
346
+ function countUniqueLocations<L>(
347
+ locations: Iterable<L>,
348
+ locationAccessors: LocationAccessors<L>,
349
+ ) {
350
+ const {getLocationLon, getLocationLat} = locationAccessors;
351
+ const countByLatLon = new Map<string, number>();
352
+ let uniqueCnt = 0;
353
+ for (const loc of locations) {
354
+ const lon = getLocationLon(loc);
355
+ const lat = getLocationLat(loc);
356
+ const key = `${lon},${lat}`;
357
+ const prev = countByLatLon.get(key);
358
+ if (!prev) {
359
+ uniqueCnt++;
360
+ }
361
+ countByLatLon.set(key, prev ? prev + 1 : 1);
362
+ }
363
+ return uniqueCnt;
364
+ }
365
+
366
+ function findIndexOfMax(arr: (number | undefined)[]): number | undefined {
367
+ let max = -Infinity;
368
+ let maxIndex: number | undefined = undefined;
369
+
370
+ for (let i = 0; i < arr.length; i++) {
371
+ const value = arr[i];
372
+
373
+ if (typeof value === 'number') {
374
+ if (value > max) {
375
+ max = value;
376
+ maxIndex = i;
377
+ }
378
+ }
379
+ }
380
+
381
+ return maxIndex;
382
+ }
383
+
384
+ function findLastIndex<T>(
385
+ arr: T[],
386
+ predicate: (value: T, index: number, array: T[]) => boolean,
387
+ ): number {
388
+ for (let i = arr.length - 1; i >= 0; i--) {
389
+ if (predicate(arr[i], i, arr)) {
390
+ return i;
391
+ }
392
+ }
393
+ return -1;
394
+ }
package/src/colors.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /*
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  import {
2
8
  interpolateCool,
3
9
  interpolateInferno,
@@ -333,17 +339,15 @@ const diffColors: DiffColors = {
333
339
  outlineColor: 'rgb(230,233,237)',
334
340
  };
335
341
 
336
- export function getFlowmapColors(
337
- settingsState: SettingsState,
338
- ): Colors | DiffColors {
342
+ export function getFlowmapColors(settings: SettingsState): Colors | DiffColors {
339
343
  return getColors(
340
344
  false, // TODO: diffMode
341
- settingsState.colorScheme,
342
- settingsState.darkMode,
343
- settingsState.fadeEnabled,
344
- settingsState.fadeOpacityEnabled,
345
- settingsState.fadeAmount,
346
- settingsState.animationEnabled,
345
+ settings.colorScheme,
346
+ settings.darkMode,
347
+ settings.fadeEnabled,
348
+ settings.fadeOpacityEnabled,
349
+ settings.fadeAmount,
350
+ settings.animationEnabled,
347
351
  );
348
352
  }
349
353
 
@@ -1,3 +1,9 @@
1
+ /*
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  import {geoBounds} from 'd3-geo';
2
8
  import {fitBounds} from '@math.gl/web-mercator';
3
9
  import type {
package/src/index.ts CHANGED
@@ -1,10 +1,19 @@
1
+ /*
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  export * from './types';
2
8
  export * from './colors';
3
9
  export * from './FlowmapState';
4
10
  export * from './FlowmapSelectors';
11
+ export * from './selector-functions';
5
12
  export * from './time';
6
13
  export * from './getViewStateForLocations';
7
14
  export * from './provider/FlowmapDataProvider';
15
+ export * from './cluster/cluster';
16
+ export * from './cluster/ClusterIndex';
8
17
  export {default as FlowmapAggregateAccessors} from './FlowmapAggregateAccessors';
9
18
  export type {default as FlowmapDataProvider} from './provider/FlowmapDataProvider';
10
19
  export {default as LocalFlowmapDataProvider} from './provider/LocalFlowmapDataProvider';
@@ -1,3 +1,9 @@
1
+ /*
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  import {AggregateFlow, Cluster, LocationAccessors, LocationTotals} from '..';
2
8
  import {FlowmapState} from '../FlowmapState';
3
9
  import {
@@ -23,11 +29,13 @@ export default interface FlowmapDataProvider<L, F> {
23
29
 
24
30
  getFlowByIndex(index: number): Promise<F | AggregateFlow | undefined>;
25
31
 
26
- getLocationById(id: string): Promise<L | Cluster | undefined>;
32
+ getLocationById(id: string | number): Promise<L | Cluster | undefined>;
27
33
 
28
34
  getLocationByIndex(idx: number): Promise<L | ClusterNode | undefined>;
29
35
 
30
- getTotalsForLocation(id: string): Promise<LocationTotals | undefined>;
36
+ getTotalsForLocation(
37
+ id: string | number,
38
+ ): Promise<LocationTotals | undefined>;
31
39
 
32
40
  // getLocationsInBbox(
33
41
  // bbox: [number, number, number, number],
@@ -36,17 +44,25 @@ export default interface FlowmapDataProvider<L, F> {
36
44
  // getLocationsForSearchBox(): Promise<(FlowLocation | ClusterNode)[] | undefined>;
37
45
 
38
46
  getLayersData(): Promise<LayersData | undefined>;
47
+
48
+ /**
49
+ * This is to give the data provider control over when/how often layersData
50
+ * is updated which leads to the flowmap being redrawn.
51
+ */
52
+ updateLayersData(
53
+ setLayersData: (layersData: LayersData | undefined) => void,
54
+ changeFlags: Record<string, boolean>,
55
+ ): Promise<void>;
39
56
  }
40
57
 
41
58
  export function isFlowmapData<L, F>(
42
59
  data: Record<string, any>,
43
60
  ): data is FlowmapData<L, F> {
44
61
  return (
45
- data &&
46
- data.locations &&
47
- data.flows &&
48
- Array.isArray(data.locations) &&
49
- Array.isArray(data.flows)
62
+ data && data.locations && data.flows
63
+ // TODO: test that they are iterable
64
+ // Array.isArray(data.locations) &&
65
+ // Array.isArray(data.flows)
50
66
  );
51
67
  }
52
68
 
@@ -1,3 +1,9 @@
1
+ /*
2
+ * Copyright (c) Flowmap.gl contributors
3
+ * Copyright (c) 2018-2020 Teralytics
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  import type FlowmapDataProvider from './FlowmapDataProvider';
2
8
  import type {
3
9
  Cluster,
@@ -15,9 +21,12 @@ import {
15
21
  GetViewStateOptions,
16
22
  getViewStateForLocations,
17
23
  } from '../getViewStateForLocations';
24
+ import {ClusterIndex} from '../cluster/ClusterIndex';
18
25
 
19
- export default class LocalFlowmapDataProvider<L, F>
20
- implements FlowmapDataProvider<L, F>
26
+ export default class LocalFlowmapDataProvider<
27
+ L extends Record<string, any>,
28
+ F extends Record<string, any>,
29
+ > implements FlowmapDataProvider<L, F>
21
30
  {
22
31
  private selectors: FlowmapSelectors<L, F>;
23
32
  private flowmapData: FlowmapData<L, F> | undefined;
@@ -34,14 +43,26 @@ export default class LocalFlowmapDataProvider<L, F>
34
43
  this.selectors.setAccessors(accessors);
35
44
  }
36
45
 
37
- async setFlowmapData(flowmapData: FlowmapData<L, F>): Promise<void> {
46
+ setFlowmapData(flowmapData: FlowmapData<L, F>): void {
38
47
  this.flowmapData = flowmapData;
39
48
  }
40
49
 
50
+ getSelectors(): FlowmapSelectors<L, F> {
51
+ return this.selectors;
52
+ }
53
+
54
+ getFlowmapData(): FlowmapData<L, F> | undefined {
55
+ return this.flowmapData;
56
+ }
57
+
41
58
  async setFlowmapState(flowmapState: FlowmapState): Promise<void> {
42
59
  this.flowmapState = flowmapState;
43
60
  }
44
61
 
62
+ getFlowmapState(): FlowmapState | undefined {
63
+ return this.flowmapState;
64
+ }
65
+
45
66
  async getFlowByIndex(idx: number): Promise<F | AggregateFlow | undefined> {
46
67
  if (!this.flowmapState || !this.flowmapData) {
47
68
  return undefined;
@@ -53,6 +74,7 @@ export default class LocalFlowmapDataProvider<L, F>
53
74
  return flows?.[idx];
54
75
  }
55
76
 
77
+ // TODO: this is unreliable, should replace by unqiue ID
56
78
  async getLocationByIndex(idx: number): Promise<L | ClusterNode | undefined> {
57
79
  if (!this.flowmapState || !this.flowmapData) {
58
80
  return undefined;
@@ -71,7 +93,7 @@ export default class LocalFlowmapDataProvider<L, F>
71
93
  return this.selectors.getLayersData(this.flowmapState, this.flowmapData);
72
94
  }
73
95
 
74
- async getLocationById(id: string): Promise<L | Cluster | undefined> {
96
+ async getLocationById(id: string | number): Promise<L | Cluster | undefined> {
75
97
  if (!this.flowmapState || !this.flowmapData) {
76
98
  return undefined;
77
99
  }
@@ -92,7 +114,9 @@ export default class LocalFlowmapDataProvider<L, F>
92
114
  return locationsById?.get(id);
93
115
  }
94
116
 
95
- async getTotalsForLocation(id: string): Promise<LocationTotals | undefined> {
117
+ async getTotalsForLocation(
118
+ id: string | number,
119
+ ): Promise<LocationTotals | undefined> {
96
120
  if (!this.flowmapState || !this.flowmapData) {
97
121
  return undefined;
98
122
  }
@@ -119,4 +143,43 @@ export default class LocalFlowmapDataProvider<L, F>
119
143
  opts,
120
144
  );
121
145
  }
146
+
147
+ async updateLayersData(
148
+ setLayersData: (layersData: LayersData | undefined) => void,
149
+ ) {
150
+ setLayersData(await this.getLayersData());
151
+ }
152
+
153
+ getClusterZoom(): number | undefined {
154
+ return this.flowmapState && this.flowmapData
155
+ ? this.selectors.getClusterZoom(this.flowmapState, this.flowmapData)
156
+ : undefined;
157
+ }
158
+
159
+ getClusterIndex(): ClusterIndex<F> | undefined {
160
+ return this.flowmapState && this.flowmapData
161
+ ? this.selectors.getClusterIndex(this.flowmapState, this.flowmapData)
162
+ : undefined;
163
+ }
164
+
165
+ getLocationsById(): Map<string | number, L> | undefined {
166
+ return this.flowmapState && this.flowmapData
167
+ ? this.selectors.getLocationsById(this.flowmapState, this.flowmapData)
168
+ : undefined;
169
+ }
170
+
171
+ getLocationTotals(): Map<string | number, LocationTotals> | undefined {
172
+ return this.flowmapState && this.flowmapData
173
+ ? this.selectors.getLocationTotals(this.flowmapState, this.flowmapData)
174
+ : undefined;
175
+ }
176
+
177
+ getFlowsForFlowmapLayer(): Array<F | AggregateFlow> | undefined {
178
+ return this.flowmapState && this.flowmapData
179
+ ? this.selectors.getFlowsForFlowmapLayer(
180
+ this.flowmapState,
181
+ this.flowmapData,
182
+ )
183
+ : undefined;
184
+ }
122
185
  }