@fscharter/flowmap-data 8.0.2-fsc.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/dist/FlowmapAggregateAccessors.d.ts +16 -0
- package/dist/FlowmapAggregateAccessors.d.ts.map +1 -0
- package/dist/FlowmapAggregateAccessors.js +53 -0
- package/dist/FlowmapSelectors.d.ts +143 -0
- package/dist/FlowmapSelectors.d.ts.map +1 -0
- package/dist/FlowmapSelectors.js +881 -0
- package/dist/FlowmapState.d.ts +31 -0
- package/dist/FlowmapState.d.ts.map +1 -0
- package/dist/FlowmapState.js +7 -0
- package/dist/cluster/ClusterIndex.d.ts +42 -0
- package/dist/cluster/ClusterIndex.d.ts.map +1 -0
- package/dist/cluster/ClusterIndex.js +166 -0
- package/dist/cluster/cluster.d.ts +51 -0
- package/dist/cluster/cluster.d.ts.map +1 -0
- package/dist/cluster/cluster.js +267 -0
- package/dist/colors.d.ts +103 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +487 -0
- package/dist/getViewStateForLocations.d.ts +23 -0
- package/dist/getViewStateForLocations.d.ts.map +1 -0
- package/dist/getViewStateForLocations.js +54 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/provider/FlowmapDataProvider.d.ts +21 -0
- package/dist/provider/FlowmapDataProvider.d.ts.map +1 -0
- package/dist/provider/FlowmapDataProvider.js +22 -0
- package/dist/provider/LocalFlowmapDataProvider.d.ts +31 -0
- package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -0
- package/dist/provider/LocalFlowmapDataProvider.js +115 -0
- 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 +24 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +131 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/util.d.ts +5 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +16 -0
- package/package.json +48 -0
- package/src/FlowmapAggregateAccessors.ts +76 -0
- package/src/FlowmapSelectors.ts +1539 -0
- package/src/FlowmapState.ts +40 -0
- package/src/cluster/ClusterIndex.ts +261 -0
- package/src/cluster/cluster.ts +394 -0
- package/src/colors.ts +771 -0
- package/src/getViewStateForLocations.ts +86 -0
- package/src/index.ts +19 -0
- package/src/provider/FlowmapDataProvider.ts +81 -0
- package/src/provider/LocalFlowmapDataProvider.ts +185 -0
- package/src/selector-functions.ts +93 -0
- package/src/time.ts +166 -0
- package/src/types.ts +172 -0
- package/src/util.ts +17 -0
- package/tsconfig.json +11 -0
- package/typings.d.ts +1 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Flowmap.gl contributors
|
|
3
|
+
* Copyright (c) 2018-2020 Teralytics
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {geoBounds} from 'd3-geo';
|
|
8
|
+
import {fitBounds} from '@math.gl/web-mercator';
|
|
9
|
+
import type {
|
|
10
|
+
FeatureCollection,
|
|
11
|
+
GeometryCollection,
|
|
12
|
+
GeometryObject,
|
|
13
|
+
} from 'geojson';
|
|
14
|
+
import type {ViewState} from './types';
|
|
15
|
+
|
|
16
|
+
export type LocationProperties = any;
|
|
17
|
+
|
|
18
|
+
export type GetViewStateOptions = {
|
|
19
|
+
pad?: number; // size ratio
|
|
20
|
+
padding?: {top: number; bottom: number; left: number; right: number};
|
|
21
|
+
tileSize?: number;
|
|
22
|
+
// minZoom?: number; // not supported by fitBounds
|
|
23
|
+
maxZoom?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function getViewStateForFeatures(
|
|
27
|
+
featureCollection:
|
|
28
|
+
| FeatureCollection<GeometryObject, LocationProperties>
|
|
29
|
+
| GeometryCollection,
|
|
30
|
+
size: [number, number],
|
|
31
|
+
opts?: GetViewStateOptions,
|
|
32
|
+
): ViewState & {width: number; height: number} {
|
|
33
|
+
const {pad = 0.05, maxZoom = 100} = opts || {};
|
|
34
|
+
const bounds = geoBounds(featureCollection as any);
|
|
35
|
+
const [[x1, y1], [x2, y2]] = bounds;
|
|
36
|
+
const paddedBounds: [[number, number], [number, number]] = pad
|
|
37
|
+
? [
|
|
38
|
+
[x1 - pad * (x2 - x1), y1 - pad * (y2 - y1)],
|
|
39
|
+
[x2 + pad * (x2 - x1), y2 + pad * (y2 - y1)],
|
|
40
|
+
]
|
|
41
|
+
: bounds;
|
|
42
|
+
const [width, height] = size;
|
|
43
|
+
return {
|
|
44
|
+
...fitBounds({
|
|
45
|
+
width,
|
|
46
|
+
height,
|
|
47
|
+
bounds: paddedBounds,
|
|
48
|
+
padding: opts?.padding,
|
|
49
|
+
// minZoom,
|
|
50
|
+
maxZoom,
|
|
51
|
+
}),
|
|
52
|
+
width,
|
|
53
|
+
height,
|
|
54
|
+
bearing: 0,
|
|
55
|
+
pitch: 0,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getViewStateForLocations<L>(
|
|
60
|
+
locations: Iterable<L>,
|
|
61
|
+
getLocationCoords: (location: L) => [number, number],
|
|
62
|
+
size: [number, number],
|
|
63
|
+
opts?: GetViewStateOptions,
|
|
64
|
+
): ViewState & {width: number; height: number} {
|
|
65
|
+
const asGeometry = (location: L) => ({
|
|
66
|
+
type: 'Point',
|
|
67
|
+
coordinates: getLocationCoords(location),
|
|
68
|
+
});
|
|
69
|
+
let geometries;
|
|
70
|
+
if (Array.isArray(locations)) {
|
|
71
|
+
geometries = locations.map(asGeometry);
|
|
72
|
+
} else {
|
|
73
|
+
geometries = [];
|
|
74
|
+
for (const location of locations) {
|
|
75
|
+
geometries.push(asGeometry(location));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return getViewStateForFeatures(
|
|
79
|
+
{
|
|
80
|
+
type: 'GeometryCollection',
|
|
81
|
+
geometries,
|
|
82
|
+
} as any,
|
|
83
|
+
size,
|
|
84
|
+
opts,
|
|
85
|
+
);
|
|
86
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Flowmap.gl contributors
|
|
3
|
+
* Copyright (c) 2018-2020 Teralytics
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from './types';
|
|
8
|
+
export * from './colors';
|
|
9
|
+
export * from './FlowmapState';
|
|
10
|
+
export * from './FlowmapSelectors';
|
|
11
|
+
export * from './selector-functions';
|
|
12
|
+
export * from './time';
|
|
13
|
+
export * from './getViewStateForLocations';
|
|
14
|
+
export * from './provider/FlowmapDataProvider';
|
|
15
|
+
export * from './cluster/cluster';
|
|
16
|
+
export * from './cluster/ClusterIndex';
|
|
17
|
+
export {default as FlowmapAggregateAccessors} from './FlowmapAggregateAccessors';
|
|
18
|
+
export type {default as FlowmapDataProvider} from './provider/FlowmapDataProvider';
|
|
19
|
+
export {default as LocalFlowmapDataProvider} from './provider/LocalFlowmapDataProvider';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Flowmap.gl contributors
|
|
3
|
+
* Copyright (c) 2018-2020 Teralytics
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {AggregateFlow, Cluster, LocationAccessors, LocationTotals} from '..';
|
|
8
|
+
import {FlowmapState} from '../FlowmapState';
|
|
9
|
+
import {
|
|
10
|
+
ClusterNode,
|
|
11
|
+
FlowmapData,
|
|
12
|
+
FlowmapDataAccessors,
|
|
13
|
+
LayersData,
|
|
14
|
+
ViewportProps,
|
|
15
|
+
} from '../types';
|
|
16
|
+
|
|
17
|
+
export default interface FlowmapDataProvider<L, F> {
|
|
18
|
+
setAccessors(accessors: FlowmapDataAccessors<L, F>): void;
|
|
19
|
+
|
|
20
|
+
setFlowmapState(flowmapState: FlowmapState): Promise<void>;
|
|
21
|
+
|
|
22
|
+
// clearData(): void;
|
|
23
|
+
|
|
24
|
+
getViewportForLocations(
|
|
25
|
+
dims: [number, number],
|
|
26
|
+
): Promise<ViewportProps | undefined>;
|
|
27
|
+
|
|
28
|
+
// getFlowTotals(): Promise<FlowTotals>;
|
|
29
|
+
|
|
30
|
+
getFlowByIndex(index: number): Promise<F | AggregateFlow | undefined>;
|
|
31
|
+
|
|
32
|
+
getLocationById(id: string | number): Promise<L | Cluster | undefined>;
|
|
33
|
+
|
|
34
|
+
getLocationByIndex(idx: number): Promise<L | ClusterNode | undefined>;
|
|
35
|
+
|
|
36
|
+
getTotalsForLocation(
|
|
37
|
+
id: string | number,
|
|
38
|
+
): Promise<LocationTotals | undefined>;
|
|
39
|
+
|
|
40
|
+
// getLocationsInBbox(
|
|
41
|
+
// bbox: [number, number, number, number],
|
|
42
|
+
// ): Promise<Array<FlowLocation | ClusterNode> | undefined>;
|
|
43
|
+
|
|
44
|
+
// getLocationsForSearchBox(): Promise<(FlowLocation | ClusterNode)[] | undefined>;
|
|
45
|
+
|
|
46
|
+
getLayersData(): Promise<LayersData | undefined>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* This is to give the data provider control over when/how often layersData
|
|
50
|
+
* is updated which leads to the flowmap being redrawn.
|
|
51
|
+
*/
|
|
52
|
+
updateLayersData(
|
|
53
|
+
setLayersData: (layersData: LayersData | undefined) => void,
|
|
54
|
+
changeFlags: Record<string, boolean>,
|
|
55
|
+
): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isFlowmapData<L, F>(
|
|
59
|
+
data: Record<string, any>,
|
|
60
|
+
): data is FlowmapData<L, F> {
|
|
61
|
+
return (
|
|
62
|
+
data && data.locations && data.flows
|
|
63
|
+
// TODO: test that they are iterable
|
|
64
|
+
// Array.isArray(data.locations) &&
|
|
65
|
+
// Array.isArray(data.flows)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isFlowmapDataProvider<L, F>(
|
|
70
|
+
dataProvider: Record<string, any>,
|
|
71
|
+
): dataProvider is FlowmapDataProvider<L, F> {
|
|
72
|
+
return (
|
|
73
|
+
dataProvider &&
|
|
74
|
+
typeof dataProvider.setFlowmapState === 'function' &&
|
|
75
|
+
typeof dataProvider.getViewportForLocations === 'function' &&
|
|
76
|
+
typeof dataProvider.getFlowByIndex === 'function' &&
|
|
77
|
+
typeof dataProvider.getLocationById === 'function' &&
|
|
78
|
+
typeof dataProvider.getLocationByIndex === 'function' &&
|
|
79
|
+
typeof dataProvider.getLayersData === 'function'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Flowmap.gl contributors
|
|
3
|
+
* Copyright (c) 2018-2020 Teralytics
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type FlowmapDataProvider from './FlowmapDataProvider';
|
|
8
|
+
import type {
|
|
9
|
+
Cluster,
|
|
10
|
+
ClusterNode,
|
|
11
|
+
FlowmapData,
|
|
12
|
+
FlowmapDataAccessors,
|
|
13
|
+
LayersData,
|
|
14
|
+
LocationTotals,
|
|
15
|
+
ViewportProps,
|
|
16
|
+
AggregateFlow,
|
|
17
|
+
} from '../types';
|
|
18
|
+
import {FlowmapState} from '../FlowmapState';
|
|
19
|
+
import FlowmapSelectors from '../FlowmapSelectors';
|
|
20
|
+
import {
|
|
21
|
+
GetViewStateOptions,
|
|
22
|
+
getViewStateForLocations,
|
|
23
|
+
} from '../getViewStateForLocations';
|
|
24
|
+
import {ClusterIndex} from '../cluster/ClusterIndex';
|
|
25
|
+
|
|
26
|
+
export default class LocalFlowmapDataProvider<
|
|
27
|
+
L extends Record<string, any>,
|
|
28
|
+
F extends Record<string, any>,
|
|
29
|
+
> implements FlowmapDataProvider<L, F>
|
|
30
|
+
{
|
|
31
|
+
private selectors: FlowmapSelectors<L, F>;
|
|
32
|
+
private flowmapData: FlowmapData<L, F> | undefined;
|
|
33
|
+
private flowmapState: FlowmapState | undefined;
|
|
34
|
+
|
|
35
|
+
constructor(accessors: FlowmapDataAccessors<L, F>) {
|
|
36
|
+
// scope selectors to the concrete instance of FlowmapDataProvider
|
|
37
|
+
this.selectors = new FlowmapSelectors<L, F>(accessors);
|
|
38
|
+
this.flowmapData = undefined;
|
|
39
|
+
this.flowmapState = undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setAccessors(accessors: FlowmapDataAccessors<L, F>) {
|
|
43
|
+
this.selectors.setAccessors(accessors);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setFlowmapData(flowmapData: FlowmapData<L, F>): void {
|
|
47
|
+
this.flowmapData = flowmapData;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getSelectors(): FlowmapSelectors<L, F> {
|
|
51
|
+
return this.selectors;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getFlowmapData(): FlowmapData<L, F> | undefined {
|
|
55
|
+
return this.flowmapData;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async setFlowmapState(flowmapState: FlowmapState): Promise<void> {
|
|
59
|
+
this.flowmapState = flowmapState;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getFlowmapState(): FlowmapState | undefined {
|
|
63
|
+
return this.flowmapState;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getFlowByIndex(idx: number): Promise<F | AggregateFlow | undefined> {
|
|
67
|
+
if (!this.flowmapState || !this.flowmapData) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const flows = this.selectors.getFlowsForFlowmapLayer(
|
|
71
|
+
this.flowmapState,
|
|
72
|
+
this.flowmapData,
|
|
73
|
+
);
|
|
74
|
+
return flows?.[idx];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// TODO: this is unreliable, should replace by unqiue ID
|
|
78
|
+
async getLocationByIndex(idx: number): Promise<L | ClusterNode | undefined> {
|
|
79
|
+
if (!this.flowmapState || !this.flowmapData) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const locations = this.selectors.getLocationsForFlowmapLayer(
|
|
83
|
+
this.flowmapState,
|
|
84
|
+
this.flowmapData,
|
|
85
|
+
);
|
|
86
|
+
return locations?.[idx];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getLayersData(): Promise<LayersData | undefined> {
|
|
90
|
+
if (!this.flowmapState || !this.flowmapData) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return this.selectors.getLayersData(this.flowmapState, this.flowmapData);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getLocationById(id: string | number): Promise<L | Cluster | undefined> {
|
|
97
|
+
if (!this.flowmapState || !this.flowmapData) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const clusterIndex = this.selectors.getClusterIndex(
|
|
101
|
+
this.flowmapState,
|
|
102
|
+
this.flowmapData,
|
|
103
|
+
);
|
|
104
|
+
if (clusterIndex) {
|
|
105
|
+
const cluster = clusterIndex.getClusterById(id);
|
|
106
|
+
if (cluster) {
|
|
107
|
+
return cluster;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const locationsById = this.selectors.getLocationsById(
|
|
111
|
+
this.flowmapState,
|
|
112
|
+
this.flowmapData,
|
|
113
|
+
);
|
|
114
|
+
return locationsById?.get(id);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getTotalsForLocation(
|
|
118
|
+
id: string | number,
|
|
119
|
+
): Promise<LocationTotals | undefined> {
|
|
120
|
+
if (!this.flowmapState || !this.flowmapData) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return this.selectors
|
|
124
|
+
.getLocationTotals(this.flowmapState, this.flowmapData)
|
|
125
|
+
?.get(id);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getViewportForLocations(
|
|
129
|
+
dims: [number, number],
|
|
130
|
+
opts?: GetViewStateOptions,
|
|
131
|
+
): Promise<ViewportProps | undefined> {
|
|
132
|
+
if (!this.flowmapData?.locations) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
// @ts-ignore
|
|
136
|
+
return getViewStateForLocations(
|
|
137
|
+
this.flowmapData.locations,
|
|
138
|
+
(loc) => [
|
|
139
|
+
this.selectors.accessors.getLocationLon(loc),
|
|
140
|
+
this.selectors.accessors.getLocationLat(loc),
|
|
141
|
+
],
|
|
142
|
+
dims,
|
|
143
|
+
opts,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async updateLayersData(
|
|
148
|
+
setLayersData: (layersData: LayersData | undefined) => void,
|
|
149
|
+
) {
|
|
150
|
+
setLayersData(await this.getLayersData());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getClusterZoom(): number | undefined {
|
|
154
|
+
return this.flowmapState && this.flowmapData
|
|
155
|
+
? this.selectors.getClusterZoom(this.flowmapState, this.flowmapData)
|
|
156
|
+
: undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
getClusterIndex(): ClusterIndex<F> | undefined {
|
|
160
|
+
return this.flowmapState && this.flowmapData
|
|
161
|
+
? this.selectors.getClusterIndex(this.flowmapState, this.flowmapData)
|
|
162
|
+
: undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getLocationsById(): Map<string | number, L> | undefined {
|
|
166
|
+
return this.flowmapState && this.flowmapData
|
|
167
|
+
? this.selectors.getLocationsById(this.flowmapState, this.flowmapData)
|
|
168
|
+
: undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getLocationTotals(): Map<string | number, LocationTotals> | undefined {
|
|
172
|
+
return this.flowmapState && this.flowmapData
|
|
173
|
+
? this.selectors.getLocationTotals(this.flowmapState, this.flowmapData)
|
|
174
|
+
: undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getFlowsForFlowmapLayer(): Array<F | AggregateFlow> | undefined {
|
|
178
|
+
return this.flowmapState && this.flowmapData
|
|
179
|
+
? this.selectors.getFlowsForFlowmapLayer(
|
|
180
|
+
this.flowmapState,
|
|
181
|
+
this.flowmapData,
|
|
182
|
+
)
|
|
183
|
+
: undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Flowmap.gl contributors
|
|
3
|
+
* Copyright (c) 2018-2020 Teralytics
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {WebMercatorViewport} from '@math.gl/web-mercator';
|
|
8
|
+
import {
|
|
9
|
+
ClusterLevel,
|
|
10
|
+
isCluster,
|
|
11
|
+
LocationAccessors,
|
|
12
|
+
ViewportProps,
|
|
13
|
+
} from './types';
|
|
14
|
+
import {scaleLinear} from 'd3-scale';
|
|
15
|
+
import {ClusterIndex, LocationWeightGetter} from './cluster/ClusterIndex';
|
|
16
|
+
import {descending} from 'd3-array';
|
|
17
|
+
|
|
18
|
+
// TODO: use re-reselect
|
|
19
|
+
|
|
20
|
+
export const getViewportBoundingBox = (
|
|
21
|
+
viewport: ViewportProps,
|
|
22
|
+
maxLocationCircleSize = 0,
|
|
23
|
+
): [number, number, number, number] => {
|
|
24
|
+
const pad = maxLocationCircleSize;
|
|
25
|
+
const bounds = new WebMercatorViewport({
|
|
26
|
+
...viewport,
|
|
27
|
+
width: viewport.width + pad * 2,
|
|
28
|
+
height: viewport.height + pad * 2,
|
|
29
|
+
}).getBounds();
|
|
30
|
+
return [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getFlowThicknessScale = (
|
|
34
|
+
magnitudeExtent: [number, number] | undefined,
|
|
35
|
+
) => {
|
|
36
|
+
if (!magnitudeExtent) return undefined;
|
|
37
|
+
return scaleLinear()
|
|
38
|
+
.range([0.025, 0.5])
|
|
39
|
+
.domain([
|
|
40
|
+
0,
|
|
41
|
+
// should support diff mode too
|
|
42
|
+
Math.max.apply(
|
|
43
|
+
null,
|
|
44
|
+
magnitudeExtent.map((x: number | undefined) => Math.abs(x || 0)),
|
|
45
|
+
),
|
|
46
|
+
]);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Adding meaningful cluster names.
|
|
51
|
+
* NOTE: this will mutate the nodes in clusterIndex
|
|
52
|
+
*/
|
|
53
|
+
export function addClusterNames<L, F>(
|
|
54
|
+
clusterIndex: ClusterIndex<F>,
|
|
55
|
+
clusterLevels: ClusterLevel[],
|
|
56
|
+
locationsById: Map<string | number, L>,
|
|
57
|
+
locationAccessors: LocationAccessors<L>,
|
|
58
|
+
getLocationWeight: LocationWeightGetter,
|
|
59
|
+
): void {
|
|
60
|
+
const {getLocationId, getLocationName, getLocationClusterName} =
|
|
61
|
+
locationAccessors;
|
|
62
|
+
const getName = (id: string | number) => {
|
|
63
|
+
const loc = locationsById.get(id);
|
|
64
|
+
if (loc) {
|
|
65
|
+
return getLocationName ? getLocationName(loc) : getLocationId(loc) || id;
|
|
66
|
+
}
|
|
67
|
+
return `"${id}"`;
|
|
68
|
+
};
|
|
69
|
+
for (const level of clusterLevels) {
|
|
70
|
+
for (const node of level.nodes) {
|
|
71
|
+
// Here mutating the nodes (adding names)
|
|
72
|
+
if (isCluster(node)) {
|
|
73
|
+
const leaves = clusterIndex.expandCluster(node);
|
|
74
|
+
|
|
75
|
+
leaves.sort((a, b) =>
|
|
76
|
+
descending(getLocationWeight(a), getLocationWeight(b)),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (getLocationClusterName) {
|
|
80
|
+
node.name = getLocationClusterName(leaves);
|
|
81
|
+
} else {
|
|
82
|
+
const topId = leaves[0];
|
|
83
|
+
const otherId = leaves.length === 2 ? leaves[1] : undefined;
|
|
84
|
+
node.name = `"${getName(topId)}" and ${
|
|
85
|
+
otherId ? `"${getName(otherId)}"` : `${leaves.length - 1} others`
|
|
86
|
+
}`;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
(node as any).name = getName(node.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/time.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Flowmap.gl contributors
|
|
3
|
+
* Copyright (c) 2018-2020 Teralytics
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {timeFormat, timeParse} from 'd3-time-format';
|
|
8
|
+
import {
|
|
9
|
+
timeDay,
|
|
10
|
+
timeHour,
|
|
11
|
+
TimeInterval,
|
|
12
|
+
timeMinute,
|
|
13
|
+
timeMonth,
|
|
14
|
+
timeSecond,
|
|
15
|
+
timeWeek,
|
|
16
|
+
timeYear,
|
|
17
|
+
} from 'd3-time';
|
|
18
|
+
|
|
19
|
+
const dateParsers = [
|
|
20
|
+
timeParse('%Y-%m-%d'),
|
|
21
|
+
timeParse('%Y-%m-%d %H:%M'),
|
|
22
|
+
timeParse('%Y-%m-%d %H:%M:%S'),
|
|
23
|
+
timeParse('%Y'),
|
|
24
|
+
timeParse('%Y-%m'),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function parseTime(input: string | Date | undefined): Date | undefined {
|
|
28
|
+
if (input != null) {
|
|
29
|
+
if (input instanceof Date) {
|
|
30
|
+
return input;
|
|
31
|
+
}
|
|
32
|
+
for (const parse of dateParsers) {
|
|
33
|
+
const date = parse(input);
|
|
34
|
+
if (date) {
|
|
35
|
+
return date;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export enum TimeGranularityKey {
|
|
43
|
+
SECOND = 'SECOND',
|
|
44
|
+
MINUTE = 'MINUTE',
|
|
45
|
+
HOUR = 'HOUR',
|
|
46
|
+
DAY = 'DAY',
|
|
47
|
+
MONTH = 'MONTH',
|
|
48
|
+
YEAR = 'YEAR',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TimeGranularity {
|
|
52
|
+
key: TimeGranularityKey;
|
|
53
|
+
order: number;
|
|
54
|
+
interval: TimeInterval;
|
|
55
|
+
format: (date: Date) => string;
|
|
56
|
+
formatFull: (date: Date) => string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// const preferredLocale = navigator.languages ? navigator.languages[0] : 'en';
|
|
60
|
+
|
|
61
|
+
const formatMillisecond = timeFormat('.%L'),
|
|
62
|
+
formatSecond = timeFormat(':%S'),
|
|
63
|
+
formatMinute = timeFormat('%I:%M'),
|
|
64
|
+
// formatHour = (d: Date) => d.toLocaleString(preferredLocale, { hour: 'numeric' }),
|
|
65
|
+
formatHour = timeFormat('%I %p'),
|
|
66
|
+
formatDay = timeFormat('%a %d'),
|
|
67
|
+
formatWeek = timeFormat('%b %d'),
|
|
68
|
+
formatMonth = timeFormat('%b'),
|
|
69
|
+
formatYear = timeFormat('%Y');
|
|
70
|
+
|
|
71
|
+
export function tickMultiFormat(date: Date) {
|
|
72
|
+
return (
|
|
73
|
+
timeSecond(date) < date
|
|
74
|
+
? formatMillisecond
|
|
75
|
+
: timeMinute(date) < date
|
|
76
|
+
? formatSecond
|
|
77
|
+
: timeHour(date) < date
|
|
78
|
+
? formatMinute
|
|
79
|
+
: timeDay(date) < date
|
|
80
|
+
? formatHour
|
|
81
|
+
: timeMonth(date) < date
|
|
82
|
+
? timeWeek(date) < date
|
|
83
|
+
? formatDay
|
|
84
|
+
: formatWeek
|
|
85
|
+
: timeYear(date) < date
|
|
86
|
+
? formatMonth
|
|
87
|
+
: formatYear
|
|
88
|
+
)(date);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const TIME_GRANULARITIES: TimeGranularity[] = [
|
|
92
|
+
{
|
|
93
|
+
order: 0,
|
|
94
|
+
key: TimeGranularityKey.SECOND,
|
|
95
|
+
interval: timeSecond,
|
|
96
|
+
format: formatSecond,
|
|
97
|
+
formatFull: timeFormat('%Y-%m-%d %H:%M:%S'),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
order: 1,
|
|
101
|
+
key: TimeGranularityKey.MINUTE,
|
|
102
|
+
interval: timeMinute,
|
|
103
|
+
format: formatMinute,
|
|
104
|
+
formatFull: timeFormat('%Y-%m-%d %H:%M'),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
order: 2,
|
|
108
|
+
key: TimeGranularityKey.HOUR,
|
|
109
|
+
interval: timeHour,
|
|
110
|
+
// format: (d: Date) => d.toLocaleString(preferredLocale, { hour: 'numeric', minute: '2-digit' }),
|
|
111
|
+
format: formatHour,
|
|
112
|
+
formatFull: timeFormat('%a %d %b %Y, %I %p'),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
order: 3,
|
|
116
|
+
key: TimeGranularityKey.DAY,
|
|
117
|
+
interval: timeDay,
|
|
118
|
+
format: formatDay,
|
|
119
|
+
formatFull: timeFormat('%a %d %b %Y'),
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
order: 4,
|
|
123
|
+
key: TimeGranularityKey.MONTH,
|
|
124
|
+
interval: timeMonth,
|
|
125
|
+
format: formatMonth,
|
|
126
|
+
formatFull: timeFormat('%b %Y'),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
order: 5,
|
|
130
|
+
key: TimeGranularityKey.YEAR,
|
|
131
|
+
interval: timeYear,
|
|
132
|
+
format: formatYear,
|
|
133
|
+
formatFull: timeFormat('%Y'),
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
export function getTimeGranularityByKey(key: TimeGranularityKey) {
|
|
138
|
+
return TIME_GRANULARITIES.find((s) => s.key === key);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getTimeGranularityByOrder(order: number) {
|
|
142
|
+
return TIME_GRANULARITIES.find((s) => s.order === order);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function getTimeGranularityForDate(date: Date): TimeGranularity {
|
|
146
|
+
let prev = undefined;
|
|
147
|
+
for (const current of TIME_GRANULARITIES) {
|
|
148
|
+
const {interval} = current;
|
|
149
|
+
const floored = interval(date);
|
|
150
|
+
if (floored < date) {
|
|
151
|
+
if (!prev) return current;
|
|
152
|
+
return prev;
|
|
153
|
+
}
|
|
154
|
+
prev = current;
|
|
155
|
+
}
|
|
156
|
+
return TIME_GRANULARITIES[TIME_GRANULARITIES.length - 1];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function areRangesEqual(
|
|
160
|
+
a: [Date, Date] | undefined,
|
|
161
|
+
b: [Date, Date] | undefined,
|
|
162
|
+
): boolean {
|
|
163
|
+
if (!a && !b) return true;
|
|
164
|
+
if (!a || !b) return false;
|
|
165
|
+
return a[0] === b[0] && a[1] === b[1];
|
|
166
|
+
}
|