@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.
- package/dist/FlowmapAggregateAccessors.d.ts +3 -3
- package/dist/FlowmapAggregateAccessors.d.ts.map +1 -1
- package/dist/FlowmapAggregateAccessors.js +11 -9
- package/dist/FlowmapSelectors.d.ts +26 -11
- package/dist/FlowmapSelectors.d.ts.map +1 -1
- package/dist/FlowmapSelectors.js +22 -46
- package/dist/FlowmapState.d.ts +1 -0
- package/dist/FlowmapState.d.ts.map +1 -1
- package/dist/FlowmapState.js +1 -1
- package/dist/cluster/ClusterIndex.d.ts +3 -3
- package/dist/cluster/ClusterIndex.d.ts.map +1 -1
- package/dist/cluster/ClusterIndex.js +1 -1
- package/dist/cluster/cluster.js +1 -1
- package/dist/provider/FlowmapDataProvider.d.ts +2 -2
- package/dist/provider/FlowmapDataProvider.d.ts.map +1 -1
- package/dist/provider/FlowmapDataProvider.js +1 -1
- package/dist/provider/LocalFlowmapDataProvider.d.ts +13 -3
- package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -1
- package/dist/provider/LocalFlowmapDataProvider.js +36 -4
- package/dist/selector-functions.d.ts +7 -1
- package/dist/selector-functions.d.ts.map +1 -1
- package/dist/selector-functions.js +37 -1
- package/dist/types.d.ts +8 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/package.json +2 -2
- package/src/FlowmapAggregateAccessors.ts +11 -9
- package/src/FlowmapSelectors.ts +117 -125
- package/src/FlowmapState.ts +1 -0
- package/src/cluster/ClusterIndex.ts +19 -12
- package/src/cluster/cluster.ts +2 -2
- package/src/provider/FlowmapDataProvider.ts +4 -2
- package/src/provider/LocalFlowmapDataProvider.ts +51 -3
- package/src/selector-functions.ts +54 -1
- 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: (
|
|
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<
|
|
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) =>
|
|
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),
|
package/src/cluster/cluster.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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};
|