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

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 (35) hide show
  1. package/dist/FlowmapAggregateAccessors.d.ts +3 -3
  2. package/dist/FlowmapAggregateAccessors.d.ts.map +1 -1
  3. package/dist/FlowmapAggregateAccessors.js +11 -9
  4. package/dist/FlowmapSelectors.d.ts +26 -11
  5. package/dist/FlowmapSelectors.d.ts.map +1 -1
  6. package/dist/FlowmapSelectors.js +22 -46
  7. package/dist/FlowmapState.d.ts +1 -0
  8. package/dist/FlowmapState.d.ts.map +1 -1
  9. package/dist/FlowmapState.js +1 -1
  10. package/dist/cluster/ClusterIndex.d.ts +3 -3
  11. package/dist/cluster/ClusterIndex.d.ts.map +1 -1
  12. package/dist/cluster/ClusterIndex.js +1 -1
  13. package/dist/cluster/cluster.js +1 -1
  14. package/dist/provider/FlowmapDataProvider.d.ts +2 -2
  15. package/dist/provider/FlowmapDataProvider.d.ts.map +1 -1
  16. package/dist/provider/FlowmapDataProvider.js +1 -1
  17. package/dist/provider/LocalFlowmapDataProvider.d.ts +13 -3
  18. package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -1
  19. package/dist/provider/LocalFlowmapDataProvider.js +36 -4
  20. package/dist/selector-functions.d.ts +7 -1
  21. package/dist/selector-functions.d.ts.map +1 -1
  22. package/dist/selector-functions.js +37 -1
  23. package/dist/types.d.ts +8 -7
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/types.js +1 -1
  26. package/package.json +2 -2
  27. package/src/FlowmapAggregateAccessors.ts +11 -9
  28. package/src/FlowmapSelectors.ts +117 -125
  29. package/src/FlowmapState.ts +1 -0
  30. package/src/cluster/ClusterIndex.ts +19 -12
  31. package/src/cluster/cluster.ts +2 -2
  32. package/src/provider/FlowmapDataProvider.ts +4 -2
  33. package/src/provider/LocalFlowmapDataProvider.ts +51 -3
  34. package/src/selector-functions.ts +54 -1
  35. package/src/types.ts +8 -7
@@ -27,14 +27,14 @@ import {
27
27
  } from './../types';
28
28
  import {ascending, bisectLeft, extent} from 'd3-array';
29
29
 
30
- export type LocationWeightGetter = (id: string) => number;
30
+ export type LocationWeightGetter = (id: string | number) => number;
31
31
 
32
32
  /**
33
33
  * A data structure representing the cluster levels for efficient flow aggregation.
34
34
  */
35
35
  export interface ClusterIndex<F> {
36
36
  availableZoomLevels: number[];
37
- getClusterById: (clusterId: string) => Cluster | undefined;
37
+ getClusterById: (clusterId: string | number) => Cluster | undefined;
38
38
  /**
39
39
  * List the nodes on the given zoom level.
40
40
  */
@@ -50,7 +50,10 @@ export interface ClusterIndex<F> {
50
50
  /**
51
51
  * Find the cluster the given location is residing in on the specified zoom level.
52
52
  */
53
- findClusterFor: (locationId: string, zoom: number) => string | undefined;
53
+ findClusterFor: (
54
+ locationId: string | number,
55
+ zoom: number,
56
+ ) => string | number | undefined;
54
57
  /**
55
58
  * Aggregate flows for the specified zoom level.
56
59
  */
@@ -69,8 +72,8 @@ export interface ClusterIndex<F> {
69
72
  */
70
73
  export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
71
74
  const nodesByZoom = new Map<number, ClusterNode[]>();
72
- const clustersById = new Map<string, Cluster>();
73
- const minZoomByLocationId = new Map<string, number>();
75
+ const clustersById = new Map<string | number, Cluster>();
76
+ const minZoomByLocationId = new Map<string | number, number>();
74
77
  for (const {zoom, nodes} of clusterLevels) {
75
78
  nodesByZoom.set(zoom, nodes);
76
79
  for (const node of nodes) {
@@ -91,7 +94,10 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
91
94
  throw new Error('Could not determine minZoom or maxZoom');
92
95
  }
93
96
 
94
- const leavesToClustersByZoom = new Map<number, Map<string, Cluster>>();
97
+ const leavesToClustersByZoom = new Map<
98
+ number,
99
+ Map<string | number, Cluster>
100
+ >();
95
101
 
96
102
  for (const cluster of clustersById.values()) {
97
103
  const {zoom} = cluster;
@@ -118,7 +124,7 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
118
124
 
119
125
  const expandCluster = (cluster: Cluster, targetZoom: number = maxZoom) => {
120
126
  const ids: string[] = [];
121
- const visit = (c: Cluster, expandedIds: string[]) => {
127
+ const visit = (c: Cluster, expandedIds: (string | number)[]) => {
122
128
  if (targetZoom > c.zoom) {
123
129
  for (const childId of c.children) {
124
130
  const child = clustersById.get(childId);
@@ -136,7 +142,7 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
136
142
  return ids;
137
143
  };
138
144
 
139
- function findClusterFor(locationId: string, zoom: number) {
145
+ function findClusterFor(locationId: string | number, zoom: number) {
140
146
  const leavesToClusters = leavesToClustersByZoom.get(zoom);
141
147
  if (!leavesToClusters) {
142
148
  return undefined;
@@ -179,7 +185,8 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
179
185
  }
180
186
  const result: (F | AggregateFlow)[] = [];
181
187
  const aggFlowsByKey = new Map<string, AggregateFlow>();
182
- const makeKey = (origin: string, dest: string) => `${origin}:${dest}`;
188
+ const makeKey = (origin: string | number, dest: string | number) =>
189
+ `${origin}:${dest}`;
183
190
  const {
184
191
  flowCountsMapReduce = {
185
192
  map: getFlowMagnitude,
@@ -223,8 +230,8 @@ export function makeLocationWeightGetter<F>(
223
230
  {getFlowOriginId, getFlowDestId, getFlowMagnitude}: FlowAccessors<F>,
224
231
  ): LocationWeightGetter {
225
232
  const locationTotals = {
226
- incoming: new Map<string, number>(),
227
- outgoing: new Map<string, number>(),
233
+ incoming: new Map<string | number, number>(),
234
+ outgoing: new Map<string | number, number>(),
228
235
  };
229
236
  for (const flow of flows) {
230
237
  const origin = getFlowOriginId(flow);
@@ -239,7 +246,7 @@ export function makeLocationWeightGetter<F>(
239
246
  (locationTotals.outgoing.get(origin) || 0) + count,
240
247
  );
241
248
  }
242
- return (id: string) =>
249
+ return (id: string | number) =>
243
250
  Math.max(
244
251
  Math.abs(locationTotals.incoming.get(id) || 0),
245
252
  Math.abs(locationTotals.outgoing.get(id) || 0),
@@ -172,11 +172,11 @@ export function clusterLocations<L>(
172
172
  const clusterLevels = new Array<ClusterLevel>();
173
173
  prevZoom = NaN;
174
174
  for (let zoom = maxAvailZoom; zoom >= minAvailZoom; zoom--) {
175
- let childrenByParent: Map<number, string[]> | undefined;
175
+ let childrenByParent: Map<number, (string | number)[]> | undefined;
176
176
  const tree = trees[zoom];
177
177
  if (!tree) continue;
178
178
  if (zoom < maxAvailZoom) {
179
- childrenByParent = rollup<Point<L>, string[], number>(
179
+ childrenByParent = rollup<Point<L>, (string | number)[], number>(
180
180
  trees[prevZoom].points,
181
181
  (points: any[]) =>
182
182
  points.map((p: any) =>
@@ -23,11 +23,13 @@ export default interface FlowmapDataProvider<L, F> {
23
23
 
24
24
  getFlowByIndex(index: number): Promise<F | AggregateFlow | undefined>;
25
25
 
26
- getLocationById(id: string): Promise<L | Cluster | undefined>;
26
+ getLocationById(id: string | number): Promise<L | Cluster | undefined>;
27
27
 
28
28
  getLocationByIndex(idx: number): Promise<L | ClusterNode | undefined>;
29
29
 
30
- getTotalsForLocation(id: string): Promise<LocationTotals | undefined>;
30
+ getTotalsForLocation(
31
+ id: string | number,
32
+ ): Promise<LocationTotals | undefined>;
31
33
 
32
34
  // getLocationsInBbox(
33
35
  // bbox: [number, number, number, number],
@@ -15,6 +15,7 @@ import {
15
15
  GetViewStateOptions,
16
16
  getViewStateForLocations,
17
17
  } from '../getViewStateForLocations';
18
+ import {ClusterIndex} from '../cluster/ClusterIndex';
18
19
 
19
20
  export default class LocalFlowmapDataProvider<L, F>
20
21
  implements FlowmapDataProvider<L, F>
@@ -34,14 +35,26 @@ export default class LocalFlowmapDataProvider<L, F>
34
35
  this.selectors.setAccessors(accessors);
35
36
  }
36
37
 
37
- async setFlowmapData(flowmapData: FlowmapData<L, F>): Promise<void> {
38
+ setFlowmapData(flowmapData: FlowmapData<L, F>): void {
38
39
  this.flowmapData = flowmapData;
39
40
  }
40
41
 
42
+ getSelectors(): FlowmapSelectors<L, F> {
43
+ return this.selectors;
44
+ }
45
+
46
+ getFlowmapData(): FlowmapData<L, F> | undefined {
47
+ return this.flowmapData;
48
+ }
49
+
41
50
  async setFlowmapState(flowmapState: FlowmapState): Promise<void> {
42
51
  this.flowmapState = flowmapState;
43
52
  }
44
53
 
54
+ getFlowmapState(): FlowmapState | undefined {
55
+ return this.flowmapState;
56
+ }
57
+
45
58
  async getFlowByIndex(idx: number): Promise<F | AggregateFlow | undefined> {
46
59
  if (!this.flowmapState || !this.flowmapData) {
47
60
  return undefined;
@@ -72,7 +85,7 @@ export default class LocalFlowmapDataProvider<L, F>
72
85
  return this.selectors.getLayersData(this.flowmapState, this.flowmapData);
73
86
  }
74
87
 
75
- async getLocationById(id: string): Promise<L | Cluster | undefined> {
88
+ async getLocationById(id: string | number): Promise<L | Cluster | undefined> {
76
89
  if (!this.flowmapState || !this.flowmapData) {
77
90
  return undefined;
78
91
  }
@@ -93,7 +106,9 @@ export default class LocalFlowmapDataProvider<L, F>
93
106
  return locationsById?.get(id);
94
107
  }
95
108
 
96
- async getTotalsForLocation(id: string): Promise<LocationTotals | undefined> {
109
+ async getTotalsForLocation(
110
+ id: string | number,
111
+ ): Promise<LocationTotals | undefined> {
97
112
  if (!this.flowmapState || !this.flowmapData) {
98
113
  return undefined;
99
114
  }
@@ -126,4 +141,37 @@ export default class LocalFlowmapDataProvider<L, F>
126
141
  ) {
127
142
  setLayersData(await this.getLayersData());
128
143
  }
144
+
145
+ getClusterZoom(): number | undefined {
146
+ return this.flowmapState && this.flowmapData
147
+ ? this.selectors.getClusterZoom(this.flowmapState, this.flowmapData)
148
+ : undefined;
149
+ }
150
+
151
+ getClusterIndex(): ClusterIndex<F> | undefined {
152
+ return this.flowmapState && this.flowmapData
153
+ ? this.selectors.getClusterIndex(this.flowmapState, this.flowmapData)
154
+ : undefined;
155
+ }
156
+
157
+ getLocationsById(): Map<string | number, L> | undefined {
158
+ return this.flowmapState && this.flowmapData
159
+ ? this.selectors.getLocationsById(this.flowmapState, this.flowmapData)
160
+ : undefined;
161
+ }
162
+
163
+ getLocationTotals(): Map<string | number, LocationTotals> | undefined {
164
+ return this.flowmapState && this.flowmapData
165
+ ? this.selectors.getLocationTotals(this.flowmapState, this.flowmapData)
166
+ : undefined;
167
+ }
168
+
169
+ getFlowsForFlowmapLayer(): Array<F | AggregateFlow> | undefined {
170
+ return this.flowmapState && this.flowmapData
171
+ ? this.selectors.getFlowsForFlowmapLayer(
172
+ this.flowmapState,
173
+ this.flowmapData,
174
+ )
175
+ : undefined;
176
+ }
129
177
  }
@@ -1,6 +1,13 @@
1
1
  import {WebMercatorViewport} from '@math.gl/web-mercator';
2
- import {ViewportProps} from './types';
2
+ import {
3
+ ClusterLevel,
4
+ isCluster,
5
+ LocationAccessors,
6
+ ViewportProps,
7
+ } from './types';
3
8
  import {scaleLinear} from 'd3-scale';
9
+ import {ClusterIndex, LocationWeightGetter} from './cluster/ClusterIndex';
10
+ import {descending} from 'd3-array';
4
11
 
5
12
  // TODO: use re-reselect
6
13
 
@@ -32,3 +39,49 @@ export const getFlowThicknessScale = (
32
39
  ),
33
40
  ]);
34
41
  };
42
+
43
+ /**
44
+ * Adding meaningful cluster names.
45
+ * NOTE: this will mutate the nodes in clusterIndex
46
+ */
47
+ export function addClusterNames<L, F>(
48
+ clusterIndex: ClusterIndex<F>,
49
+ clusterLevels: ClusterLevel[],
50
+ locationsById: Map<string | number, L>,
51
+ locationAccessors: LocationAccessors<L>,
52
+ getLocationWeight: LocationWeightGetter,
53
+ ): void {
54
+ const {getLocationId, getLocationName, getLocationClusterName} =
55
+ locationAccessors;
56
+ const getName = (id: string | number) => {
57
+ const loc = locationsById.get(id);
58
+ if (loc) {
59
+ return getLocationName ? getLocationName(loc) : getLocationId(loc) || id;
60
+ }
61
+ return `"${id}"`;
62
+ };
63
+ for (const level of clusterLevels) {
64
+ for (const node of level.nodes) {
65
+ // Here mutating the nodes (adding names)
66
+ if (isCluster(node)) {
67
+ const leaves = clusterIndex.expandCluster(node);
68
+
69
+ leaves.sort((a, b) =>
70
+ descending(getLocationWeight(a), getLocationWeight(b)),
71
+ );
72
+
73
+ if (getLocationClusterName) {
74
+ node.name = getLocationClusterName(leaves);
75
+ } else {
76
+ const topId = leaves[0];
77
+ const otherId = leaves.length === 2 ? leaves[1] : undefined;
78
+ node.name = `"${getName(topId)}" and ${
79
+ otherId ? `"${getName(otherId)}"` : `${leaves.length - 1} others`
80
+ }`;
81
+ }
82
+ } else {
83
+ (node as any).name = getName(node.id);
84
+ }
85
+ }
86
+ }
87
+ }
package/src/types.ts CHANGED
@@ -17,19 +17,19 @@ export type FlowAccessor<F, T> = (flow: F) => T; // objectInfo?: AccessorObjectI
17
17
  export type LocationAccessor<L, T> = (location: L) => T;
18
18
 
19
19
  export interface FlowAccessors<F> {
20
- getFlowOriginId: FlowAccessor<F, string>;
21
- getFlowDestId: FlowAccessor<F, string>;
20
+ getFlowOriginId: FlowAccessor<F, string | number>;
21
+ getFlowDestId: FlowAccessor<F, string | number>;
22
22
  getFlowMagnitude: FlowAccessor<F, number>;
23
23
  getFlowTime?: FlowAccessor<F, Date>; // TODO: use number instead of Date
24
24
  // getFlowColor?: FlowAccessor<string | undefined>;
25
25
  }
26
26
 
27
27
  export interface LocationAccessors<L> {
28
- getLocationId: LocationAccessor<L, string>;
28
+ getLocationId: LocationAccessor<L, string | number>;
29
29
  getLocationName?: LocationAccessor<L, string>;
30
30
  getLocationLat: LocationAccessor<L, number>;
31
31
  getLocationLon: LocationAccessor<L, number>;
32
- getLocationClusterName?: (locationIds: string[]) => string;
32
+ getLocationClusterName?: (locationIds: (string | number)[]) => string;
33
33
  // getLocationTotalIn?: LocationAccessor<number>;
34
34
  // getLocationTotalOut?: LocationAccessor<number>;
35
35
  // getLocationTotalInternal?: LocationAccessor<number>;
@@ -75,7 +75,7 @@ export interface ViewportProps {
75
75
  }
76
76
 
77
77
  export interface ClusterNode {
78
- id: string;
78
+ id: string | number;
79
79
  zoom: number;
80
80
  lat: number;
81
81
  lon: number;
@@ -105,8 +105,8 @@ export function isLocationClusterNode<L>(l: L | ClusterNode): l is ClusterNode {
105
105
  }
106
106
 
107
107
  export interface AggregateFlow {
108
- origin: string;
109
- dest: string;
108
+ origin: string | number;
109
+ dest: string | number;
110
110
  count: number;
111
111
  aggregate: true;
112
112
  }
@@ -160,6 +160,7 @@ export interface FlowLinesLayerAttributes {
160
160
  export interface LayersData {
161
161
  circleAttributes: FlowCirclesLayerAttributes;
162
162
  lineAttributes: FlowLinesLayerAttributes;
163
+ locationLabels?: string[];
163
164
  }
164
165
 
165
166
  export type LayersDataAttrValues<T> = {value: T; size: number};