@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.
Files changed (70) hide show
  1. package/dist/FlowmapAggregateAccessors.d.ts +16 -0
  2. package/dist/FlowmapAggregateAccessors.d.ts.map +1 -0
  3. package/dist/FlowmapAggregateAccessors.js +48 -0
  4. package/dist/FlowmapSelectors.d.ts +213 -0
  5. package/dist/FlowmapSelectors.d.ts.map +1 -0
  6. package/dist/FlowmapSelectors.js +861 -0
  7. package/dist/{FlowMapState.d.ts → FlowmapState.d.ts} +12 -8
  8. package/dist/FlowmapState.d.ts.map +1 -0
  9. package/dist/FlowmapState.js +2 -0
  10. package/dist/cluster/ClusterIndex.d.ts +3 -3
  11. package/dist/cluster/ClusterIndex.d.ts.map +1 -1
  12. package/dist/cluster/ClusterIndex.js +1 -1
  13. package/dist/cluster/cluster.d.ts +6 -5
  14. package/dist/cluster/cluster.d.ts.map +1 -1
  15. package/dist/cluster/cluster.js +76 -20
  16. package/dist/colors.d.ts +7 -7
  17. package/dist/colors.d.ts.map +1 -1
  18. package/dist/colors.js +55 -20
  19. package/dist/getViewStateForLocations.d.ts +18 -11
  20. package/dist/getViewStateForLocations.d.ts.map +1 -1
  21. package/dist/getViewStateForLocations.js +37 -23
  22. package/dist/index.d.ts +9 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -6
  25. package/dist/provider/FlowmapDataProvider.d.ts +21 -0
  26. package/dist/provider/FlowmapDataProvider.d.ts.map +1 -0
  27. package/dist/provider/FlowmapDataProvider.js +17 -0
  28. package/dist/provider/LocalFlowmapDataProvider.d.ts +25 -0
  29. package/dist/provider/LocalFlowmapDataProvider.d.ts.map +1 -0
  30. package/dist/provider/LocalFlowmapDataProvider.js +111 -0
  31. package/dist/selector-functions.d.ts +10 -0
  32. package/dist/selector-functions.d.ts.map +1 -0
  33. package/dist/selector-functions.js +56 -0
  34. package/dist/types.d.ts +20 -16
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js +4 -4
  37. package/dist/util.d.ts +0 -1
  38. package/dist/util.d.ts.map +1 -1
  39. package/dist/util.js +1 -4
  40. package/package.json +10 -12
  41. package/src/FlowmapAggregateAccessors.ts +67 -0
  42. package/src/{FlowMapSelectors.ts → FlowmapSelectors.ts} +453 -398
  43. package/src/{FlowMapState.ts → FlowmapState.ts} +11 -7
  44. package/src/cluster/ClusterIndex.ts +19 -12
  45. package/src/cluster/cluster.ts +96 -35
  46. package/src/colors.ts +70 -28
  47. package/src/getViewStateForLocations.ts +56 -40
  48. package/src/index.ts +9 -6
  49. package/src/provider/FlowmapDataProvider.ts +75 -0
  50. package/src/provider/LocalFlowmapDataProvider.ts +143 -0
  51. package/src/selector-functions.ts +87 -0
  52. package/src/types.ts +23 -19
  53. package/src/util.ts +0 -4
  54. package/dist/FlowMapAggregateAccessors.d.ts +0 -15
  55. package/dist/FlowMapAggregateAccessors.d.ts.map +0 -1
  56. package/dist/FlowMapAggregateAccessors.js +0 -43
  57. package/dist/FlowMapSelectors.d.ts +0 -182
  58. package/dist/FlowMapSelectors.d.ts.map +0 -1
  59. package/dist/FlowMapSelectors.js +0 -834
  60. package/dist/FlowMapState.d.ts.map +0 -1
  61. package/dist/FlowMapState.js +0 -2
  62. package/dist/provider/FlowMapDataProvider.d.ts +0 -16
  63. package/dist/provider/FlowMapDataProvider.d.ts.map +0 -1
  64. package/dist/provider/FlowMapDataProvider.js +0 -17
  65. package/dist/provider/LocalFlowMapDataProvider.d.ts +0 -20
  66. package/dist/provider/LocalFlowMapDataProvider.d.ts.map +0 -1
  67. package/dist/provider/LocalFlowMapDataProvider.js +0 -87
  68. package/src/FlowMapAggregateAccessors.ts +0 -60
  69. package/src/provider/FlowMapDataProvider.ts +0 -63
  70. package/src/provider/LocalFlowMapDataProvider.ts +0 -105
@@ -1,26 +1,30 @@
1
1
  import {LocationFilterMode, ViewportProps} from './types';
2
2
 
3
3
  export interface FilterState {
4
- selectedLocations: string[] | undefined;
5
- selectedTimeRange: [Date, Date] | undefined;
6
- locationFilterMode: LocationFilterMode;
4
+ selectedLocations?: string[];
5
+ locationFilterMode?: LocationFilterMode;
6
+ selectedTimeRange?: [Date, Date];
7
7
  }
8
8
 
9
9
  export interface SettingsState {
10
10
  animationEnabled: boolean;
11
11
  fadeEnabled: boolean;
12
+ fadeOpacityEnabled: boolean;
12
13
  locationTotalsEnabled: boolean;
14
+ locationLabelsEnabled: boolean;
13
15
  adaptiveScalesEnabled: boolean;
14
16
  clusteringEnabled: boolean;
15
17
  clusteringAuto: boolean;
16
18
  clusteringLevel?: number;
17
19
  darkMode: boolean;
18
20
  fadeAmount: number;
19
- colorScheme: string | undefined;
21
+ colorScheme: string | string[] | undefined;
22
+ highlightColor: string;
23
+ maxTopFlowsDisplayNum: number;
20
24
  }
21
25
 
22
- export interface FlowMapState {
23
- filterState: FilterState;
24
- settingsState: SettingsState;
26
+ export interface FlowmapState {
27
+ filter?: FilterState;
28
+ settings: SettingsState;
25
29
  viewport: ViewportProps;
26
30
  }
@@ -27,14 +27,14 @@ import {
27
27
  } from './../types';
28
28
  import {ascending, bisectLeft, extent} from 'd3-array';
29
29
 
30
- export type LocationWeightGetter = (id: string) => number;
30
+ export type LocationWeightGetter = (id: string | number) => number;
31
31
 
32
32
  /**
33
33
  * A data structure representing the cluster levels for efficient flow aggregation.
34
34
  */
35
35
  export interface ClusterIndex<F> {
36
36
  availableZoomLevels: number[];
37
- getClusterById: (clusterId: string) => Cluster | undefined;
37
+ getClusterById: (clusterId: string | number) => Cluster | undefined;
38
38
  /**
39
39
  * List the nodes on the given zoom level.
40
40
  */
@@ -50,7 +50,10 @@ export interface ClusterIndex<F> {
50
50
  /**
51
51
  * Find the cluster the given location is residing in on the specified zoom level.
52
52
  */
53
- findClusterFor: (locationId: string, zoom: number) => string | undefined;
53
+ findClusterFor: (
54
+ locationId: string | number,
55
+ zoom: number,
56
+ ) => string | number | undefined;
54
57
  /**
55
58
  * Aggregate flows for the specified zoom level.
56
59
  */
@@ -69,8 +72,8 @@ export interface ClusterIndex<F> {
69
72
  */
70
73
  export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
71
74
  const nodesByZoom = new Map<number, ClusterNode[]>();
72
- const clustersById = new Map<string, Cluster>();
73
- const minZoomByLocationId = new Map<string, number>();
75
+ const clustersById = new Map<string | number, Cluster>();
76
+ const minZoomByLocationId = new Map<string | number, number>();
74
77
  for (const {zoom, nodes} of clusterLevels) {
75
78
  nodesByZoom.set(zoom, nodes);
76
79
  for (const node of nodes) {
@@ -91,7 +94,10 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
91
94
  throw new Error('Could not determine minZoom or maxZoom');
92
95
  }
93
96
 
94
- const leavesToClustersByZoom = new Map<number, Map<string, Cluster>>();
97
+ const leavesToClustersByZoom = new Map<
98
+ number,
99
+ Map<string | number, Cluster>
100
+ >();
95
101
 
96
102
  for (const cluster of clustersById.values()) {
97
103
  const {zoom} = cluster;
@@ -118,7 +124,7 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
118
124
 
119
125
  const expandCluster = (cluster: Cluster, targetZoom: number = maxZoom) => {
120
126
  const ids: string[] = [];
121
- const visit = (c: Cluster, expandedIds: string[]) => {
127
+ const visit = (c: Cluster, expandedIds: (string | number)[]) => {
122
128
  if (targetZoom > c.zoom) {
123
129
  for (const childId of c.children) {
124
130
  const child = clustersById.get(childId);
@@ -136,7 +142,7 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
136
142
  return ids;
137
143
  };
138
144
 
139
- function findClusterFor(locationId: string, zoom: number) {
145
+ function findClusterFor(locationId: string | number, zoom: number) {
140
146
  const leavesToClusters = leavesToClustersByZoom.get(zoom);
141
147
  if (!leavesToClusters) {
142
148
  return undefined;
@@ -179,7 +185,8 @@ export function buildIndex<F>(clusterLevels: ClusterLevels): ClusterIndex<F> {
179
185
  }
180
186
  const result: (F | AggregateFlow)[] = [];
181
187
  const aggFlowsByKey = new Map<string, AggregateFlow>();
182
- const makeKey = (origin: string, dest: string) => `${origin}:${dest}`;
188
+ const makeKey = (origin: string | number, dest: string | number) =>
189
+ `${origin}:${dest}`;
183
190
  const {
184
191
  flowCountsMapReduce = {
185
192
  map: getFlowMagnitude,
@@ -223,8 +230,8 @@ export function makeLocationWeightGetter<F>(
223
230
  {getFlowOriginId, getFlowDestId, getFlowMagnitude}: FlowAccessors<F>,
224
231
  ): LocationWeightGetter {
225
232
  const locationTotals = {
226
- incoming: new Map<string, number>(),
227
- outgoing: new Map<string, number>(),
233
+ incoming: new Map<string | number, number>(),
234
+ outgoing: new Map<string | number, number>(),
228
235
  };
229
236
  for (const flow of flows) {
230
237
  const origin = getFlowOriginId(flow);
@@ -239,7 +246,7 @@ export function makeLocationWeightGetter<F>(
239
246
  (locationTotals.outgoing.get(origin) || 0) + count,
240
247
  );
241
248
  }
242
- return (id: string) =>
249
+ return (id: string | number) =>
243
250
  Math.max(
244
251
  Math.abs(locationTotals.incoming.get(id) || 0),
245
252
  Math.abs(locationTotals.outgoing.get(id) || 0),
@@ -36,7 +36,7 @@
36
36
  // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
37
37
  // THIS SOFTWARE.
38
38
 
39
- import {rollup} from 'd3-array';
39
+ import {min, rollup} from 'd3-array';
40
40
  import KDBush from 'kdbush';
41
41
  import {LocationWeightGetter} from './ClusterIndex';
42
42
  import {Cluster, ClusterLevel, ClusterNode, LocationAccessors} from '../types';
@@ -69,8 +69,9 @@ interface BasePoint {
69
69
  parentId: number; // parent cluster id
70
70
  }
71
71
 
72
- interface LeafPoint extends BasePoint {
72
+ interface LeafPoint<L> extends BasePoint {
73
73
  index: number; // index of the source feature in the original input array,
74
+ location: L;
74
75
  }
75
76
 
76
77
  interface ClusterPoint extends BasePoint {
@@ -78,14 +79,14 @@ interface ClusterPoint extends BasePoint {
78
79
  numPoints: number;
79
80
  }
80
81
 
81
- type Point = LeafPoint | ClusterPoint;
82
+ type Point<L> = LeafPoint<L> | ClusterPoint;
82
83
 
83
- export function isLeafPoint(p: Point): p is LeafPoint {
84
- const {index} = p as LeafPoint;
84
+ export function isLeafPoint<L>(p: Point<L>): p is LeafPoint<L> {
85
+ const {index} = p as LeafPoint<L>;
85
86
  return index != null;
86
87
  }
87
88
 
88
- export function isClusterPoint(p: Point): p is ClusterPoint {
89
+ export function isClusterPoint<L>(p: Point<L>): p is ClusterPoint {
89
90
  const {id} = p as ClusterPoint;
90
91
  return id != null;
91
92
  }
@@ -93,12 +94,12 @@ export function isClusterPoint(p: Point): p is ClusterPoint {
93
94
  type ZoomLevelKDBush = any;
94
95
 
95
96
  export function clusterLocations<L>(
96
- locations: L[],
97
+ locations: Iterable<L>,
97
98
  locationAccessors: LocationAccessors<L>,
98
99
  getLocationWeight: LocationWeightGetter,
99
100
  options?: Partial<Options>,
100
101
  ): ClusterLevel[] {
101
- const {getLocationCentroid, getLocationId} = locationAccessors;
102
+ const {getLocationLon, getLocationLat, getLocationId} = locationAccessors;
102
103
  const opts = {
103
104
  ...defaultOptions,
104
105
  ...options,
@@ -108,50 +109,78 @@ export function clusterLocations<L>(
108
109
  const trees = new Array<ZoomLevelKDBush>(maxZoom + 1);
109
110
 
110
111
  // generate a cluster object for each point and index input points into a KD-tree
111
- let clusters = new Array<Point>();
112
- for (let i = 0; i < locations.length; i++) {
113
- const [x, y] = getLocationCentroid(locations[i]);
112
+ let clusters = new Array<Point<L>>();
113
+ let locationsCount = 0;
114
+ for (const location of locations) {
115
+ const x = getLocationLon(location);
116
+ const y = getLocationLat(location);
114
117
  clusters.push({
115
118
  x: lngX(x), // projected point coordinates
116
119
  y: latY(y),
117
- weight: getLocationWeight(getLocationId(locations[i])),
120
+ weight: getLocationWeight(getLocationId(location)),
118
121
  zoom: Infinity, // the last zoom the point was processed at
119
- index: i, // index of the source feature in the original input array,
122
+ index: locationsCount, // index of the source feature in the original input array,
120
123
  parentId: -1, // parent cluster id
124
+ location,
121
125
  });
126
+ locationsCount++;
122
127
  }
123
- trees[maxZoom + 1] = new KDBush(clusters, getX, getY, nodeSize, Float32Array);
124
128
 
125
129
  // cluster points on max zoom, then cluster the results on previous zoom, etc.;
126
130
  // results in a cluster hierarchy across zoom levels
131
+ trees[maxZoom + 1] = new KDBush(clusters, getX, getY, nodeSize, Float32Array);
132
+ let prevZoom = maxZoom + 1;
133
+
127
134
  for (let z = maxZoom; z >= minZoom; z--) {
128
135
  // create a new set of clusters for the zoom and index them with a KD-tree
129
- clusters = cluster(clusters, z, trees[z + 1], opts);
130
- trees[z] = new KDBush(clusters, getX, getY, nodeSize, Float32Array);
136
+ const _clusters = cluster(clusters, z, trees[prevZoom], opts);
137
+ if (_clusters.length === clusters.length) {
138
+ // same number of clusters => move the higher level clusters up
139
+ // no need to keep the same data on multiple levels
140
+ trees[z] = trees[prevZoom];
141
+ trees[prevZoom] = undefined;
142
+ prevZoom = z;
143
+ clusters = _clusters;
144
+ } else {
145
+ prevZoom = z;
146
+ clusters = _clusters;
147
+ trees[z] = new KDBush(clusters, getX, getY, nodeSize, Float32Array);
148
+ }
131
149
  }
132
150
 
133
151
  if (trees.length === 0) {
134
152
  return [];
135
153
  }
136
- const numbersOfClusters = trees.map((d) => d.points.length);
137
- const maxAvailZoom = numbersOfClusters.indexOf(
138
- numbersOfClusters[numbersOfClusters.length - 1],
139
- );
154
+
155
+ const numbersOfClusters = trees.map((d) => d?.points.length);
156
+ const minClusters = min(numbersOfClusters.filter((d) => d > 0));
157
+ const maxClusters = getMaxNumberOfClusters(locations, locationAccessors);
158
+
159
+ let maxAvailZoom = numbersOfClusters.indexOf(maxClusters);
160
+ if (maxClusters < locationsCount) {
161
+ maxAvailZoom++;
162
+ if (maxAvailZoom < maxZoom + 1) {
163
+ trees[maxAvailZoom] = trees[maxZoom + 1];
164
+ trees[maxZoom + 1] = undefined;
165
+ }
166
+ }
140
167
  const minAvailZoom = Math.min(
141
168
  maxAvailZoom,
142
- numbersOfClusters.lastIndexOf(numbersOfClusters[0]),
169
+ numbersOfClusters.lastIndexOf(minClusters),
143
170
  );
144
171
 
145
172
  const clusterLevels = new Array<ClusterLevel>();
146
- for (let zoom = minAvailZoom; zoom <= maxAvailZoom; zoom++) {
147
- let childrenByParent: Map<number, string[]> | undefined;
173
+ prevZoom = NaN;
174
+ for (let zoom = maxAvailZoom; zoom >= minAvailZoom; zoom--) {
175
+ let childrenByParent: Map<number, (string | number)[]> | undefined;
148
176
  const tree = trees[zoom];
177
+ if (!tree) continue;
149
178
  if (zoom < maxAvailZoom) {
150
- childrenByParent = rollup<Point, string[], number>(
151
- trees[zoom + 1].points,
179
+ childrenByParent = rollup<Point<L>, (string | number)[], number>(
180
+ trees[prevZoom].points,
152
181
  (points: any[]) =>
153
182
  points.map((p: any) =>
154
- p.id ? makeClusterId(p.id) : getLocationId(locations[p.index]),
183
+ p.id ? makeClusterId(p.id) : getLocationId(p.location),
155
184
  ),
156
185
  (point: any) => point.parentId,
157
186
  );
@@ -159,13 +188,13 @@ export function clusterLocations<L>(
159
188
 
160
189
  const nodes: ClusterNode[] = [];
161
190
  for (const point of tree.points) {
162
- const {x, y, numPoints} = point;
191
+ const {x, y, numPoints, location} = point;
163
192
  if (isLeafPoint(point)) {
164
- const location = locations[point.index];
165
193
  nodes.push({
166
194
  id: getLocationId(location),
167
195
  zoom,
168
- centroid: getLocationCentroid(location),
196
+ lat: getLocationLat(location),
197
+ lon: getLocationLon(location),
169
198
  });
170
199
  } else if (isClusterPoint(point)) {
171
200
  const {id} = point;
@@ -177,7 +206,8 @@ export function clusterLocations<L>(
177
206
  id: makeClusterId(id),
178
207
  name: makeClusterName(id, numPoints),
179
208
  zoom,
180
- centroid: [xLng(x), yLat(y)] as [number, number],
209
+ lat: yLat(y),
210
+ lon: xLng(x),
181
211
  children,
182
212
  } as Cluster);
183
213
  }
@@ -186,6 +216,7 @@ export function clusterLocations<L>(
186
216
  zoom,
187
217
  nodes,
188
218
  });
219
+ prevZoom = zoom;
189
220
  }
190
221
  return clusterLevels;
191
222
  }
@@ -208,13 +239,13 @@ function createCluster(
208
239
  };
209
240
  }
210
241
 
211
- function cluster(
212
- points: Point[],
242
+ function cluster<L>(
243
+ points: Point<L>[],
213
244
  zoom: number,
214
245
  tree: ZoomLevelKDBush,
215
246
  options: Options,
216
247
  ) {
217
- const clusters: Point[] = [];
248
+ const clusters: Point<L>[] = [];
218
249
  const {radius, extent} = options;
219
250
  const r = radius / (extent * Math.pow(2, zoom));
220
251
 
@@ -290,10 +321,40 @@ function latY(lat: number) {
290
321
  return y < 0 ? 0 : y > 1 ? 1 : y;
291
322
  }
292
323
 
293
- function getX(p: Point) {
324
+ function getX<L>(p: Point<L>) {
294
325
  return p.x;
295
326
  }
296
327
 
297
- function getY(p: Point) {
328
+ function getY<L>(p: Point<L>) {
298
329
  return p.y;
299
330
  }
331
+
332
+ /**
333
+ * Finds groups of locations which share the same positions.
334
+ * They will always be clustered together at any zoom level
335
+ * which can lead to having too many zooms.
336
+ */
337
+ function getMaxNumberOfClusters<L>(
338
+ locations: Iterable<L>,
339
+ locationAccessors: LocationAccessors<L>,
340
+ ) {
341
+ const {getLocationLon, getLocationLat} = locationAccessors;
342
+ const countByLatLon = new Map<string, number>();
343
+ let numLocations = 0;
344
+ for (const loc of locations) {
345
+ const lon = getLocationLon(loc);
346
+ const lat = getLocationLat(loc);
347
+ const key = `${lon},${lat}`;
348
+ const prev = countByLatLon.get(key);
349
+ countByLatLon.set(key, prev ? prev + 1 : 1);
350
+ numLocations++;
351
+ }
352
+
353
+ let numSame = 0;
354
+ for (const [key, count] of countByLatLon) {
355
+ if (count > 1) {
356
+ numSame++;
357
+ }
358
+ }
359
+ return numLocations - numSame;
360
+ }
package/src/colors.ts CHANGED
@@ -26,9 +26,9 @@ import {
26
26
  } from 'd3-scale-chromatic';
27
27
  import {range} from 'd3-array';
28
28
  import {scalePow, scaleSequential, scaleSequentialPow} from 'd3-scale';
29
- import {interpolateRgbBasis} from 'd3-interpolate';
30
- import {color as d3color, hcl} from 'd3-color';
31
- import {SettingsState} from './FlowMapState';
29
+ import {interpolateBasis, interpolateRgbBasis} from 'd3-interpolate';
30
+ import {color as d3color, hcl, rgb as colorRgb} from 'd3-color';
31
+ import {SettingsState} from './FlowmapState';
32
32
 
33
33
  const DEFAULT_OUTLINE_COLOR = '#fff';
34
34
  const DEFAULT_DIMMED_OPACITY = 0.4;
@@ -58,7 +58,10 @@ export function opacifyHex(hexCode: string, opacity: number): string {
58
58
  return `rgba(${col.r}, ${col.g}, ${col.b}, ${opacity})`;
59
59
  }
60
60
 
61
- export function colorAsRgba(color: string): RGBA {
61
+ export function colorAsRgba(color: string | number[]): RGBA {
62
+ if (Array.isArray(color)) {
63
+ return color as RGBA;
64
+ }
62
65
  const col = d3color(color);
63
66
  if (!col) {
64
67
  console.warn('Invalid color: ', color);
@@ -100,7 +103,7 @@ const getColorSteps = (interpolate: (x: number) => string) =>
100
103
  .reverse();
101
104
 
102
105
  const FLOW_MIN_COLOR = 'rgba(240,240,240,0.5)';
103
- export const BLUES_PALE = [FLOW_MIN_COLOR, ColorScheme.primary];
106
+ export const GRAYISH = [FLOW_MIN_COLOR, ColorScheme.primary];
104
107
  const schemeBluYl = [
105
108
  '#f7feae',
106
109
  '#b7e6a5',
@@ -134,7 +137,6 @@ export const schemeTeal = [
134
137
  export const DEFAULT_COLOR_SCHEME = schemeTeal;
135
138
  export const COLOR_SCHEMES: {[key: string]: string[]} = {
136
139
  Blues: asScheme(schemeBlues),
137
- BluesPale: BLUES_PALE,
138
140
  BluGrn: [
139
141
  '#c4e6c3',
140
142
  '#96d2a4',
@@ -186,6 +188,7 @@ export const COLOR_SCHEMES: {[key: string]: string[]} = {
186
188
  ],
187
189
  Emrld: schemeEmrld,
188
190
  GnBu: asScheme(schemeGnBu),
191
+ Grayish: GRAYISH,
189
192
  Greens: asScheme(schemeGreens),
190
193
  Greys: asScheme(schemeGreys),
191
194
  Inferno: getColorSteps(interpolateInferno),
@@ -330,24 +333,24 @@ const diffColors: DiffColors = {
330
333
  outlineColor: 'rgb(230,233,237)',
331
334
  };
332
335
 
333
- export function getFlowMapColors(
334
- settingsState: SettingsState,
335
- ): Colors | DiffColors {
336
+ export function getFlowmapColors(settings: SettingsState): Colors | DiffColors {
336
337
  return getColors(
337
338
  false, // TODO: diffMode
338
- settingsState.colorScheme,
339
- settingsState.darkMode,
340
- settingsState.fadeEnabled,
341
- settingsState.fadeAmount,
342
- settingsState.animationEnabled,
339
+ settings.colorScheme,
340
+ settings.darkMode,
341
+ settings.fadeEnabled,
342
+ settings.fadeOpacityEnabled,
343
+ settings.fadeAmount,
344
+ settings.animationEnabled,
343
345
  );
344
346
  }
345
347
 
346
348
  export function getColors(
347
349
  diffMode: boolean,
348
- schemeKey: string | undefined,
350
+ colorScheme: string | string[] | undefined,
349
351
  darkMode: boolean,
350
352
  fadeEnabled: boolean,
353
+ fadeOpacityEnabled: boolean,
351
354
  fadeAmount: number,
352
355
  animate: boolean,
353
356
  ): Colors | DiffColors {
@@ -355,11 +358,18 @@ export function getColors(
355
358
  return diffColors;
356
359
  }
357
360
 
358
- let scheme = (schemeKey && COLOR_SCHEMES[schemeKey]) || DEFAULT_COLOR_SCHEME;
361
+ let scheme;
359
362
 
360
- if (darkMode) {
361
- scheme = scheme.slice().reverse();
363
+ if (Array.isArray(colorScheme)) {
364
+ scheme = colorScheme;
365
+ } else {
366
+ scheme =
367
+ (colorScheme && COLOR_SCHEMES[colorScheme]) || DEFAULT_COLOR_SCHEME;
368
+ if (darkMode) {
369
+ scheme = scheme.slice().reverse();
370
+ }
362
371
  }
372
+
363
373
  // if (animate)
364
374
  // if (fadeAmount > 0)
365
375
  {
@@ -390,13 +400,14 @@ export function getColors(
390
400
  scheme = indices.map(
391
401
  (c, i) => {
392
402
  const color = colorScale(i);
393
- const alpha = amount(i);
394
- if (color == null || alpha == null) return '#000';
403
+ const a = amount(i);
404
+ if (color == null || a == null) return '#000';
395
405
  const col = hcl(color);
396
- col.l = darkMode
397
- ? col.l - col.l * alpha
398
- : col.l + (100 - col.l) * alpha;
399
- col.c = col.c - col.c * (alpha / 4);
406
+ col.l = darkMode ? col.l - col.l * a : col.l + (100 - col.l) * a;
407
+ col.c = col.c - col.c * (a / 4);
408
+ if (fadeOpacityEnabled) {
409
+ col.opacity = col.opacity * (1.0 - a);
410
+ }
400
411
  return col.toString();
401
412
  },
402
413
  // interpolateRgbBasis([colorScale(i), darkMode ? '#000' : '#fff'])(amount(i))
@@ -417,15 +428,46 @@ export function getColors(
417
428
  };
418
429
  }
419
430
 
431
+ function interpolateRgbaBasis(colors: string[]) {
432
+ const spline = interpolateBasis;
433
+ const n = colors.length;
434
+ let r: any = new Array(n),
435
+ g: any = new Array(n),
436
+ b: any = new Array(n),
437
+ opacity: any = new Array(n),
438
+ i,
439
+ color: any;
440
+ for (i = 0; i < n; ++i) {
441
+ color = colorRgb(colors[i]);
442
+ r[i] = color.r || 0;
443
+ g[i] = color.g || 0;
444
+ b[i] = color.b || 0;
445
+ opacity[i] = color.opacity || 0;
446
+ }
447
+ r = spline(r);
448
+ g = spline(g);
449
+ b = spline(b);
450
+ opacity = spline(opacity);
451
+ // color.opacity = 1;
452
+ return function (t: number) {
453
+ color.r = r(t);
454
+ color.g = g(t);
455
+ color.b = b(t);
456
+ color.opacity = opacity(t);
457
+ return color + '';
458
+ };
459
+ }
460
+
420
461
  export function createFlowColorScale(
421
462
  domain: [number, number],
422
463
  scheme: string[],
423
464
  animate: boolean | undefined,
424
465
  ): ColorScale {
425
- const scale = scaleSequentialPow(interpolateRgbBasis(scheme))
466
+ const scale = scaleSequentialPow(interpolateRgbaBasis(scheme))
426
467
  // @ts-ignore
427
468
  .exponent(animate ? 1 / 2 : 1 / 3)
428
469
  .domain(domain);
470
+
429
471
  return (value: number) => colorAsRgba(scale(value));
430
472
  }
431
473
 
@@ -516,7 +558,7 @@ export interface LocationCircleColors {
516
558
  incoming?: string;
517
559
  highlighted?: string;
518
560
  empty?: string;
519
- emptyOutline?: string;
561
+ outlineEmptyMix?: number;
520
562
  }
521
563
 
522
564
  export interface LocationAreaColors {
@@ -563,7 +605,7 @@ export interface LocationCircleColorsRGBA {
563
605
  incoming: RGBA;
564
606
  highlighted: RGBA;
565
607
  empty: RGBA;
566
- emptyOutline: RGBA;
608
+ outlineEmptyMix: number;
567
609
  }
568
610
 
569
611
  export interface LocationAreaColorsRGBA {
@@ -648,7 +690,7 @@ function getFlowAndCircleColors(
648
690
  flowColorHighlighted,
649
691
  ),
650
692
  empty: emptyColor,
651
- emptyOutline: mixColorsRGBA(innerColor, emptyColor, 0.4),
693
+ outlineEmptyMix: inputColors?.locationCircles?.outlineEmptyMix ?? 0.4,
652
694
  },
653
695
  };
654
696
  }