@flowmap.gl/data 8.0.0-alpha.2 → 8.0.0-alpha.22
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 +16 -0
- package/dist/FlowmapAggregateAccessors.d.ts.map +1 -0
- package/dist/FlowmapAggregateAccessors.js +48 -0
- package/dist/FlowmapSelectors.d.ts +213 -0
- package/dist/FlowmapSelectors.d.ts.map +1 -0
- package/dist/FlowmapSelectors.js +861 -0
- package/dist/{FlowMapState.d.ts → FlowmapState.d.ts} +12 -8
- package/dist/FlowmapState.d.ts.map +1 -0
- package/dist/FlowmapState.js +2 -0
- 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.d.ts +6 -5
- package/dist/cluster/cluster.d.ts.map +1 -1
- package/dist/cluster/cluster.js +76 -20
- package/dist/colors.d.ts +7 -7
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +55 -20
- package/dist/getViewStateForLocations.d.ts +18 -11
- package/dist/getViewStateForLocations.d.ts.map +1 -1
- package/dist/getViewStateForLocations.js +37 -23
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/provider/FlowmapDataProvider.d.ts +21 -0
- package/dist/provider/FlowmapDataProvider.d.ts.map +1 -0
- package/dist/provider/FlowmapDataProvider.js +17 -0
- package/dist/provider/LocalFlowmapDataProvider.d.ts +25 -0
- package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -0
- package/dist/provider/LocalFlowmapDataProvider.js +111 -0
- package/dist/selector-functions.d.ts +10 -0
- package/dist/selector-functions.d.ts.map +1 -0
- package/dist/selector-functions.js +56 -0
- package/dist/types.d.ts +20 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -4
- package/dist/util.d.ts +0 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +1 -4
- package/package.json +10 -12
- package/src/FlowmapAggregateAccessors.ts +67 -0
- package/src/{FlowMapSelectors.ts → FlowmapSelectors.ts} +453 -398
- package/src/{FlowMapState.ts → FlowmapState.ts} +11 -7
- package/src/cluster/ClusterIndex.ts +19 -12
- package/src/cluster/cluster.ts +96 -35
- package/src/colors.ts +70 -28
- package/src/getViewStateForLocations.ts +56 -40
- package/src/index.ts +9 -6
- package/src/provider/FlowmapDataProvider.ts +75 -0
- package/src/provider/LocalFlowmapDataProvider.ts +143 -0
- package/src/selector-functions.ts +87 -0
- package/src/types.ts +23 -19
- package/src/util.ts +0 -4
- package/dist/FlowMapAggregateAccessors.d.ts +0 -15
- package/dist/FlowMapAggregateAccessors.d.ts.map +0 -1
- package/dist/FlowMapAggregateAccessors.js +0 -43
- package/dist/FlowMapSelectors.d.ts +0 -182
- package/dist/FlowMapSelectors.d.ts.map +0 -1
- package/dist/FlowMapSelectors.js +0 -834
- package/dist/FlowMapState.d.ts.map +0 -1
- package/dist/FlowMapState.js +0 -2
- package/dist/provider/FlowMapDataProvider.d.ts +0 -16
- package/dist/provider/FlowMapDataProvider.d.ts.map +0 -1
- package/dist/provider/FlowMapDataProvider.js +0 -17
- package/dist/provider/LocalFlowMapDataProvider.d.ts +0 -20
- package/dist/provider/LocalFlowMapDataProvider.d.ts.map +0 -1
- package/dist/provider/LocalFlowMapDataProvider.js +0 -87
- package/src/FlowMapAggregateAccessors.ts +0 -60
- package/src/provider/FlowMapDataProvider.ts +0 -63
- package/src/provider/LocalFlowMapDataProvider.ts +0 -105
|
@@ -16,10 +16,8 @@
|
|
|
16
16
|
*
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {nest} from 'd3-collection';
|
|
22
|
-
import {ScaleLinear, scaleLinear, scaleSqrt} from 'd3-scale';
|
|
19
|
+
import {ascending, descending, extent, min, rollup} from 'd3-array';
|
|
20
|
+
import {ScaleLinear, scaleSqrt} from 'd3-scale';
|
|
23
21
|
import KDBush from 'kdbush';
|
|
24
22
|
import {
|
|
25
23
|
createSelector,
|
|
@@ -33,6 +31,7 @@ import {
|
|
|
33
31
|
buildIndex,
|
|
34
32
|
ClusterIndex,
|
|
35
33
|
findAppropriateZoomLevel,
|
|
34
|
+
LocationWeightGetter,
|
|
36
35
|
makeLocationWeightGetter,
|
|
37
36
|
} from './cluster/ClusterIndex';
|
|
38
37
|
import getColors, {
|
|
@@ -44,8 +43,13 @@ import getColors, {
|
|
|
44
43
|
isDiffColors,
|
|
45
44
|
isDiffColorsRGBA,
|
|
46
45
|
} from './colors';
|
|
47
|
-
import
|
|
48
|
-
import {
|
|
46
|
+
import FlowmapAggregateAccessors from './FlowmapAggregateAccessors';
|
|
47
|
+
import {FlowmapState} from './FlowmapState';
|
|
48
|
+
import {
|
|
49
|
+
addClusterNames,
|
|
50
|
+
getFlowThicknessScale,
|
|
51
|
+
getViewportBoundingBox,
|
|
52
|
+
} from './selector-functions';
|
|
49
53
|
import {
|
|
50
54
|
getTimeGranularityByKey,
|
|
51
55
|
getTimeGranularityByOrder,
|
|
@@ -55,126 +59,143 @@ import {
|
|
|
55
59
|
import {
|
|
56
60
|
AggregateFlow,
|
|
57
61
|
Cluster,
|
|
62
|
+
ClusterLevels,
|
|
58
63
|
ClusterNode,
|
|
59
64
|
CountByTime,
|
|
60
65
|
FlowAccessors,
|
|
61
66
|
FlowCirclesLayerAttributes,
|
|
62
67
|
FlowLinesLayerAttributes,
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
FlowmapData,
|
|
69
|
+
FlowmapDataAccessors,
|
|
65
70
|
isCluster,
|
|
66
71
|
isLocationClusterNode,
|
|
67
72
|
LayersData,
|
|
68
73
|
LocationFilterMode,
|
|
69
74
|
LocationTotals,
|
|
70
75
|
} from './types';
|
|
71
|
-
import {flatMap} from './util';
|
|
72
76
|
|
|
73
77
|
const MAX_CLUSTER_ZOOM_LEVEL = 20;
|
|
74
|
-
const NUMBER_OF_FLOWS_TO_DISPLAY = 5000;
|
|
75
78
|
type KDBushTree = any;
|
|
76
79
|
|
|
77
80
|
export type Selector<L, F, T> = ParametricSelector<
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
FlowmapState,
|
|
82
|
+
FlowmapData<L, F>,
|
|
80
83
|
T
|
|
81
84
|
>;
|
|
82
85
|
|
|
83
|
-
export default class
|
|
84
|
-
accessors:
|
|
86
|
+
export default class FlowmapSelectors<L, F> {
|
|
87
|
+
accessors: FlowmapAggregateAccessors<L, F>;
|
|
85
88
|
|
|
86
|
-
constructor(accessors:
|
|
87
|
-
this.accessors = new
|
|
89
|
+
constructor(accessors: FlowmapDataAccessors<L, F>) {
|
|
90
|
+
this.accessors = new FlowmapAggregateAccessors(accessors);
|
|
88
91
|
this.setAccessors(accessors);
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
setAccessors(accessors:
|
|
92
|
-
this.accessors = new
|
|
94
|
+
setAccessors(accessors: FlowmapDataAccessors<L, F>) {
|
|
95
|
+
this.accessors = new FlowmapAggregateAccessors(accessors);
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
getFlowsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
96
99
|
props.flows;
|
|
97
|
-
|
|
100
|
+
getLocationsFromProps = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
98
101
|
props.locations;
|
|
99
|
-
|
|
100
|
-
state
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
state.
|
|
107
|
-
|
|
102
|
+
getClusterLevelsFromProps = (
|
|
103
|
+
state: FlowmapState,
|
|
104
|
+
props: FlowmapData<L, F>,
|
|
105
|
+
) => {
|
|
106
|
+
return props.clusterLevels;
|
|
107
|
+
};
|
|
108
|
+
getMaxTopFlowsDisplayNum = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
109
|
+
state.settings.maxTopFlowsDisplayNum;
|
|
110
|
+
getSelectedLocations = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
111
|
+
state.filter?.selectedLocations;
|
|
112
|
+
getLocationFilterMode = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
113
|
+
state.filter?.locationFilterMode;
|
|
114
|
+
getClusteringEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
115
|
+
state.settings.clusteringEnabled;
|
|
116
|
+
getLocationTotalsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
117
|
+
state.settings.locationTotalsEnabled;
|
|
118
|
+
getLocationLabelsEnabled = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
119
|
+
state.settings.locationLabelsEnabled;
|
|
120
|
+
getZoom = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
108
121
|
state.viewport.zoom;
|
|
109
|
-
getViewport = (state:
|
|
122
|
+
getViewport = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
110
123
|
state.viewport;
|
|
111
|
-
getSelectedTimeRange = (state:
|
|
112
|
-
state.
|
|
124
|
+
getSelectedTimeRange = (state: FlowmapState, props: FlowmapData<L, F>) =>
|
|
125
|
+
state.filter?.selectedTimeRange;
|
|
113
126
|
|
|
114
|
-
|
|
115
|
-
state:
|
|
116
|
-
props:
|
|
117
|
-
) => state.
|
|
127
|
+
getColorScheme: Selector<L, F, string | string[] | undefined> = (
|
|
128
|
+
state: FlowmapState,
|
|
129
|
+
props: FlowmapData<L, F>,
|
|
130
|
+
) => state.settings.colorScheme;
|
|
118
131
|
|
|
119
132
|
getDarkMode: Selector<L, F, boolean> = (
|
|
120
|
-
state:
|
|
121
|
-
props:
|
|
122
|
-
) => state.
|
|
133
|
+
state: FlowmapState,
|
|
134
|
+
props: FlowmapData<L, F>,
|
|
135
|
+
) => state.settings.darkMode;
|
|
123
136
|
|
|
124
137
|
getFadeEnabled: Selector<L, F, boolean> = (
|
|
125
|
-
state:
|
|
126
|
-
props:
|
|
127
|
-
) => state.
|
|
138
|
+
state: FlowmapState,
|
|
139
|
+
props: FlowmapData<L, F>,
|
|
140
|
+
) => state.settings.fadeEnabled;
|
|
141
|
+
|
|
142
|
+
getFadeOpacityEnabled: Selector<L, F, boolean> = (
|
|
143
|
+
state: FlowmapState,
|
|
144
|
+
props: FlowmapData<L, F>,
|
|
145
|
+
) => state.settings.fadeOpacityEnabled;
|
|
128
146
|
|
|
129
147
|
getFadeAmount: Selector<L, F, number> = (
|
|
130
|
-
state:
|
|
131
|
-
props:
|
|
132
|
-
) => state.
|
|
148
|
+
state: FlowmapState,
|
|
149
|
+
props: FlowmapData<L, F>,
|
|
150
|
+
) => state.settings.fadeAmount;
|
|
133
151
|
|
|
134
152
|
getAnimate: Selector<L, F, boolean> = (
|
|
135
|
-
state:
|
|
136
|
-
props:
|
|
137
|
-
) => state.
|
|
153
|
+
state: FlowmapState,
|
|
154
|
+
props: FlowmapData<L, F>,
|
|
155
|
+
) => state.settings.animationEnabled;
|
|
138
156
|
|
|
139
|
-
getInvalidLocationIds: Selector<L, F, string[] | undefined> =
|
|
140
|
-
this.
|
|
141
|
-
(locations) => {
|
|
157
|
+
getInvalidLocationIds: Selector<L, F, (string | number)[] | undefined> =
|
|
158
|
+
createSelector(this.getLocationsFromProps, (locations) => {
|
|
142
159
|
if (!locations) return undefined;
|
|
143
160
|
const invalid = [];
|
|
144
161
|
for (const location of locations) {
|
|
145
162
|
const id = this.accessors.getLocationId(location);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
NaN,
|
|
149
|
-
];
|
|
163
|
+
const lon = this.accessors.getLocationLon(location);
|
|
164
|
+
const lat = this.accessors.getLocationLat(location);
|
|
150
165
|
if (!(-90 <= lat && lat <= 90) || !(-180 <= lon && lon <= 180)) {
|
|
151
166
|
invalid.push(id);
|
|
152
167
|
}
|
|
153
168
|
}
|
|
154
169
|
return invalid.length > 0 ? invalid : undefined;
|
|
155
|
-
}
|
|
156
|
-
);
|
|
170
|
+
});
|
|
157
171
|
|
|
158
|
-
getLocations: Selector<L, F, L
|
|
159
|
-
this.
|
|
172
|
+
getLocations: Selector<L, F, Iterable<L> | undefined> = createSelector(
|
|
173
|
+
this.getLocationsFromProps,
|
|
160
174
|
this.getInvalidLocationIds,
|
|
161
175
|
(locations, invalidIds) => {
|
|
162
176
|
if (!locations) return undefined;
|
|
163
177
|
if (!invalidIds || invalidIds.length === 0) return locations;
|
|
164
178
|
const invalid = new Set(invalidIds);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
179
|
+
const filtered: L[] = [];
|
|
180
|
+
for (const location of locations) {
|
|
181
|
+
const id = this.accessors.getLocationId(location);
|
|
182
|
+
if (!invalid.has(id)) {
|
|
183
|
+
filtered.push(location);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return filtered;
|
|
168
187
|
},
|
|
169
188
|
);
|
|
170
189
|
|
|
171
|
-
getLocationIds: Selector<L, F, Set<string> | undefined> =
|
|
172
|
-
this.getLocations,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
190
|
+
getLocationIds: Selector<L, F, Set<string | number> | undefined> =
|
|
191
|
+
createSelector(this.getLocations, (locations) => {
|
|
192
|
+
if (!locations) return undefined;
|
|
193
|
+
const ids = new Set<string | number>();
|
|
194
|
+
for (const id of locations) {
|
|
195
|
+
ids.add(this.accessors.getLocationId(id));
|
|
196
|
+
}
|
|
197
|
+
return ids;
|
|
198
|
+
});
|
|
178
199
|
|
|
179
200
|
getSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
|
|
180
201
|
createSelector(this.getSelectedLocations, (ids) =>
|
|
@@ -182,21 +203,27 @@ export default class FlowMapSelectors<L, F> {
|
|
|
182
203
|
);
|
|
183
204
|
|
|
184
205
|
getSortedFlowsForKnownLocations: Selector<L, F, F[] | undefined> =
|
|
185
|
-
createSelector(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
206
|
+
createSelector(
|
|
207
|
+
this.getFlowsFromProps,
|
|
208
|
+
this.getLocationIds,
|
|
209
|
+
(flows, ids) => {
|
|
210
|
+
if (!ids || !flows) return undefined;
|
|
211
|
+
const filtered = [];
|
|
212
|
+
for (const flow of flows) {
|
|
213
|
+
const srcId = this.accessors.getFlowOriginId(flow);
|
|
214
|
+
const dstId = this.accessors.getFlowDestId(flow);
|
|
215
|
+
if (ids.has(srcId) && ids.has(dstId)) {
|
|
216
|
+
filtered.push(flow);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return filtered.sort((a: F, b: F) =>
|
|
194
220
|
descending(
|
|
195
221
|
Math.abs(this.accessors.getFlowMagnitude(a)),
|
|
196
222
|
Math.abs(this.accessors.getFlowMagnitude(b)),
|
|
197
223
|
),
|
|
198
224
|
);
|
|
199
|
-
|
|
225
|
+
},
|
|
226
|
+
);
|
|
200
227
|
|
|
201
228
|
getActualTimeExtent: Selector<L, F, [Date, Date] | undefined> =
|
|
202
229
|
createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
|
|
@@ -268,93 +295,83 @@ export default class FlowMapSelectors<L, F> {
|
|
|
268
295
|
},
|
|
269
296
|
);
|
|
270
297
|
|
|
271
|
-
getLocationsHavingFlows: Selector<L, F, L
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
298
|
+
getLocationsHavingFlows: Selector<L, F, Iterable<L> | undefined> =
|
|
299
|
+
createSelector(
|
|
300
|
+
this.getSortedFlowsForKnownLocations,
|
|
301
|
+
this.getLocations,
|
|
302
|
+
(flows, locations) => {
|
|
303
|
+
if (!locations || !flows) return locations;
|
|
304
|
+
const withFlows = new Set();
|
|
305
|
+
for (const flow of flows) {
|
|
306
|
+
withFlows.add(this.accessors.getFlowOriginId(flow));
|
|
307
|
+
withFlows.add(this.accessors.getFlowDestId(flow));
|
|
308
|
+
}
|
|
309
|
+
const filtered = [];
|
|
310
|
+
for (const location of locations) {
|
|
311
|
+
if (withFlows.has(this.accessors.getLocationId(location))) {
|
|
312
|
+
filtered.push(location);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return filtered;
|
|
316
|
+
},
|
|
317
|
+
);
|
|
286
318
|
|
|
287
|
-
getLocationsById: Selector<L, F, Map<string, L> | undefined> =
|
|
288
|
-
this.getLocationsHavingFlows,
|
|
289
|
-
(locations) => {
|
|
319
|
+
getLocationsById: Selector<L, F, Map<string | number, L> | undefined> =
|
|
320
|
+
createSelector(this.getLocationsHavingFlows, (locations) => {
|
|
290
321
|
if (!locations) return undefined;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
|
|
299
|
-
this.getLocationsHavingFlows,
|
|
300
|
-
this.getLocationsById,
|
|
301
|
-
this.getSortedFlowsForKnownLocations,
|
|
302
|
-
(locations, locationsById, flows) => {
|
|
303
|
-
if (!locations || !locationsById || !flows) return undefined;
|
|
322
|
+
const locationsById = new Map<string | number, L>();
|
|
323
|
+
for (const location of locations) {
|
|
324
|
+
locationsById.set(this.accessors.getLocationId(location), location);
|
|
325
|
+
}
|
|
326
|
+
return locationsById;
|
|
327
|
+
});
|
|
304
328
|
|
|
329
|
+
getLocationWeightGetter: Selector<L, F, LocationWeightGetter | undefined> =
|
|
330
|
+
createSelector(this.getSortedFlowsForKnownLocations, (flows) => {
|
|
331
|
+
if (!flows) return undefined;
|
|
305
332
|
const getLocationWeight = makeLocationWeightGetter(
|
|
306
333
|
flows,
|
|
307
|
-
this.accessors.
|
|
334
|
+
this.accessors.getFlowmapDataAccessors(),
|
|
308
335
|
);
|
|
336
|
+
return getLocationWeight;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
getClusterLevels: Selector<L, F, ClusterLevels | undefined> = createSelector(
|
|
340
|
+
this.getClusterLevelsFromProps,
|
|
341
|
+
this.getLocationsHavingFlows,
|
|
342
|
+
this.getLocationWeightGetter,
|
|
343
|
+
(clusterLevelsFromProps, locations, getLocationWeight) => {
|
|
344
|
+
if (clusterLevelsFromProps) return clusterLevelsFromProps;
|
|
345
|
+
if (!locations || !getLocationWeight) return undefined;
|
|
309
346
|
const clusterLevels = clusterLocations(
|
|
310
347
|
locations,
|
|
311
|
-
this.accessors.
|
|
348
|
+
this.accessors.getFlowmapDataAccessors(),
|
|
312
349
|
getLocationWeight,
|
|
313
350
|
{
|
|
314
351
|
maxZoom: MAX_CLUSTER_ZOOM_LEVEL,
|
|
315
352
|
},
|
|
316
353
|
);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// Adding meaningful names
|
|
322
|
-
const getName = (id: string) => {
|
|
323
|
-
const loc = locationsById.get(id);
|
|
324
|
-
if (loc) {
|
|
325
|
-
return getLocationName
|
|
326
|
-
? getLocationName(loc)
|
|
327
|
-
: this.accessors.getLocationId(loc) || id;
|
|
328
|
-
}
|
|
329
|
-
return `"${id}"`;
|
|
330
|
-
};
|
|
331
|
-
for (const level of clusterLevels) {
|
|
332
|
-
for (const node of level.nodes) {
|
|
333
|
-
// Here mutating the nodes (adding names)
|
|
334
|
-
if (isCluster(node)) {
|
|
335
|
-
const leaves = clusterIndex.expandCluster(node);
|
|
336
|
-
|
|
337
|
-
leaves.sort((a, b) =>
|
|
338
|
-
descending(getLocationWeight(a), getLocationWeight(b)),
|
|
339
|
-
);
|
|
354
|
+
return clusterLevels;
|
|
355
|
+
},
|
|
356
|
+
);
|
|
340
357
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
? `"${getName(otherId)}"`
|
|
349
|
-
: `${leaves.length - 1} others`
|
|
350
|
-
}`;
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
(node as any).name = getName(node.id);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
358
|
+
getClusterIndex: Selector<L, F, ClusterIndex<F> | undefined> = createSelector(
|
|
359
|
+
this.getLocationsById,
|
|
360
|
+
this.getLocationWeightGetter,
|
|
361
|
+
this.getClusterLevels,
|
|
362
|
+
(locationsById, getLocationWeight, clusterLevels) => {
|
|
363
|
+
if (!locationsById || !getLocationWeight || !clusterLevels)
|
|
364
|
+
return undefined;
|
|
357
365
|
|
|
366
|
+
const clusterIndex = buildIndex<F>(clusterLevels);
|
|
367
|
+
// Adding meaningful names
|
|
368
|
+
addClusterNames(
|
|
369
|
+
clusterIndex,
|
|
370
|
+
clusterLevels,
|
|
371
|
+
locationsById,
|
|
372
|
+
this.accessors.getFlowmapDataAccessors(),
|
|
373
|
+
getLocationWeight,
|
|
374
|
+
);
|
|
358
375
|
return clusterIndex;
|
|
359
376
|
},
|
|
360
377
|
);
|
|
@@ -399,7 +416,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
399
416
|
this.getAvailableClusterZoomLevels,
|
|
400
417
|
(clusterIndex, mapZoom, availableClusterZoomLevels) => {
|
|
401
418
|
if (!clusterIndex) return undefined;
|
|
402
|
-
if (!availableClusterZoomLevels) {
|
|
419
|
+
if (!availableClusterZoomLevels || mapZoom == null) {
|
|
403
420
|
return undefined;
|
|
404
421
|
}
|
|
405
422
|
|
|
@@ -411,13 +428,13 @@ export default class FlowMapSelectors<L, F> {
|
|
|
411
428
|
},
|
|
412
429
|
);
|
|
413
430
|
|
|
414
|
-
getClusterZoom = (state:
|
|
415
|
-
const {
|
|
416
|
-
if (!
|
|
417
|
-
if (
|
|
431
|
+
getClusterZoom = (state: FlowmapState, props: FlowmapData<L, F>) => {
|
|
432
|
+
const {settings} = state;
|
|
433
|
+
if (!settings.clusteringEnabled) return undefined;
|
|
434
|
+
if (settings.clusteringAuto || settings.clusteringLevel == null) {
|
|
418
435
|
return this._getClusterZoom(state, props);
|
|
419
436
|
}
|
|
420
|
-
return
|
|
437
|
+
return settings.clusteringLevel;
|
|
421
438
|
};
|
|
422
439
|
|
|
423
440
|
getLocationsForSearchBox: Selector<L, F, (L | Cluster)[] | undefined> =
|
|
@@ -435,7 +452,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
435
452
|
clusterIndex,
|
|
436
453
|
) => {
|
|
437
454
|
if (!locations) return undefined;
|
|
438
|
-
let result: (L | Cluster)[] = locations;
|
|
455
|
+
let result: (L | Cluster)[] = Array.from(locations);
|
|
439
456
|
// if (clusteringEnabled) {
|
|
440
457
|
// if (clusterIndex) {
|
|
441
458
|
// const zoomItems = clusterIndex.getClusterNodesFor(clusterZoom);
|
|
@@ -445,7 +462,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
445
462
|
// }
|
|
446
463
|
// }
|
|
447
464
|
|
|
448
|
-
if (
|
|
465
|
+
if (clusterIndex && selectedLocations) {
|
|
449
466
|
const toAppend = [];
|
|
450
467
|
for (const id of selectedLocations) {
|
|
451
468
|
const cluster = clusterIndex.getClusterById(id);
|
|
@@ -470,58 +487,61 @@ export default class FlowMapSelectors<L, F> {
|
|
|
470
487
|
);
|
|
471
488
|
|
|
472
489
|
getDiffMode: Selector<L, F, boolean> = createSelector(
|
|
473
|
-
this.
|
|
490
|
+
this.getFlowsFromProps,
|
|
474
491
|
(flows) => {
|
|
475
|
-
if (
|
|
476
|
-
flows
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
492
|
+
if (flows) {
|
|
493
|
+
for (const f of flows) {
|
|
494
|
+
if (this.accessors.getFlowMagnitude(f) < 0) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
480
498
|
}
|
|
481
499
|
return false;
|
|
482
500
|
},
|
|
483
501
|
);
|
|
484
502
|
|
|
485
|
-
|
|
503
|
+
_getFlowmapColors = createSelector(
|
|
486
504
|
this.getDiffMode,
|
|
487
|
-
this.
|
|
505
|
+
this.getColorScheme,
|
|
488
506
|
this.getDarkMode,
|
|
489
507
|
this.getFadeEnabled,
|
|
508
|
+
this.getFadeOpacityEnabled,
|
|
490
509
|
this.getFadeAmount,
|
|
491
510
|
this.getAnimate,
|
|
492
511
|
getColors,
|
|
493
512
|
);
|
|
494
513
|
|
|
495
|
-
|
|
496
|
-
this.
|
|
497
|
-
(
|
|
498
|
-
return isDiffColors(
|
|
499
|
-
? getDiffColorsRGBA(
|
|
500
|
-
: getColorsRGBA(
|
|
514
|
+
getFlowmapColorsRGBA = createSelector(
|
|
515
|
+
this._getFlowmapColors,
|
|
516
|
+
(flowmapColors) => {
|
|
517
|
+
return isDiffColors(flowmapColors)
|
|
518
|
+
? getDiffColorsRGBA(flowmapColors)
|
|
519
|
+
: getColorsRGBA(flowmapColors);
|
|
501
520
|
},
|
|
502
521
|
);
|
|
503
522
|
|
|
504
|
-
getUnknownLocations: Selector<L, F, Set<string> | undefined> =
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
523
|
+
getUnknownLocations: Selector<L, F, Set<string | number> | undefined> =
|
|
524
|
+
createSelector(
|
|
525
|
+
this.getLocationIds,
|
|
526
|
+
this.getFlowsFromProps,
|
|
527
|
+
this.getSortedFlowsForKnownLocations,
|
|
528
|
+
(ids, flows, flowsForKnownLocations) => {
|
|
529
|
+
if (!ids || !flows) return undefined;
|
|
530
|
+
if (
|
|
531
|
+
flowsForKnownLocations
|
|
532
|
+
// && flows.length === flowsForKnownLocations.length
|
|
533
|
+
)
|
|
534
|
+
return undefined;
|
|
535
|
+
const missing = new Set<string | number>();
|
|
536
|
+
for (const flow of flows) {
|
|
537
|
+
if (!ids.has(this.accessors.getFlowOriginId(flow)))
|
|
538
|
+
missing.add(this.accessors.getFlowOriginId(flow));
|
|
539
|
+
if (!ids.has(this.accessors.getFlowDestId(flow)))
|
|
540
|
+
missing.add(this.accessors.getFlowDestId(flow));
|
|
541
|
+
}
|
|
542
|
+
return missing;
|
|
543
|
+
},
|
|
544
|
+
);
|
|
525
545
|
|
|
526
546
|
getSortedAggregatedFilteredFlows: Selector<
|
|
527
547
|
L,
|
|
@@ -544,12 +564,12 @@ export default class FlowMapSelectors<L, F> {
|
|
|
544
564
|
// : flows,
|
|
545
565
|
flows,
|
|
546
566
|
clusterZoom,
|
|
547
|
-
this.accessors.
|
|
567
|
+
this.accessors.getFlowmapDataAccessors(),
|
|
548
568
|
);
|
|
549
569
|
} else {
|
|
550
570
|
aggregated = aggregateFlows(
|
|
551
571
|
flows,
|
|
552
|
-
this.accessors.
|
|
572
|
+
this.accessors.getFlowmapDataAccessors(),
|
|
553
573
|
);
|
|
554
574
|
}
|
|
555
575
|
aggregated.sort((a, b) =>
|
|
@@ -562,33 +582,6 @@ export default class FlowMapSelectors<L, F> {
|
|
|
562
582
|
},
|
|
563
583
|
);
|
|
564
584
|
|
|
565
|
-
getFlowMagnitudeExtent: Selector<L, F, [number, number] | undefined> =
|
|
566
|
-
createSelector(
|
|
567
|
-
this.getSortedAggregatedFilteredFlows,
|
|
568
|
-
this.getSelectedLocationsSet,
|
|
569
|
-
this.getLocationFilterMode,
|
|
570
|
-
(flows, selectedLocationsSet, locationFilterMode) => {
|
|
571
|
-
if (!flows) return undefined;
|
|
572
|
-
let rv: [number, number] | undefined = undefined;
|
|
573
|
-
for (const f of flows) {
|
|
574
|
-
if (
|
|
575
|
-
this.accessors.getFlowOriginId(f) !==
|
|
576
|
-
this.accessors.getFlowDestId(f) &&
|
|
577
|
-
this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
|
|
578
|
-
) {
|
|
579
|
-
const count = this.accessors.getFlowMagnitude(f);
|
|
580
|
-
if (rv == null) {
|
|
581
|
-
rv = [count, count];
|
|
582
|
-
} else {
|
|
583
|
-
if (count < rv[0]) rv[0] = count;
|
|
584
|
-
if (count > rv[1]) rv[1] = count;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return rv;
|
|
589
|
-
},
|
|
590
|
-
);
|
|
591
|
-
|
|
592
585
|
getExpandedSelectedLocationsSet: Selector<L, F, Set<string> | undefined> =
|
|
593
586
|
createSelector(
|
|
594
587
|
this.getClusteringEnabled,
|
|
@@ -668,18 +661,10 @@ export default class FlowMapSelectors<L, F> {
|
|
|
668
661
|
createSelector(
|
|
669
662
|
this.getViewport,
|
|
670
663
|
this.getMaxLocationCircleSize,
|
|
671
|
-
|
|
672
|
-
const pad = maxLocationCircleSize;
|
|
673
|
-
return bounds(
|
|
674
|
-
[viewport.longitude, viewport.latitude],
|
|
675
|
-
viewport.zoom,
|
|
676
|
-
[viewport.width + pad * 2, viewport.height + pad * 2],
|
|
677
|
-
512,
|
|
678
|
-
);
|
|
679
|
-
},
|
|
664
|
+
getViewportBoundingBox,
|
|
680
665
|
);
|
|
681
666
|
|
|
682
|
-
getLocationsForZoom: Selector<L, F, L
|
|
667
|
+
getLocationsForZoom: Selector<L, F, Iterable<L> | ClusterNode[] | undefined> =
|
|
683
668
|
createSelector(
|
|
684
669
|
this.getClusteringEnabled,
|
|
685
670
|
this.getLocationsHavingFlows,
|
|
@@ -694,47 +679,50 @@ export default class FlowMapSelectors<L, F> {
|
|
|
694
679
|
},
|
|
695
680
|
);
|
|
696
681
|
|
|
697
|
-
getLocationTotals: Selector<
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (d.internalCount != null) rv.internalCount += d.internalCount;
|
|
718
|
-
return rv;
|
|
682
|
+
getLocationTotals: Selector<
|
|
683
|
+
L,
|
|
684
|
+
F,
|
|
685
|
+
Map<string | number, LocationTotals> | undefined
|
|
686
|
+
> = createSelector(
|
|
687
|
+
this.getLocationsForZoom,
|
|
688
|
+
this.getSortedAggregatedFilteredFlows,
|
|
689
|
+
this.getSelectedLocationsSet,
|
|
690
|
+
this.getLocationFilterMode,
|
|
691
|
+
(locations, flows, selectedLocationsSet, locationFilterMode) => {
|
|
692
|
+
if (!flows) return undefined;
|
|
693
|
+
const totals = new Map<string | number, LocationTotals>();
|
|
694
|
+
const add = (
|
|
695
|
+
id: string | number,
|
|
696
|
+
d: Partial<LocationTotals>,
|
|
697
|
+
): LocationTotals => {
|
|
698
|
+
const rv = totals.get(id) ?? {
|
|
699
|
+
incomingCount: 0,
|
|
700
|
+
outgoingCount: 0,
|
|
701
|
+
internalCount: 0,
|
|
719
702
|
};
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
703
|
+
if (d.incomingCount != null) rv.incomingCount += d.incomingCount;
|
|
704
|
+
if (d.outgoingCount != null) rv.outgoingCount += d.outgoingCount;
|
|
705
|
+
if (d.internalCount != null) rv.internalCount += d.internalCount;
|
|
706
|
+
return rv;
|
|
707
|
+
};
|
|
708
|
+
for (const f of flows) {
|
|
709
|
+
if (
|
|
710
|
+
this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
|
|
711
|
+
) {
|
|
712
|
+
const originId = this.accessors.getFlowOriginId(f);
|
|
713
|
+
const destId = this.accessors.getFlowDestId(f);
|
|
714
|
+
const count = this.accessors.getFlowMagnitude(f);
|
|
715
|
+
if (originId === destId) {
|
|
716
|
+
totals.set(originId, add(originId, {internalCount: count}));
|
|
717
|
+
} else {
|
|
718
|
+
totals.set(originId, add(originId, {outgoingCount: count}));
|
|
719
|
+
totals.set(destId, add(destId, {incomingCount: count}));
|
|
733
720
|
}
|
|
734
721
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
722
|
+
}
|
|
723
|
+
return totals;
|
|
724
|
+
},
|
|
725
|
+
);
|
|
738
726
|
|
|
739
727
|
getLocationsTree: Selector<L, F, KDBushTree> = createSelector(
|
|
740
728
|
this.getLocationsForZoom,
|
|
@@ -746,17 +734,9 @@ export default class FlowMapSelectors<L, F> {
|
|
|
746
734
|
// @ts-ignore
|
|
747
735
|
locations,
|
|
748
736
|
(location: L | ClusterNode) =>
|
|
749
|
-
lngX(
|
|
750
|
-
isLocationClusterNode(location)
|
|
751
|
-
? location.centroid[0]
|
|
752
|
-
: this.accessors.getLocationCentroid(location)[0],
|
|
753
|
-
),
|
|
737
|
+
lngX(this.accessors.getLocationLon(location)),
|
|
754
738
|
(location: L | ClusterNode) =>
|
|
755
|
-
latY(
|
|
756
|
-
isLocationClusterNode(location)
|
|
757
|
-
? location.centroid[1]
|
|
758
|
-
: this.accessors.getLocationCentroid(location)[1],
|
|
759
|
-
),
|
|
739
|
+
latY(this.accessors.getLocationLat(location)),
|
|
760
740
|
);
|
|
761
741
|
},
|
|
762
742
|
);
|
|
@@ -776,7 +756,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
776
756
|
},
|
|
777
757
|
);
|
|
778
758
|
|
|
779
|
-
getLocationIdsInViewport: Selector<L, F, Set<string> | undefined> =
|
|
759
|
+
getLocationIdsInViewport: Selector<L, F, Set<string | number> | undefined> =
|
|
780
760
|
createSelectorCreator(
|
|
781
761
|
defaultMemoize,
|
|
782
762
|
// @ts-ignore
|
|
@@ -845,27 +825,29 @@ export default class FlowMapSelectors<L, F> {
|
|
|
845
825
|
);
|
|
846
826
|
|
|
847
827
|
getLocationTotalsExtent = (
|
|
848
|
-
state:
|
|
849
|
-
props:
|
|
828
|
+
state: FlowmapState,
|
|
829
|
+
props: FlowmapData<L, F>,
|
|
850
830
|
): [number, number] | undefined => {
|
|
851
|
-
if (state.
|
|
831
|
+
if (state.settings.adaptiveScalesEnabled) {
|
|
852
832
|
return this._getLocationTotalsForViewportExtent(state, props);
|
|
853
833
|
} else {
|
|
854
834
|
return this._getLocationTotalsExtent(state, props);
|
|
855
835
|
}
|
|
856
836
|
};
|
|
857
837
|
|
|
858
|
-
|
|
838
|
+
getFlowsForFlowmapLayer: Selector<L, F, (F | AggregateFlow)[] | undefined> =
|
|
859
839
|
createSelector(
|
|
860
840
|
this.getSortedAggregatedFilteredFlows,
|
|
861
841
|
this.getLocationIdsInViewport,
|
|
862
842
|
this.getSelectedLocationsSet,
|
|
863
843
|
this.getLocationFilterMode,
|
|
844
|
+
this.getMaxTopFlowsDisplayNum,
|
|
864
845
|
(
|
|
865
846
|
flows,
|
|
866
847
|
locationIdsInViewport,
|
|
867
848
|
selectedLocationsSet,
|
|
868
849
|
locationFilterMode,
|
|
850
|
+
maxTopFlowsDisplayNum,
|
|
869
851
|
) => {
|
|
870
852
|
if (!flows || !locationIdsInViewport) return undefined;
|
|
871
853
|
const picked: (F | AggregateFlow)[] = [];
|
|
@@ -892,7 +874,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
892
874
|
}
|
|
893
875
|
}
|
|
894
876
|
// Only keep top
|
|
895
|
-
if (pickedCount >
|
|
877
|
+
if (pickedCount > maxTopFlowsDisplayNum) break;
|
|
896
878
|
}
|
|
897
879
|
// assuming they are sorted in descending order,
|
|
898
880
|
// we need ascending for rendering
|
|
@@ -900,20 +882,51 @@ export default class FlowMapSelectors<L, F> {
|
|
|
900
882
|
},
|
|
901
883
|
);
|
|
902
884
|
|
|
903
|
-
_getFlowMagnitudeExtent =
|
|
904
|
-
|
|
905
|
-
|
|
885
|
+
_getFlowMagnitudeExtent: Selector<L, F, [number, number] | undefined> =
|
|
886
|
+
createSelector(
|
|
887
|
+
this.getSortedAggregatedFilteredFlows,
|
|
888
|
+
this.getSelectedLocationsSet,
|
|
889
|
+
this.getLocationFilterMode,
|
|
890
|
+
(flows, selectedLocationsSet, locationFilterMode) => {
|
|
891
|
+
if (!flows) return undefined;
|
|
892
|
+
let rv: [number, number] | undefined = undefined;
|
|
893
|
+
for (const f of flows) {
|
|
894
|
+
if (
|
|
895
|
+
this.accessors.getFlowOriginId(f) !==
|
|
896
|
+
this.accessors.getFlowDestId(f) &&
|
|
897
|
+
this.isFlowInSelection(f, selectedLocationsSet, locationFilterMode)
|
|
898
|
+
) {
|
|
899
|
+
const count = this.accessors.getFlowMagnitude(f);
|
|
900
|
+
if (rv == null) {
|
|
901
|
+
rv = [count, count];
|
|
902
|
+
} else {
|
|
903
|
+
if (count < rv[0]) rv[0] = count;
|
|
904
|
+
if (count > rv[1]) rv[1] = count;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return rv;
|
|
909
|
+
},
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
_getAdaptiveFlowMagnitudeExtent: Selector<
|
|
913
|
+
L,
|
|
914
|
+
F,
|
|
915
|
+
[number, number] | undefined
|
|
916
|
+
> = createSelector(this.getFlowsForFlowmapLayer, (flows) => {
|
|
917
|
+
if (!flows) return undefined;
|
|
918
|
+
const rv = extent(flows, this.accessors.getFlowMagnitude);
|
|
919
|
+
return rv[0] !== undefined && rv[1] !== undefined ? rv : undefined;
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
getFlowMagnitudeExtent = (
|
|
923
|
+
state: FlowmapState,
|
|
924
|
+
props: FlowmapData<L, F>,
|
|
906
925
|
): [number, number] | undefined => {
|
|
907
|
-
if (state.
|
|
908
|
-
|
|
909
|
-
if (flows) {
|
|
910
|
-
const rv = extent(flows, this.accessors.getFlowMagnitude);
|
|
911
|
-
return rv[0] !== undefined && rv[1] !== undefined ? rv : undefined;
|
|
912
|
-
} else {
|
|
913
|
-
return undefined;
|
|
914
|
-
}
|
|
926
|
+
if (state.settings.adaptiveScalesEnabled) {
|
|
927
|
+
return this._getAdaptiveFlowMagnitudeExtent(state, props);
|
|
915
928
|
} else {
|
|
916
|
-
return this.
|
|
929
|
+
return this._getFlowMagnitudeExtent(state, props);
|
|
917
930
|
}
|
|
918
931
|
};
|
|
919
932
|
|
|
@@ -933,19 +946,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
933
946
|
|
|
934
947
|
getFlowThicknessScale = createSelector(
|
|
935
948
|
this.getFlowMagnitudeExtent,
|
|
936
|
-
|
|
937
|
-
if (!magnitudeExtent) return undefined;
|
|
938
|
-
return scaleLinear()
|
|
939
|
-
.range([0.025, 0.5])
|
|
940
|
-
.domain([
|
|
941
|
-
0,
|
|
942
|
-
// should support diff mode too
|
|
943
|
-
Math.max.apply(
|
|
944
|
-
null,
|
|
945
|
-
magnitudeExtent.map((x: number | undefined) => Math.abs(x || 0)),
|
|
946
|
-
),
|
|
947
|
-
]);
|
|
948
|
-
},
|
|
949
|
+
getFlowThicknessScale,
|
|
949
950
|
);
|
|
950
951
|
|
|
951
952
|
getCircleSizeScale = createSelector(
|
|
@@ -976,7 +977,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
976
977
|
this.getCircleSizeScale,
|
|
977
978
|
this.getLocationTotals,
|
|
978
979
|
(circleSizeScale, locationTotals) => {
|
|
979
|
-
return (locationId: string) => {
|
|
980
|
+
return (locationId: string | number) => {
|
|
980
981
|
const total = locationTotals?.get(locationId);
|
|
981
982
|
if (total && circleSizeScale) {
|
|
982
983
|
return (
|
|
@@ -994,7 +995,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
994
995
|
this.getCircleSizeScale,
|
|
995
996
|
this.getLocationTotals,
|
|
996
997
|
(circleSizeScale, locationTotals) => {
|
|
997
|
-
return (locationId: string) => {
|
|
998
|
+
return (locationId: string | number) => {
|
|
998
999
|
const total = locationTotals?.get(locationId);
|
|
999
1000
|
if (total && circleSizeScale) {
|
|
1000
1001
|
return (
|
|
@@ -1027,7 +1028,7 @@ export default class FlowMapSelectors<L, F> {
|
|
|
1027
1028
|
},
|
|
1028
1029
|
);
|
|
1029
1030
|
|
|
1030
|
-
|
|
1031
|
+
getLocationsForFlowmapLayer: Selector<
|
|
1031
1032
|
L,
|
|
1032
1033
|
F,
|
|
1033
1034
|
Array<L | ClusterNode> | undefined
|
|
@@ -1057,11 +1058,11 @@ export default class FlowMapSelectors<L, F> {
|
|
|
1057
1058
|
},
|
|
1058
1059
|
);
|
|
1059
1060
|
|
|
1060
|
-
|
|
1061
|
+
getLocationsForFlowmapLayerById: Selector<
|
|
1061
1062
|
L,
|
|
1062
1063
|
F,
|
|
1063
1064
|
Map<string, L | ClusterNode> | undefined
|
|
1064
|
-
> = createSelector(this.
|
|
1065
|
+
> = createSelector(this.getLocationsForFlowmapLayer, (locations) => {
|
|
1065
1066
|
if (!locations) return undefined;
|
|
1066
1067
|
return locations.reduce(
|
|
1067
1068
|
(m, d) => (m.set(this.accessors.getLocationId(d), d), m),
|
|
@@ -1069,73 +1070,88 @@ export default class FlowMapSelectors<L, F> {
|
|
|
1069
1070
|
);
|
|
1070
1071
|
});
|
|
1071
1072
|
|
|
1073
|
+
getLocationOrClusterByIdGetter = createSelector(
|
|
1074
|
+
this.getClusterIndex,
|
|
1075
|
+
this.getLocationsById,
|
|
1076
|
+
(clusterIndex, locationsById) => {
|
|
1077
|
+
return (id: string | number) =>
|
|
1078
|
+
clusterIndex?.getClusterById(id) ?? locationsById?.get(id);
|
|
1079
|
+
},
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1072
1082
|
getLayersData: Selector<L, F, LayersData> = createSelector(
|
|
1073
|
-
this.
|
|
1074
|
-
this.
|
|
1075
|
-
this.
|
|
1076
|
-
this.
|
|
1083
|
+
this.getLocationsForFlowmapLayer,
|
|
1084
|
+
this.getFlowsForFlowmapLayer,
|
|
1085
|
+
this.getFlowmapColorsRGBA,
|
|
1086
|
+
this.getLocationsForFlowmapLayerById,
|
|
1077
1087
|
this.getLocationIdsInViewport,
|
|
1078
1088
|
this.getInCircleSizeGetter,
|
|
1079
1089
|
this.getOutCircleSizeGetter,
|
|
1080
1090
|
this.getFlowThicknessScale,
|
|
1081
1091
|
this.getAnimate,
|
|
1092
|
+
this.getLocationLabelsEnabled,
|
|
1082
1093
|
(
|
|
1083
1094
|
locations,
|
|
1084
1095
|
flows,
|
|
1085
|
-
|
|
1096
|
+
flowmapColors,
|
|
1086
1097
|
locationsById,
|
|
1087
1098
|
locationIdsInViewport,
|
|
1088
1099
|
getInCircleSize,
|
|
1089
1100
|
getOutCircleSize,
|
|
1090
1101
|
flowThicknessScale,
|
|
1091
1102
|
animationEnabled,
|
|
1103
|
+
locationLabelsEnabled,
|
|
1092
1104
|
) => {
|
|
1093
1105
|
return this._prepareLayersData(
|
|
1094
1106
|
locations,
|
|
1095
1107
|
flows,
|
|
1096
|
-
|
|
1108
|
+
flowmapColors,
|
|
1097
1109
|
locationsById,
|
|
1098
1110
|
locationIdsInViewport,
|
|
1099
1111
|
getInCircleSize,
|
|
1100
1112
|
getOutCircleSize,
|
|
1101
1113
|
flowThicknessScale,
|
|
1102
1114
|
animationEnabled,
|
|
1115
|
+
locationLabelsEnabled,
|
|
1103
1116
|
);
|
|
1104
1117
|
},
|
|
1105
1118
|
);
|
|
1106
1119
|
|
|
1107
|
-
prepareLayersData(state:
|
|
1108
|
-
const locations = this.
|
|
1109
|
-
const flows = this.
|
|
1110
|
-
const
|
|
1111
|
-
const locationsById = this.
|
|
1120
|
+
prepareLayersData(state: FlowmapState, props: FlowmapData<L, F>): LayersData {
|
|
1121
|
+
const locations = this.getLocationsForFlowmapLayer(state, props) || [];
|
|
1122
|
+
const flows = this.getFlowsForFlowmapLayer(state, props) || [];
|
|
1123
|
+
const flowmapColors = this.getFlowmapColorsRGBA(state, props);
|
|
1124
|
+
const locationsById = this.getLocationsForFlowmapLayerById(state, props);
|
|
1112
1125
|
const locationIdsInViewport = this.getLocationIdsInViewport(state, props);
|
|
1113
1126
|
const getInCircleSize = this.getInCircleSizeGetter(state, props);
|
|
1114
1127
|
const getOutCircleSize = this.getOutCircleSizeGetter(state, props);
|
|
1115
1128
|
const flowThicknessScale = this.getFlowThicknessScale(state, props);
|
|
1129
|
+
const locationLabelsEnabled = this.getLocationLabelsEnabled(state, props);
|
|
1116
1130
|
return this._prepareLayersData(
|
|
1117
1131
|
locations,
|
|
1118
1132
|
flows,
|
|
1119
|
-
|
|
1133
|
+
flowmapColors,
|
|
1120
1134
|
locationsById,
|
|
1121
1135
|
locationIdsInViewport,
|
|
1122
1136
|
getInCircleSize,
|
|
1123
1137
|
getOutCircleSize,
|
|
1124
1138
|
flowThicknessScale,
|
|
1125
|
-
state.
|
|
1139
|
+
state.settings.animationEnabled,
|
|
1140
|
+
locationLabelsEnabled,
|
|
1126
1141
|
);
|
|
1127
1142
|
}
|
|
1128
1143
|
|
|
1129
1144
|
_prepareLayersData(
|
|
1130
1145
|
locations: (L | ClusterNode)[] | undefined,
|
|
1131
1146
|
flows: (F | AggregateFlow)[] | undefined,
|
|
1132
|
-
|
|
1133
|
-
locationsById: Map<string, L | ClusterNode> | undefined,
|
|
1134
|
-
locationIdsInViewport: Set<string> | undefined,
|
|
1135
|
-
getInCircleSize: (locationId: string) => number,
|
|
1136
|
-
getOutCircleSize: (locationId: string) => number,
|
|
1147
|
+
flowmapColors: DiffColorsRGBA | ColorsRGBA,
|
|
1148
|
+
locationsById: Map<string | number, L | ClusterNode> | undefined,
|
|
1149
|
+
locationIdsInViewport: Set<string | number> | undefined,
|
|
1150
|
+
getInCircleSize: (locationId: string | number) => number,
|
|
1151
|
+
getOutCircleSize: (locationId: string | number) => number,
|
|
1137
1152
|
flowThicknessScale: ScaleLinear<number, number, never> | undefined,
|
|
1138
1153
|
animationEnabled: boolean,
|
|
1154
|
+
locationLabelsEnabled: boolean,
|
|
1139
1155
|
): LayersData {
|
|
1140
1156
|
if (!locations) locations = [];
|
|
1141
1157
|
if (!flows) flows = [];
|
|
@@ -1144,80 +1160,114 @@ export default class FlowMapSelectors<L, F> {
|
|
|
1144
1160
|
getFlowDestId,
|
|
1145
1161
|
getFlowMagnitude,
|
|
1146
1162
|
getLocationId,
|
|
1147
|
-
|
|
1163
|
+
getLocationLon,
|
|
1164
|
+
getLocationLat,
|
|
1165
|
+
getLocationName,
|
|
1148
1166
|
} = this.accessors;
|
|
1149
1167
|
|
|
1150
|
-
const getCentroid = (id: string) => {
|
|
1151
|
-
const loc = locationsById?.get(id);
|
|
1152
|
-
return loc ? getLocationCentroid(loc) : [0, 0];
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
1168
|
const flowMagnitudeExtent = extent(flows, (f) => getFlowMagnitude(f)) as [
|
|
1156
1169
|
number,
|
|
1157
1170
|
number,
|
|
1158
1171
|
];
|
|
1159
1172
|
const flowColorScale = getFlowColorScale(
|
|
1160
|
-
|
|
1173
|
+
flowmapColors,
|
|
1161
1174
|
flowMagnitudeExtent,
|
|
1162
1175
|
false,
|
|
1163
1176
|
);
|
|
1164
1177
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1178
|
+
// Using a generator here helps to avoid creating intermediary arrays
|
|
1179
|
+
const circlePositions = Float32Array.from(
|
|
1180
|
+
(function* () {
|
|
1181
|
+
for (const location of locations) {
|
|
1182
|
+
yield getLocationLon(location);
|
|
1183
|
+
yield getLocationLat(location);
|
|
1184
|
+
}
|
|
1185
|
+
})(),
|
|
1167
1186
|
);
|
|
1168
1187
|
|
|
1169
1188
|
// TODO: diff mode
|
|
1170
|
-
const circleColor = isDiffColorsRGBA(
|
|
1171
|
-
?
|
|
1172
|
-
:
|
|
1173
|
-
|
|
1174
|
-
const circleColors =
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}),
|
|
1189
|
+
const circleColor = isDiffColorsRGBA(flowmapColors)
|
|
1190
|
+
? flowmapColors.positive.locationCircles.inner
|
|
1191
|
+
: flowmapColors.locationCircles.inner;
|
|
1192
|
+
|
|
1193
|
+
const circleColors = Uint8Array.from(
|
|
1194
|
+
(function* () {
|
|
1195
|
+
for (const location of locations) {
|
|
1196
|
+
yield* circleColor;
|
|
1197
|
+
}
|
|
1198
|
+
})(),
|
|
1180
1199
|
);
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1200
|
+
|
|
1201
|
+
const inCircleRadii = Float32Array.from(
|
|
1202
|
+
(function* () {
|
|
1203
|
+
for (const location of locations) {
|
|
1204
|
+
const id = getLocationId(location);
|
|
1205
|
+
yield locationIdsInViewport?.has(id) ? getInCircleSize(id) : 1.0;
|
|
1206
|
+
}
|
|
1207
|
+
})(),
|
|
1208
|
+
);
|
|
1209
|
+
const outCircleRadii = Float32Array.from(
|
|
1210
|
+
(function* () {
|
|
1211
|
+
for (const location of locations) {
|
|
1212
|
+
const id = getLocationId(location);
|
|
1213
|
+
yield locationIdsInViewport?.has(id) ? getOutCircleSize(id) : 1.0;
|
|
1214
|
+
}
|
|
1215
|
+
})(),
|
|
1186
1216
|
);
|
|
1187
1217
|
|
|
1188
|
-
const sourcePositions =
|
|
1189
|
-
|
|
1218
|
+
const sourcePositions = Float32Array.from(
|
|
1219
|
+
(function* () {
|
|
1220
|
+
for (const flow of flows) {
|
|
1221
|
+
const loc = locationsById?.get(getFlowOriginId(flow));
|
|
1222
|
+
yield loc ? getLocationLon(loc) : 0;
|
|
1223
|
+
yield loc ? getLocationLat(loc) : 0;
|
|
1224
|
+
}
|
|
1225
|
+
})(),
|
|
1190
1226
|
);
|
|
1191
|
-
const targetPositions =
|
|
1192
|
-
|
|
1227
|
+
const targetPositions = Float32Array.from(
|
|
1228
|
+
(function* () {
|
|
1229
|
+
for (const flow of flows) {
|
|
1230
|
+
const loc = locationsById?.get(getFlowDestId(flow));
|
|
1231
|
+
yield loc ? getLocationLon(loc) : 0;
|
|
1232
|
+
yield loc ? getLocationLat(loc) : 0;
|
|
1233
|
+
}
|
|
1234
|
+
})(),
|
|
1193
1235
|
);
|
|
1194
|
-
const thicknesses =
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1236
|
+
const thicknesses = Float32Array.from(
|
|
1237
|
+
(function* () {
|
|
1238
|
+
for (const flow of flows) {
|
|
1239
|
+
yield flowThicknessScale
|
|
1240
|
+
? flowThicknessScale(getFlowMagnitude(flow)) || 0
|
|
1241
|
+
: 0;
|
|
1242
|
+
}
|
|
1243
|
+
})(),
|
|
1198
1244
|
);
|
|
1199
|
-
const endpointOffsets =
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
Math.max(getInCircleSize(originId), getOutCircleSize(originId))
|
|
1205
|
-
Math.max(getInCircleSize(destId), getOutCircleSize(destId))
|
|
1206
|
-
|
|
1207
|
-
}),
|
|
1245
|
+
const endpointOffsets = Float32Array.from(
|
|
1246
|
+
(function* () {
|
|
1247
|
+
for (const flow of flows) {
|
|
1248
|
+
const originId = getFlowOriginId(flow);
|
|
1249
|
+
const destId = getFlowDestId(flow);
|
|
1250
|
+
yield Math.max(getInCircleSize(originId), getOutCircleSize(originId));
|
|
1251
|
+
yield Math.max(getInCircleSize(destId), getOutCircleSize(destId));
|
|
1252
|
+
}
|
|
1253
|
+
})(),
|
|
1208
1254
|
);
|
|
1209
|
-
const flowLineColors =
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1255
|
+
const flowLineColors = Uint8Array.from(
|
|
1256
|
+
(function* () {
|
|
1257
|
+
for (const flow of flows) {
|
|
1258
|
+
yield* flowColorScale(getFlowMagnitude(flow));
|
|
1259
|
+
}
|
|
1260
|
+
})(),
|
|
1213
1261
|
);
|
|
1214
1262
|
|
|
1215
1263
|
const staggeringValues = animationEnabled
|
|
1216
|
-
?
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1264
|
+
? Float32Array.from(
|
|
1265
|
+
(function* () {
|
|
1266
|
+
for (const f of flows) {
|
|
1267
|
+
// @ts-ignore
|
|
1268
|
+
yield new alea(`${getFlowOriginId(f)}-${getFlowDestId(f)}`)();
|
|
1269
|
+
}
|
|
1270
|
+
})(),
|
|
1221
1271
|
)
|
|
1222
1272
|
: undefined;
|
|
1223
1273
|
|
|
@@ -1244,6 +1294,9 @@ export default class FlowMapSelectors<L, F> {
|
|
|
1244
1294
|
: {}),
|
|
1245
1295
|
},
|
|
1246
1296
|
},
|
|
1297
|
+
...(locationLabelsEnabled
|
|
1298
|
+
? {locationLabels: locations.map(getLocationName)}
|
|
1299
|
+
: undefined),
|
|
1247
1300
|
};
|
|
1248
1301
|
}
|
|
1249
1302
|
|
|
@@ -1274,8 +1327,8 @@ export default class FlowMapSelectors<L, F> {
|
|
|
1274
1327
|
|
|
1275
1328
|
isFlowInSelection(
|
|
1276
1329
|
flow: F | AggregateFlow,
|
|
1277
|
-
selectedLocationsSet: Set<string> | undefined,
|
|
1278
|
-
locationFilterMode
|
|
1330
|
+
selectedLocationsSet: Set<string | number> | undefined,
|
|
1331
|
+
locationFilterMode?: LocationFilterMode,
|
|
1279
1332
|
) {
|
|
1280
1333
|
const origin = this.accessors.getFlowOriginId(flow);
|
|
1281
1334
|
const dest = this.accessors.getFlowDestId(flow);
|
|
@@ -1321,8 +1374,8 @@ export default class FlowMapSelectors<L, F> {
|
|
|
1321
1374
|
}
|
|
1322
1375
|
|
|
1323
1376
|
function calcLocationTotalsExtent(
|
|
1324
|
-
locationTotals: Map<string, LocationTotals> | undefined,
|
|
1325
|
-
locationIdsInViewport: Set<string> | undefined,
|
|
1377
|
+
locationTotals: Map<string | number, LocationTotals> | undefined,
|
|
1378
|
+
locationIdsInViewport: Set<string | number> | undefined,
|
|
1326
1379
|
) {
|
|
1327
1380
|
if (!locationTotals) return undefined;
|
|
1328
1381
|
let rv: [number, number] | undefined = undefined;
|
|
@@ -1368,10 +1421,9 @@ function aggregateFlows<F>(
|
|
|
1368
1421
|
flowAccessors: FlowAccessors<F>,
|
|
1369
1422
|
): AggregateFlow[] {
|
|
1370
1423
|
// Sum up flows with same origin, dest
|
|
1371
|
-
const byOriginDest =
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
.rollup((ff: F[]) => {
|
|
1424
|
+
const byOriginDest = rollup(
|
|
1425
|
+
flows,
|
|
1426
|
+
(ff: F[]) => {
|
|
1375
1427
|
const origin = flowAccessors.getFlowOriginId(ff[0]);
|
|
1376
1428
|
const dest = flowAccessors.getFlowDestId(ff[0]);
|
|
1377
1429
|
// const color = ff[0].color;
|
|
@@ -1390,11 +1442,14 @@ function aggregateFlows<F>(
|
|
|
1390
1442
|
};
|
|
1391
1443
|
// if (color) rv.color = color;
|
|
1392
1444
|
return rv;
|
|
1393
|
-
}
|
|
1394
|
-
.
|
|
1445
|
+
},
|
|
1446
|
+
flowAccessors.getFlowOriginId,
|
|
1447
|
+
flowAccessors.getFlowDestId,
|
|
1448
|
+
);
|
|
1449
|
+
|
|
1395
1450
|
const rv: AggregateFlow[] = [];
|
|
1396
|
-
for (const
|
|
1397
|
-
for (const
|
|
1451
|
+
for (const values of byOriginDest.values()) {
|
|
1452
|
+
for (const value of values.values()) {
|
|
1398
1453
|
rv.push(value);
|
|
1399
1454
|
}
|
|
1400
1455
|
}
|
|
@@ -1414,7 +1469,7 @@ export function getOuterCircleRadiusByIndex(
|
|
|
1414
1469
|
return Math.max(getInRadius.value[index], getOutRadius.value[index]);
|
|
1415
1470
|
}
|
|
1416
1471
|
|
|
1417
|
-
export function
|
|
1472
|
+
export function getLocationCoordsByIndex(
|
|
1418
1473
|
circleAttributes: FlowCirclesLayerAttributes,
|
|
1419
1474
|
index: number,
|
|
1420
1475
|
): [number, number] {
|