@flowmap.gl/data 8.0.0-y.14 → 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.
- package/.turbo/turbo-build.log +3 -0
- package/.turbo/turbo-dev.log +6 -0
- package/LICENSE +2 -2
- package/dist/FlowmapAggregateAccessors.d.ts +4 -4
- package/dist/FlowmapAggregateAccessors.d.ts.map +1 -1
- package/dist/FlowmapAggregateAccessors.js +16 -9
- package/dist/FlowmapSelectors.d.ts +38 -84
- package/dist/FlowmapSelectors.d.ts.map +1 -1
- package/dist/FlowmapSelectors.js +125 -141
- package/dist/FlowmapState.d.ts +7 -5
- package/dist/FlowmapState.d.ts.map +1 -1
- package/dist/FlowmapState.js +6 -1
- package/dist/cluster/ClusterIndex.d.ts +4 -4
- package/dist/cluster/ClusterIndex.d.ts.map +1 -1
- package/dist/cluster/ClusterIndex.js +5 -17
- package/dist/cluster/cluster.d.ts +20 -1
- package/dist/cluster/cluster.d.ts.map +1 -1
- package/dist/cluster/cluster.js +108 -52
- package/dist/colors.d.ts +3 -3
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +19 -8
- package/dist/getViewStateForLocations.d.ts +2 -2
- package/dist/getViewStateForLocations.d.ts.map +1 -1
- package/dist/getViewStateForLocations.js +18 -8
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/provider/FlowmapDataProvider.d.ts +7 -2
- package/dist/provider/FlowmapDataProvider.d.ts.map +1 -1
- package/dist/provider/FlowmapDataProvider.js +11 -6
- package/dist/provider/LocalFlowmapDataProvider.d.ts +15 -4
- package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -1
- package/dist/provider/LocalFlowmapDataProvider.js +98 -81
- package/dist/selector-functions.d.ts +10 -0
- package/dist/selector-functions.d.ts.map +1 -0
- package/dist/selector-functions.js +65 -0
- package/dist/time.d.ts.map +1 -1
- package/dist/time.js +6 -1
- package/dist/types.d.ts +18 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -4
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +6 -1
- package/package.json +22 -23
- package/src/FlowmapAggregateAccessors.ts +21 -10
- package/src/FlowmapSelectors.ts +261 -255
- package/src/FlowmapState.ts +13 -5
- package/src/cluster/ClusterIndex.ts +23 -28
- package/src/cluster/cluster.ts +145 -56
- package/src/colors.ts +13 -9
- package/src/getViewStateForLocations.ts +6 -0
- package/src/index.ts +9 -0
- package/src/provider/FlowmapDataProvider.ts +23 -7
- package/src/provider/LocalFlowmapDataProvider.ts +68 -5
- package/src/selector-functions.ts +93 -0
- package/src/time.ts +6 -0
- package/src/types.ts +21 -13
- package/src/util.ts +6 -0
package/src/FlowmapSelectors.ts
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
3
|
-
* Copyright 2018-2020 Teralytics
|
|
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 {
|
|
20
|
-
import {
|
|
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<
|
|
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
|
-
|
|
89
|
+
getAggregateAccessors(): FlowmapAggregateAccessors<L, F> {
|
|
90
|
+
return this.accessors;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getFlowsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
94
94
|
props.flows;
|
|
95
|
-
|
|
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.
|
|
104
|
+
state.settings.maxTopFlowsDisplayNum;
|
|
99
105
|
getSelectedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
100
|
-
state.
|
|
106
|
+
state.filter?.selectedLocations;
|
|
101
107
|
getLocationFilterMode = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
102
|
-
state.
|
|
108
|
+
state.filter?.locationFilterMode;
|
|
103
109
|
getClusteringEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
104
|
-
state.
|
|
110
|
+
state.settings.clusteringEnabled;
|
|
105
111
|
getLocationTotalsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
106
|
-
state.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
150
|
+
) => state.settings.animationEnabled;
|
|
143
151
|
|
|
144
|
-
getInvalidLocationIds: Selector<L, F, string[] | undefined> =
|
|
145
|
-
this.
|
|
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,11 +162,10 @@ export default class FlowmapSelectors<L, F> {
|
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
return invalid.length > 0 ? invalid : undefined;
|
|
158
|
-
}
|
|
159
|
-
);
|
|
165
|
+
});
|
|
160
166
|
|
|
161
167
|
getLocations: Selector<L, F, Iterable<L> | undefined> = createSelector(
|
|
162
|
-
this.
|
|
168
|
+
this.getLocationsFromProps,
|
|
163
169
|
this.getInvalidLocationIds,
|
|
164
170
|
(locations, invalidIds) => {
|
|
165
171
|
if (!locations) return undefined;
|
|
@@ -176,41 +182,43 @@ export default class FlowmapSelectors<L, F> {
|
|
|
176
182
|
},
|
|
177
183
|
);
|
|
178
184
|
|
|
179
|
-
getLocationIds: Selector<L, F, Set<string> | undefined> =
|
|
180
|
-
this.getLocations,
|
|
181
|
-
(locations) => {
|
|
185
|
+
getLocationIds: Selector<L, F, Set<string | number> | undefined> =
|
|
186
|
+
createSelector(this.getLocations, (locations) => {
|
|
182
187
|
if (!locations) return undefined;
|
|
183
|
-
const ids = new Set<string>();
|
|
188
|
+
const ids = new Set<string | number>();
|
|
184
189
|
for (const id of locations) {
|
|
185
190
|
ids.add(this.accessors.getLocationId(id));
|
|
186
191
|
}
|
|
187
192
|
return ids;
|
|
188
|
-
}
|
|
189
|
-
);
|
|
193
|
+
});
|
|
190
194
|
|
|
191
|
-
getSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
|
|
195
|
+
getSelectedLocationsSet: Selector<L, F, Set<string | number> | undefined> =
|
|
192
196
|
createSelector(this.getSelectedLocations, (ids) =>
|
|
193
197
|
ids && ids.length > 0 ? new Set(ids) : undefined,
|
|
194
198
|
);
|
|
195
199
|
|
|
196
200
|
getSortedFlowsForKnownLocations: Selector<L, F, F[] | undefined> =
|
|
197
|
-
createSelector(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
}
|
|
205
213
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
return filtered.sort((a: F, b: F) =>
|
|
215
|
+
descending(
|
|
216
|
+
Math.abs(this.accessors.getFlowMagnitude(a)),
|
|
217
|
+
Math.abs(this.accessors.getFlowMagnitude(b)),
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
);
|
|
214
222
|
|
|
215
223
|
getActualTimeExtent: Selector<L, F, [Date, Date] | undefined> =
|
|
216
224
|
createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
|
|
@@ -303,29 +311,33 @@ export default class FlowmapSelectors<L, F> {
|
|
|
303
311
|
},
|
|
304
312
|
);
|
|
305
313
|
|
|
306
|
-
getLocationsById: Selector<L, F, Map<string, L> | undefined> =
|
|
307
|
-
this.getLocationsHavingFlows,
|
|
308
|
-
(locations) => {
|
|
314
|
+
getLocationsById: Selector<L, F, Map<string | number, L> | undefined> =
|
|
315
|
+
createSelector(this.getLocationsHavingFlows, (locations) => {
|
|
309
316
|
if (!locations) return undefined;
|
|
310
|
-
const locationsById = new Map<string, L>();
|
|
317
|
+
const locationsById = new Map<string | number, L>();
|
|
311
318
|
for (const location of locations) {
|
|
312
319
|
locationsById.set(this.accessors.getLocationId(location), location);
|
|
313
320
|
}
|
|
314
321
|
return locationsById;
|
|
315
|
-
}
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
|
|
319
|
-
this.getLocationsHavingFlows,
|
|
320
|
-
this.getLocationsById,
|
|
321
|
-
this.getSortedFlowsForKnownLocations,
|
|
322
|
-
(locations, locationsById, flows) => {
|
|
323
|
-
if (!locations || !locationsById || !flows) return undefined;
|
|
322
|
+
});
|
|
324
323
|
|
|
324
|
+
getLocationWeightGetter: Selector<L, F, LocationWeightGetter | undefined> =
|
|
325
|
+
createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
|
|
326
|
+
if (!flows) return undefined;
|
|
325
327
|
const getLocationWeight = makeLocationWeightGetter(
|
|
326
328
|
flows,
|
|
327
329
|
this.accessors.getFlowmapDataAccessors(),
|
|
328
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;
|
|
329
341
|
const clusterLevels = clusterLocations(
|
|
330
342
|
locations,
|
|
331
343
|
this.accessors.getFlowmapDataAccessors(),
|
|
@@ -334,47 +346,27 @@ export default class FlowmapSelectors<L, F> {
|
|
|
334
346
|
maxZoom: MAX_CLUSTER_ZOOM_LEVEL,
|
|
335
347
|
},
|
|
336
348
|
);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// Adding meaningful names
|
|
342
|
-
const getName = (id: string) => {
|
|
343
|
-
const loc = locationsById.get(id);
|
|
344
|
-
if (loc) {
|
|
345
|
-
return getLocationName
|
|
346
|
-
? getLocationName(loc)
|
|
347
|
-
: this.accessors.getLocationId(loc) || id;
|
|
348
|
-
}
|
|
349
|
-
return `"${id}"`;
|
|
350
|
-
};
|
|
351
|
-
for (const level of clusterLevels) {
|
|
352
|
-
for (const node of level.nodes) {
|
|
353
|
-
// Here mutating the nodes (adding names)
|
|
354
|
-
if (isCluster(node)) {
|
|
355
|
-
const leaves = clusterIndex.expandCluster(node);
|
|
356
|
-
|
|
357
|
-
leaves.sort((a, b) =>
|
|
358
|
-
descending(getLocationWeight(a), getLocationWeight(b)),
|
|
359
|
-
);
|
|
349
|
+
return clusterLevels;
|
|
350
|
+
},
|
|
351
|
+
);
|
|
360
352
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
? `"${getName(otherId)}"`
|
|
369
|
-
: `${leaves.length - 1} others`
|
|
370
|
-
}`;
|
|
371
|
-
}
|
|
372
|
-
} else {
|
|
373
|
-
(node as any).name = getName(node.id);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
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;
|
|
377
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
|
+
);
|
|
378
370
|
return clusterIndex;
|
|
379
371
|
},
|
|
380
372
|
);
|
|
@@ -390,7 +382,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
390
382
|
let maxZoom = Number.POSITIVE_INFINITY;
|
|
391
383
|
let minZoom = Number.NEGATIVE_INFINITY;
|
|
392
384
|
|
|
393
|
-
const adjust = (zoneId: string) => {
|
|
385
|
+
const adjust = (zoneId: string | number) => {
|
|
394
386
|
const cluster = clusterIndex.getClusterById(zoneId);
|
|
395
387
|
if (cluster) {
|
|
396
388
|
minZoom = Math.max(minZoom, cluster.zoom);
|
|
@@ -419,7 +411,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
419
411
|
this.getAvailableClusterZoomLevels,
|
|
420
412
|
(clusterIndex, mapZoom, availableClusterZoomLevels) => {
|
|
421
413
|
if (!clusterIndex) return undefined;
|
|
422
|
-
if (!availableClusterZoomLevels) {
|
|
414
|
+
if (!availableClusterZoomLevels || mapZoom == null) {
|
|
423
415
|
return undefined;
|
|
424
416
|
}
|
|
425
417
|
|
|
@@ -432,12 +424,12 @@ export default class FlowmapSelectors<L, F> {
|
|
|
432
424
|
);
|
|
433
425
|
|
|
434
426
|
getClusterZoom = (state: FlowmapState, props: FlowmapData<L, F>) => {
|
|
435
|
-
const {
|
|
436
|
-
if (!
|
|
437
|
-
if (
|
|
427
|
+
const {settings} = state;
|
|
428
|
+
if (!settings.clusteringEnabled) return undefined;
|
|
429
|
+
if (settings.clusteringAuto || settings.clusteringLevel == null) {
|
|
438
430
|
return this._getClusterZoom(state, props);
|
|
439
431
|
}
|
|
440
|
-
return
|
|
432
|
+
return settings.clusteringLevel;
|
|
441
433
|
};
|
|
442
434
|
|
|
443
435
|
getLocationsForSearchBox: Selector<L, F, (L | Cluster)[] | undefined> =
|
|
@@ -490,7 +482,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
490
482
|
);
|
|
491
483
|
|
|
492
484
|
getDiffMode: Selector<L, F, boolean> = createSelector(
|
|
493
|
-
this.
|
|
485
|
+
this.getFlowsFromProps,
|
|
494
486
|
(flows) => {
|
|
495
487
|
if (flows) {
|
|
496
488
|
for (const f of flows) {
|
|
@@ -523,27 +515,28 @@ export default class FlowmapSelectors<L, F> {
|
|
|
523
515
|
},
|
|
524
516
|
);
|
|
525
517
|
|
|
526
|
-
getUnknownLocations: Selector<L, F, Set<string> | undefined> =
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
+
);
|
|
547
540
|
|
|
548
541
|
getSortedAggregatedFilteredFlows: Selector<
|
|
549
542
|
L,
|
|
@@ -584,31 +577,34 @@ export default class FlowmapSelectors<L, F> {
|
|
|
584
577
|
},
|
|
585
578
|
);
|
|
586
579
|
|
|
587
|
-
getExpandedSelectedLocationsSet: Selector<
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
+
}
|
|
596
592
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
} else {
|
|
606
|
-
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);
|
|
607
600
|
}
|
|
601
|
+
} else {
|
|
602
|
+
result.add(locationId);
|
|
608
603
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
604
|
+
}
|
|
605
|
+
return result;
|
|
606
|
+
},
|
|
607
|
+
);
|
|
612
608
|
|
|
613
609
|
getTotalCountsByTime: Selector<L, F, CountByTime[] | undefined> =
|
|
614
610
|
createSelector(
|
|
@@ -663,15 +659,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
663
659
|
createSelector(
|
|
664
660
|
this.getViewport,
|
|
665
661
|
this.getMaxLocationCircleSize,
|
|
666
|
-
|
|
667
|
-
const pad = maxLocationCircleSize;
|
|
668
|
-
const bounds = new WebMercatorViewport({
|
|
669
|
-
...viewport,
|
|
670
|
-
width: viewport.width + pad * 2,
|
|
671
|
-
height: viewport.height + pad * 2,
|
|
672
|
-
}).getBounds();
|
|
673
|
-
return [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
|
|
674
|
-
},
|
|
662
|
+
getViewportBoundingBox,
|
|
675
663
|
);
|
|
676
664
|
|
|
677
665
|
getLocationsForZoom: Selector<L, F, Iterable<L> | ClusterNode[] | undefined> =
|
|
@@ -689,47 +677,50 @@ export default class FlowmapSelectors<L, F> {
|
|
|
689
677
|
},
|
|
690
678
|
);
|
|
691
679
|
|
|
692
|
-
getLocationTotals: Selector<
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (d.internalCount != null) rv.internalCount += d.internalCount;
|
|
713
|
-
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,
|
|
714
700
|
};
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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}));
|
|
728
718
|
}
|
|
729
719
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
720
|
+
}
|
|
721
|
+
return totals;
|
|
722
|
+
},
|
|
723
|
+
);
|
|
733
724
|
|
|
734
725
|
getLocationsTree: Selector<L, F, KDBushTree> = createSelector(
|
|
735
726
|
this.getLocationsForZoom,
|
|
@@ -737,14 +728,20 @@ export default class FlowmapSelectors<L, F> {
|
|
|
737
728
|
if (!locations) {
|
|
738
729
|
return undefined;
|
|
739
730
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
locations
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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;
|
|
748
745
|
},
|
|
749
746
|
);
|
|
750
747
|
|
|
@@ -763,7 +760,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
763
760
|
},
|
|
764
761
|
);
|
|
765
762
|
|
|
766
|
-
getLocationIdsInViewport: Selector<L, F, Set<string> | undefined> =
|
|
763
|
+
getLocationIdsInViewport: Selector<L, F, Set<string | number> | undefined> =
|
|
767
764
|
createSelectorCreator(
|
|
768
765
|
defaultMemoize,
|
|
769
766
|
// @ts-ignore
|
|
@@ -835,7 +832,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
835
832
|
state: FlowmapState,
|
|
836
833
|
props: FlowmapData<L, F>,
|
|
837
834
|
): [number, number] | undefined => {
|
|
838
|
-
if (state.
|
|
835
|
+
if (state.settings.adaptiveScalesEnabled) {
|
|
839
836
|
return this._getLocationTotalsForViewportExtent(state, props);
|
|
840
837
|
} else {
|
|
841
838
|
return this._getLocationTotalsExtent(state, props);
|
|
@@ -930,7 +927,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
930
927
|
state: FlowmapState,
|
|
931
928
|
props: FlowmapData<L, F>,
|
|
932
929
|
): [number, number] | undefined => {
|
|
933
|
-
if (state.
|
|
930
|
+
if (state.settings.adaptiveScalesEnabled) {
|
|
934
931
|
return this._getAdaptiveFlowMagnitudeExtent(state, props);
|
|
935
932
|
} else {
|
|
936
933
|
return this._getFlowMagnitudeExtent(state, props);
|
|
@@ -953,19 +950,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
953
950
|
|
|
954
951
|
getFlowThicknessScale = createSelector(
|
|
955
952
|
this.getFlowMagnitudeExtent,
|
|
956
|
-
|
|
957
|
-
if (!magnitudeExtent) return undefined;
|
|
958
|
-
return scaleLinear()
|
|
959
|
-
.range([0.025, 0.5])
|
|
960
|
-
.domain([
|
|
961
|
-
0,
|
|
962
|
-
// should support diff mode too
|
|
963
|
-
Math.max.apply(
|
|
964
|
-
null,
|
|
965
|
-
magnitudeExtent.map((x: number | undefined) => Math.abs(x || 0)),
|
|
966
|
-
),
|
|
967
|
-
]);
|
|
968
|
-
},
|
|
953
|
+
getFlowThicknessScale,
|
|
969
954
|
);
|
|
970
955
|
|
|
971
956
|
getCircleSizeScale = createSelector(
|
|
@@ -996,7 +981,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
996
981
|
this.getCircleSizeScale,
|
|
997
982
|
this.getLocationTotals,
|
|
998
983
|
(circleSizeScale, locationTotals) => {
|
|
999
|
-
return (locationId: string) => {
|
|
984
|
+
return (locationId: string | number) => {
|
|
1000
985
|
const total = locationTotals?.get(locationId);
|
|
1001
986
|
if (total && circleSizeScale) {
|
|
1002
987
|
return (
|
|
@@ -1014,7 +999,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1014
999
|
this.getCircleSizeScale,
|
|
1015
1000
|
this.getLocationTotals,
|
|
1016
1001
|
(circleSizeScale, locationTotals) => {
|
|
1017
|
-
return (locationId: string) => {
|
|
1002
|
+
return (locationId: string | number) => {
|
|
1018
1003
|
const total = locationTotals?.get(locationId);
|
|
1019
1004
|
if (total && circleSizeScale) {
|
|
1020
1005
|
return (
|
|
@@ -1089,6 +1074,15 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1089
1074
|
);
|
|
1090
1075
|
});
|
|
1091
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
|
+
|
|
1092
1086
|
getLayersData: Selector<L, F, LayersData> = createSelector(
|
|
1093
1087
|
this.getLocationsForFlowmapLayer,
|
|
1094
1088
|
this.getFlowsForFlowmapLayer,
|
|
@@ -1099,6 +1093,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1099
1093
|
this.getOutCircleSizeGetter,
|
|
1100
1094
|
this.getFlowThicknessScale,
|
|
1101
1095
|
this.getAnimate,
|
|
1096
|
+
this.getLocationLabelsEnabled,
|
|
1102
1097
|
(
|
|
1103
1098
|
locations,
|
|
1104
1099
|
flows,
|
|
@@ -1109,6 +1104,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1109
1104
|
getOutCircleSize,
|
|
1110
1105
|
flowThicknessScale,
|
|
1111
1106
|
animationEnabled,
|
|
1107
|
+
locationLabelsEnabled,
|
|
1112
1108
|
) => {
|
|
1113
1109
|
return this._prepareLayersData(
|
|
1114
1110
|
locations,
|
|
@@ -1120,6 +1116,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1120
1116
|
getOutCircleSize,
|
|
1121
1117
|
flowThicknessScale,
|
|
1122
1118
|
animationEnabled,
|
|
1119
|
+
locationLabelsEnabled,
|
|
1123
1120
|
);
|
|
1124
1121
|
},
|
|
1125
1122
|
);
|
|
@@ -1133,6 +1130,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1133
1130
|
const getInCircleSize = this.getInCircleSizeGetter(state, props);
|
|
1134
1131
|
const getOutCircleSize = this.getOutCircleSizeGetter(state, props);
|
|
1135
1132
|
const flowThicknessScale = this.getFlowThicknessScale(state, props);
|
|
1133
|
+
const locationLabelsEnabled = this.getLocationLabelsEnabled(state, props);
|
|
1136
1134
|
return this._prepareLayersData(
|
|
1137
1135
|
locations,
|
|
1138
1136
|
flows,
|
|
@@ -1142,7 +1140,8 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1142
1140
|
getInCircleSize,
|
|
1143
1141
|
getOutCircleSize,
|
|
1144
1142
|
flowThicknessScale,
|
|
1145
|
-
state.
|
|
1143
|
+
state.settings.animationEnabled,
|
|
1144
|
+
locationLabelsEnabled,
|
|
1146
1145
|
);
|
|
1147
1146
|
}
|
|
1148
1147
|
|
|
@@ -1150,12 +1149,13 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1150
1149
|
locations: (L | ClusterNode)[] | undefined,
|
|
1151
1150
|
flows: (F | AggregateFlow)[] | undefined,
|
|
1152
1151
|
flowmapColors: DiffColorsRGBA | ColorsRGBA,
|
|
1153
|
-
locationsById: Map<string, L | ClusterNode> | undefined,
|
|
1154
|
-
locationIdsInViewport: Set<string> | undefined,
|
|
1155
|
-
getInCircleSize: (locationId: string) => number,
|
|
1156
|
-
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,
|
|
1157
1156
|
flowThicknessScale: ScaleLinear<number, number, never> | undefined,
|
|
1158
1157
|
animationEnabled: boolean,
|
|
1158
|
+
locationLabelsEnabled: boolean,
|
|
1159
1159
|
): LayersData {
|
|
1160
1160
|
if (!locations) locations = [];
|
|
1161
1161
|
if (!flows) flows = [];
|
|
@@ -1166,6 +1166,7 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1166
1166
|
getLocationId,
|
|
1167
1167
|
getLocationLon,
|
|
1168
1168
|
getLocationLat,
|
|
1169
|
+
getLocationName,
|
|
1169
1170
|
} = this.accessors;
|
|
1170
1171
|
|
|
1171
1172
|
const flowMagnitudeExtent = extent(flows, (f) => getFlowMagnitude(f)) as [
|
|
@@ -1297,6 +1298,9 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1297
1298
|
: {}),
|
|
1298
1299
|
},
|
|
1299
1300
|
},
|
|
1301
|
+
...(locationLabelsEnabled
|
|
1302
|
+
? {locationLabels: locations.map(getLocationName)}
|
|
1303
|
+
: undefined),
|
|
1300
1304
|
};
|
|
1301
1305
|
}
|
|
1302
1306
|
|
|
@@ -1327,8 +1331,8 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1327
1331
|
|
|
1328
1332
|
isFlowInSelection(
|
|
1329
1333
|
flow: F | AggregateFlow,
|
|
1330
|
-
selectedLocationsSet: Set<string> | undefined,
|
|
1331
|
-
locationFilterMode
|
|
1334
|
+
selectedLocationsSet: Set<string | number> | undefined,
|
|
1335
|
+
locationFilterMode?: LocationFilterMode,
|
|
1332
1336
|
) {
|
|
1333
1337
|
const origin = this.accessors.getFlowOriginId(flow);
|
|
1334
1338
|
const dest = this.accessors.getFlowDestId(flow);
|
|
@@ -1374,8 +1378,8 @@ export default class FlowmapSelectors<L, F> {
|
|
|
1374
1378
|
}
|
|
1375
1379
|
|
|
1376
1380
|
function calcLocationTotalsExtent(
|
|
1377
|
-
locationTotals: Map<string, LocationTotals> | undefined,
|
|
1378
|
-
locationIdsInViewport: Set<string> | undefined,
|
|
1381
|
+
locationTotals: Map<string | number, LocationTotals> | undefined,
|
|
1382
|
+
locationIdsInViewport: Set<string | number> | undefined,
|
|
1379
1383
|
) {
|
|
1380
1384
|
if (!locationTotals) return undefined;
|
|
1381
1385
|
let rv: [number, number] | undefined = undefined;
|
|
@@ -1421,10 +1425,9 @@ function aggregateFlows<F>(
|
|
|
1421
1425
|
flowAccessors: FlowAccessors<F>,
|
|
1422
1426
|
): AggregateFlow[] {
|
|
1423
1427
|
// Sum up flows with same origin, dest
|
|
1424
|
-
const byOriginDest =
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
.rollup((ff: F[]) => {
|
|
1428
|
+
const byOriginDest = rollup(
|
|
1429
|
+
flows,
|
|
1430
|
+
(ff: F[]) => {
|
|
1428
1431
|
const origin = flowAccessors.getFlowOriginId(ff[0]);
|
|
1429
1432
|
const dest = flowAccessors.getFlowDestId(ff[0]);
|
|
1430
1433
|
// const color = ff[0].color;
|
|
@@ -1443,11 +1446,14 @@ function aggregateFlows<F>(
|
|
|
1443
1446
|
};
|
|
1444
1447
|
// if (color) rv.color = color;
|
|
1445
1448
|
return rv;
|
|
1446
|
-
}
|
|
1447
|
-
.
|
|
1449
|
+
},
|
|
1450
|
+
flowAccessors.getFlowOriginId,
|
|
1451
|
+
flowAccessors.getFlowDestId,
|
|
1452
|
+
);
|
|
1453
|
+
|
|
1448
1454
|
const rv: AggregateFlow[] = [];
|
|
1449
|
-
for (const
|
|
1450
|
-
for (const
|
|
1455
|
+
for (const values of byOriginDest.values()) {
|
|
1456
|
+
for (const value of values.values()) {
|
|
1451
1457
|
rv.push(value);
|
|
1452
1458
|
}
|
|
1453
1459
|
}
|