@flowmap.gl/data 8.0.3 → 9.1.0

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.
@@ -7,12 +7,7 @@
7
7
  import {ascending, descending, extent, min, rollup} from 'd3-array';
8
8
  import {ScaleLinear, scaleSqrt} from 'd3-scale';
9
9
  import KDBush from 'kdbush';
10
- import {
11
- ParametricSelector,
12
- createSelector,
13
- createSelectorCreator,
14
- defaultMemoize,
15
- } from 'reselect';
10
+ import {createSelector, createSelectorCreator, lruMemoize} from 'reselect';
16
11
  import {alea} from 'seedrandom';
17
12
  import FlowmapAggregateAccessors from './FlowmapAggregateAccessors';
18
13
  import {FlowmapState} from './FlowmapState';
@@ -53,22 +48,23 @@ import {
53
48
  FlowAccessors,
54
49
  FlowCirclesLayerAttributes,
55
50
  FlowLinesLayerAttributes,
51
+ FlowLinesRenderingMode,
56
52
  FlowmapData,
57
53
  FlowmapDataAccessors,
58
54
  LayersData,
59
55
  LocationFilterMode,
60
56
  LocationTotals,
57
+ ViewportProps,
61
58
  isLocationClusterNode,
62
59
  } from './types';
63
60
 
64
61
  const MAX_CLUSTER_ZOOM_LEVEL = 20;
65
62
  type KDBushTree = any;
66
63
 
67
- export type Selector<L, F, T> = ParametricSelector<
68
- FlowmapState,
69
- FlowmapData<L, F>,
70
- T
71
- >;
64
+ export type Selector<L, F, T> = (
65
+ state: FlowmapState,
66
+ props: FlowmapData<L, F>,
67
+ ) => T;
72
68
 
73
69
  export default class FlowmapSelectors<
74
70
  L extends Record<string, any>,
@@ -147,10 +143,15 @@ export default class FlowmapSelectors<
147
143
  props: FlowmapData<L, F>,
148
144
  ) => state.settings.fadeAmount;
149
145
 
150
- getAnimate: Selector<L, F, boolean> = (
146
+ getFlowLinesRenderingMode: Selector<L, F, FlowLinesRenderingMode> = (
151
147
  state: FlowmapState,
152
148
  props: FlowmapData<L, F>,
153
- ) => state.settings.animationEnabled;
149
+ ) => state.settings.flowLinesRenderingMode;
150
+
151
+ getAnimate: Selector<L, F, boolean> = createSelector(
152
+ this.getFlowLinesRenderingMode,
153
+ (flowLinesRenderingMode) => flowLinesRenderingMode === 'animated-straight',
154
+ );
154
155
 
155
156
  getInvalidLocationIds: Selector<L, F, (string | number)[] | undefined> =
156
157
  createSelector(this.getLocationsFromProps, (locations) => {
@@ -246,7 +247,7 @@ export default class FlowmapSelectors<
246
247
  (flows, timeExtent) => {
247
248
  if (!flows || !timeExtent) return undefined;
248
249
 
249
- const minOrder = min(flows, (d) => {
250
+ const minOrder = min(flows, (d: F) => {
250
251
  const t = this.accessors.getFlowTime(d);
251
252
  return t ? getTimeGranularityForDate(t).order : null;
252
253
  });
@@ -286,7 +287,7 @@ export default class FlowmapSelectors<
286
287
  ) {
287
288
  return flows;
288
289
  }
289
- return flows.filter((flow) => {
290
+ return flows.filter((flow: F) => {
290
291
  const time = this.accessors.getFlowTime(flow);
291
292
  return time && timeRange[0] <= time && time < timeRange[1];
292
293
  });
@@ -403,7 +404,7 @@ export default class FlowmapSelectors<
403
404
  }
404
405
 
405
406
  return clusterIndex.availableZoomLevels.filter(
406
- (level) => minZoom <= level && level <= maxZoom,
407
+ (level: number) => minZoom <= level && level <= maxZoom,
407
408
  );
408
409
  },
409
410
  );
@@ -627,7 +628,7 @@ export default class FlowmapSelectors<
627
628
  ? getTimeGranularityByKey(timeGranularityKey)
628
629
  : undefined;
629
630
  if (!flows || !timeGranularity || !timeExtent) return undefined;
630
- const byTime = flows.reduce((m, flow) => {
631
+ const byTime = flows.reduce((m: Map<number, number>, flow: F) => {
631
632
  if (
632
633
  this.isFlowInSelection(
633
634
  flow,
@@ -646,10 +647,12 @@ export default class FlowmapSelectors<
646
647
  return m;
647
648
  }, new Map<number, number>());
648
649
 
649
- return Array.from(byTime.entries()).map(([millis, count]) => ({
650
- time: new Date(millis),
651
- count,
652
- }));
650
+ return Array.from(byTime.entries()).map(
651
+ ([millis, count]: [number, number]) => ({
652
+ time: new Date(millis),
653
+ count,
654
+ }),
655
+ );
653
656
  },
654
657
  );
655
658
 
@@ -766,21 +769,21 @@ export default class FlowmapSelectors<
766
769
  );
767
770
 
768
771
  getLocationIdsInViewport: Selector<L, F, Set<string | number> | undefined> =
769
- createSelectorCreator(
770
- defaultMemoize,
771
- // @ts-ignore
772
- (
773
- s1: Set<string> | undefined,
774
- s2: Set<string> | undefined,
775
- index: number,
776
- ) => {
777
- if (s1 === s2) return true;
778
- if (s1 == null || s2 == null) return false;
779
- if (s1.size !== s2.size) return false;
780
- for (const item of s1) if (!s2.has(item)) return false;
781
- return true;
772
+ createSelectorCreator({
773
+ memoize: lruMemoize,
774
+ memoizeOptions: {
775
+ equalityCheck: (
776
+ s1: Set<string> | undefined,
777
+ s2: Set<string> | undefined,
778
+ ) => {
779
+ if (s1 === s2) return true;
780
+ if (s1 == null || s2 == null) return false;
781
+ if (s1.size !== s2.size) return false;
782
+ for (const item of s1) if (!s2.has(item)) return false;
783
+ return true;
784
+ },
782
785
  },
783
- )(
786
+ })(
784
787
  this._getLocationIdsInViewport,
785
788
  (locationIds: Set<string> | undefined) => {
786
789
  if (!locationIds) return undefined;
@@ -793,7 +796,7 @@ export default class FlowmapSelectors<
793
796
  (flows) => {
794
797
  if (!flows) return undefined;
795
798
  return flows.reduce(
796
- (m, flow) => m + this.accessors.getFlowMagnitude(flow),
799
+ (m: number, flow: F) => m + this.accessors.getFlowMagnitude(flow),
797
800
  0,
798
801
  );
799
802
  },
@@ -805,7 +808,7 @@ export default class FlowmapSelectors<
805
808
  this.getLocationFilterMode,
806
809
  (flows, selectedLocationSet, locationFilterMode) => {
807
810
  if (!flows) return undefined;
808
- const count = flows.reduce((m, flow) => {
811
+ const count = flows.reduce((m: number, flow: F | AggregateFlow) => {
809
812
  if (
810
813
  this.isFlowInSelection(flow, selectedLocationSet, locationFilterMode)
811
814
  ) {
@@ -1075,11 +1078,14 @@ export default class FlowmapSelectors<
1075
1078
  getLocationsForFlowmapLayerById: Selector<
1076
1079
  L,
1077
1080
  F,
1078
- Map<string, L | ClusterNode> | undefined
1081
+ Map<string | number, L | ClusterNode> | undefined
1079
1082
  > = createSelector(this.getLocationsForFlowmapLayer, (locations) => {
1080
1083
  if (!locations) return undefined;
1081
1084
  return locations.reduce(
1082
- (m, d) => (m.set(this.accessors.getLocationId(d), d), m),
1085
+ (m: Map<string | number, L | ClusterNode>, d: L | ClusterNode) => (
1086
+ m.set(this.accessors.getLocationId(d), d),
1087
+ m
1088
+ ),
1083
1089
  new Map(),
1084
1090
  );
1085
1091
  });
@@ -1102,7 +1108,8 @@ export default class FlowmapSelectors<
1102
1108
  this.getInCircleSizeGetter,
1103
1109
  this.getOutCircleSizeGetter,
1104
1110
  this.getFlowThicknessScale,
1105
- this.getAnimate,
1111
+ this.getViewport,
1112
+ this.getFlowLinesRenderingMode,
1106
1113
  this.getLocationLabelsEnabled,
1107
1114
  (
1108
1115
  locations,
@@ -1113,7 +1120,8 @@ export default class FlowmapSelectors<
1113
1120
  getInCircleSize,
1114
1121
  getOutCircleSize,
1115
1122
  flowThicknessScale,
1116
- animationEnabled,
1123
+ viewport,
1124
+ flowLinesRenderingMode,
1117
1125
  locationLabelsEnabled,
1118
1126
  ) => {
1119
1127
  return this._prepareLayersData(
@@ -1125,7 +1133,8 @@ export default class FlowmapSelectors<
1125
1133
  getInCircleSize,
1126
1134
  getOutCircleSize,
1127
1135
  flowThicknessScale,
1128
- animationEnabled,
1136
+ viewport,
1137
+ flowLinesRenderingMode,
1129
1138
  locationLabelsEnabled,
1130
1139
  );
1131
1140
  },
@@ -1134,13 +1143,16 @@ export default class FlowmapSelectors<
1134
1143
  prepareLayersData(state: FlowmapState, props: FlowmapData<L, F>): LayersData {
1135
1144
  const locations = this.getLocationsForFlowmapLayer(state, props) || [];
1136
1145
  const flows = this.getFlowsForFlowmapLayer(state, props) || [];
1137
- const flowmapColors = this.getFlowmapColorsRGBA(state, props);
1146
+ const flowmapColors = (
1147
+ this.getFlowmapColorsRGBA as Selector<L, F, DiffColorsRGBA | ColorsRGBA>
1148
+ )(state, props);
1138
1149
  const locationsById = this.getLocationsForFlowmapLayerById(state, props);
1139
1150
  const locationIdsInViewport = this.getLocationIdsInViewport(state, props);
1140
1151
  const getInCircleSize = this.getInCircleSizeGetter(state, props);
1141
1152
  const getOutCircleSize = this.getOutCircleSizeGetter(state, props);
1142
1153
  const flowThicknessScale = this.getFlowThicknessScale(state, props);
1143
1154
  const locationLabelsEnabled = this.getLocationLabelsEnabled(state, props);
1155
+ const viewport = this.getViewport(state, props);
1144
1156
  return this._prepareLayersData(
1145
1157
  locations,
1146
1158
  flows,
@@ -1150,7 +1162,8 @@ export default class FlowmapSelectors<
1150
1162
  getInCircleSize,
1151
1163
  getOutCircleSize,
1152
1164
  flowThicknessScale,
1153
- state.settings.animationEnabled,
1165
+ viewport,
1166
+ state.settings.flowLinesRenderingMode,
1154
1167
  locationLabelsEnabled,
1155
1168
  );
1156
1169
  }
@@ -1164,7 +1177,8 @@ export default class FlowmapSelectors<
1164
1177
  getInCircleSize: (locationId: string | number) => number,
1165
1178
  getOutCircleSize: (locationId: string | number) => number,
1166
1179
  flowThicknessScale: ScaleLinear<number, number, never> | undefined,
1167
- animationEnabled: boolean,
1180
+ viewport: ViewportProps,
1181
+ flowLinesRenderingMode: FlowLinesRenderingMode,
1168
1182
  locationLabelsEnabled: boolean,
1169
1183
  ): LayersData {
1170
1184
  if (!locations) locations = [];
@@ -1186,15 +1200,16 @@ export default class FlowmapSelectors<
1186
1200
  const flowColorScale = getFlowColorScale(
1187
1201
  flowmapColors,
1188
1202
  flowMagnitudeExtent,
1189
- false,
1203
+ flowLinesRenderingMode === 'animated-straight',
1190
1204
  );
1191
1205
 
1192
1206
  // Using a generator here helps to avoid creating intermediary arrays
1193
- const circlePositions = Float32Array.from(
1207
+ const circlePositions = Float64Array.from(
1194
1208
  (function* () {
1195
1209
  for (const location of locations) {
1196
1210
  yield getLocationLon(location);
1197
1211
  yield getLocationLat(location);
1212
+ yield 0;
1198
1213
  }
1199
1214
  })(),
1200
1215
  );
@@ -1229,21 +1244,23 @@ export default class FlowmapSelectors<
1229
1244
  })(),
1230
1245
  );
1231
1246
 
1232
- const sourcePositions = Float32Array.from(
1247
+ const sourcePositions = Float64Array.from(
1233
1248
  (function* () {
1234
1249
  for (const flow of flows) {
1235
1250
  const loc = locationsById?.get(getFlowOriginId(flow));
1236
1251
  yield loc ? getLocationLon(loc) : 0;
1237
1252
  yield loc ? getLocationLat(loc) : 0;
1253
+ yield 0;
1238
1254
  }
1239
1255
  })(),
1240
1256
  );
1241
- const targetPositions = Float32Array.from(
1257
+ const targetPositions = Float64Array.from(
1242
1258
  (function* () {
1243
1259
  for (const flow of flows) {
1244
1260
  const loc = locationsById?.get(getFlowDestId(flow));
1245
1261
  yield loc ? getLocationLon(loc) : 0;
1246
1262
  yield loc ? getLocationLat(loc) : 0;
1263
+ yield 0;
1247
1264
  }
1248
1265
  })(),
1249
1266
  );
@@ -1274,22 +1291,36 @@ export default class FlowmapSelectors<
1274
1291
  })(),
1275
1292
  );
1276
1293
 
1277
- const staggeringValues = animationEnabled
1278
- ? Float32Array.from(
1279
- (function* () {
1280
- for (const f of flows) {
1281
- // @ts-ignore
1282
- yield new alea(`${getFlowOriginId(f)}-${getFlowDestId(f)}`)();
1283
- }
1284
- })(),
1285
- )
1286
- : undefined;
1294
+ const staggeringValues =
1295
+ flowLinesRenderingMode === 'animated-straight'
1296
+ ? Float32Array.from(
1297
+ (function* () {
1298
+ for (const f of flows) {
1299
+ // @ts-ignore
1300
+ yield new alea(`${getFlowOriginId(f)}-${getFlowDestId(f)}`)();
1301
+ }
1302
+ })(),
1303
+ )
1304
+ : undefined;
1305
+
1306
+ const curveOffsets =
1307
+ flowLinesRenderingMode === 'curved'
1308
+ ? calculateCurveOffsets(
1309
+ flows,
1310
+ viewport,
1311
+ locationsById,
1312
+ getFlowOriginId,
1313
+ getFlowDestId,
1314
+ getLocationLon,
1315
+ getLocationLat,
1316
+ )
1317
+ : undefined;
1287
1318
 
1288
1319
  return {
1289
1320
  circleAttributes: {
1290
1321
  length: locations.length,
1291
1322
  attributes: {
1292
- getPosition: {value: circlePositions, size: 2},
1323
+ getPosition: {value: circlePositions, size: 3},
1293
1324
  getColor: {value: circleColors, size: 4},
1294
1325
  getInRadius: {value: inCircleRadii, size: 1},
1295
1326
  getOutRadius: {value: outCircleRadii, size: 1},
@@ -1298,14 +1329,17 @@ export default class FlowmapSelectors<
1298
1329
  lineAttributes: {
1299
1330
  length: flows.length,
1300
1331
  attributes: {
1301
- getSourcePosition: {value: sourcePositions, size: 2},
1302
- getTargetPosition: {value: targetPositions, size: 2},
1332
+ getSourcePosition: {value: sourcePositions, size: 3},
1333
+ getTargetPosition: {value: targetPositions, size: 3},
1303
1334
  getThickness: {value: thicknesses, size: 1},
1304
1335
  getColor: {value: flowLineColors, size: 4},
1305
1336
  getEndpointOffsets: {value: endpointOffsets, size: 2},
1306
1337
  ...(staggeringValues
1307
1338
  ? {getStaggering: {value: staggeringValues, size: 1}}
1308
1339
  : {}),
1340
+ ...(curveOffsets
1341
+ ? {getCurveOffset: {value: curveOffsets, size: 1}}
1342
+ : {}),
1309
1343
  },
1310
1344
  },
1311
1345
  ...(locationLabelsEnabled
@@ -1488,7 +1522,8 @@ export function getLocationCoordsByIndex(
1488
1522
  index: number,
1489
1523
  ): [number, number] {
1490
1524
  const {getPosition} = circleAttributes.attributes;
1491
- return [getPosition.value[index * 2], getPosition.value[index * 2 + 1]];
1525
+ const offset = index * getPosition.size;
1526
+ return [getPosition.value[offset], getPosition.value[offset + 1]];
1492
1527
  }
1493
1528
 
1494
1529
  export function getFlowLineAttributesByIndex(
@@ -1497,6 +1532,7 @@ export function getFlowLineAttributesByIndex(
1497
1532
  ): FlowLinesLayerAttributes {
1498
1533
  const {
1499
1534
  getColor,
1535
+ getCurveOffset,
1500
1536
  getEndpointOffsets,
1501
1537
  getSourcePosition,
1502
1538
  getTargetPosition,
@@ -1515,12 +1551,18 @@ export function getFlowLineAttributesByIndex(
1515
1551
  size: 2,
1516
1552
  },
1517
1553
  getSourcePosition: {
1518
- value: getSourcePosition.value.subarray(index * 2, (index + 1) * 2),
1519
- size: 2,
1554
+ value: getSourcePosition.value.subarray(
1555
+ index * getSourcePosition.size,
1556
+ (index + 1) * getSourcePosition.size,
1557
+ ),
1558
+ size: getSourcePosition.size,
1520
1559
  },
1521
1560
  getTargetPosition: {
1522
- value: getTargetPosition.value.subarray(index * 2, (index + 1) * 2),
1523
- size: 2,
1561
+ value: getTargetPosition.value.subarray(
1562
+ index * getTargetPosition.size,
1563
+ (index + 1) * getTargetPosition.size,
1564
+ ),
1565
+ size: getTargetPosition.size,
1524
1566
  },
1525
1567
  getThickness: {
1526
1568
  value: getThickness.value.subarray(index, index + 1),
@@ -1534,6 +1576,122 @@ export function getFlowLineAttributesByIndex(
1534
1576
  },
1535
1577
  }
1536
1578
  : undefined),
1579
+ ...(getCurveOffset
1580
+ ? {
1581
+ getCurveOffset: {
1582
+ value: getCurveOffset.value.subarray(index, index + 1),
1583
+ size: 1,
1584
+ },
1585
+ }
1586
+ : undefined),
1537
1587
  },
1538
1588
  };
1539
1589
  }
1590
+
1591
+ type FlowLineScreenGeometry = {
1592
+ index: number;
1593
+ originId: string | number;
1594
+ destId: string | number;
1595
+ sx: number;
1596
+ sy: number;
1597
+ tx: number;
1598
+ ty: number;
1599
+ chordLengthPx: number;
1600
+ };
1601
+
1602
+ function calculateCurveOffsets<L, F>(
1603
+ flows: (F | AggregateFlow)[],
1604
+ viewport: ViewportProps,
1605
+ locationsById: Map<string | number, L | ClusterNode> | undefined,
1606
+ getFlowOriginId: (flow: F | AggregateFlow) => string | number,
1607
+ getFlowDestId: (flow: F | AggregateFlow) => string | number,
1608
+ getLocationLon: (location: L | ClusterNode) => number,
1609
+ getLocationLat: (location: L | ClusterNode) => number,
1610
+ ): Float32Array {
1611
+ const curveOffsets = new Float32Array(flows.length);
1612
+ const corridorBuckets = new Map<string, FlowLineScreenGeometry[]>();
1613
+ const worldScale = 512 * Math.pow(2, viewport.zoom ?? 0);
1614
+
1615
+ flows.forEach((flow, index) => {
1616
+ const originId = getFlowOriginId(flow);
1617
+ const destId = getFlowDestId(flow);
1618
+ const origin = locationsById?.get(originId);
1619
+ const dest = locationsById?.get(destId);
1620
+ if (!origin || !dest) {
1621
+ return;
1622
+ }
1623
+
1624
+ const sourceLon = getLocationLon(origin);
1625
+ const sourceLat = getLocationLat(origin);
1626
+ const targetLon = getLocationLon(dest);
1627
+ const targetLat = getLocationLat(dest);
1628
+ const sx = lngX(sourceLon) * worldScale;
1629
+ const sy = latY(sourceLat) * worldScale;
1630
+ const tx = lngX(targetLon) * worldScale;
1631
+ const ty = latY(targetLat) * worldScale;
1632
+
1633
+ let corridorSourceX = sx;
1634
+ let corridorSourceY = sy;
1635
+ let corridorTargetX = tx;
1636
+ let corridorTargetY = ty;
1637
+ if (
1638
+ corridorSourceX > corridorTargetX ||
1639
+ (corridorSourceX === corridorTargetX && corridorSourceY > corridorTargetY)
1640
+ ) {
1641
+ [corridorSourceX, corridorTargetX] = [corridorTargetX, corridorSourceX];
1642
+ [corridorSourceY, corridorTargetY] = [corridorTargetY, corridorSourceY];
1643
+ }
1644
+
1645
+ const dx = corridorTargetX - corridorSourceX;
1646
+ const dy = corridorTargetY - corridorSourceY;
1647
+ const chordLengthPx = Math.hypot(dx, dy);
1648
+ if (!isFinite(chordLengthPx) || chordLengthPx < 1) {
1649
+ return;
1650
+ }
1651
+
1652
+ const angle = ((Math.atan2(dy, dx) % Math.PI) + Math.PI) % Math.PI;
1653
+ const signedDistance =
1654
+ (corridorSourceX * corridorTargetY - corridorSourceY * corridorTargetX) /
1655
+ chordLengthPx;
1656
+ const key = [
1657
+ Math.round(angle / ((6 * Math.PI) / 180)),
1658
+ Math.round(signedDistance / 18),
1659
+ Math.round(chordLengthPx / 24),
1660
+ ].join(':');
1661
+
1662
+ const bucket = corridorBuckets.get(key) ?? [];
1663
+ bucket.push({index, originId, destId, sx, sy, tx, ty, chordLengthPx});
1664
+ corridorBuckets.set(key, bucket);
1665
+ });
1666
+
1667
+ corridorBuckets.forEach((bucket) => {
1668
+ bucket
1669
+ .sort((a, b) => {
1670
+ const originCompare = compareIds(a.originId, b.originId);
1671
+ if (originCompare !== 0) return originCompare;
1672
+ const destCompare = compareIds(a.destId, b.destId);
1673
+ if (destCompare !== 0) return destCompare;
1674
+ return a.index - b.index;
1675
+ })
1676
+ .forEach((entry, bucketIndex) => {
1677
+ const maxOffsetPx = Math.min(72, entry.chordLengthPx * 0.35);
1678
+ curveOffsets[entry.index] = Math.min(
1679
+ maxOffsetPx,
1680
+ (bucketIndex + 1) * 18,
1681
+ );
1682
+ });
1683
+ });
1684
+
1685
+ return curveOffsets;
1686
+ }
1687
+
1688
+ function compareIds(a: string | number, b: string | number): number {
1689
+ if (typeof a === 'number' && typeof b === 'number') {
1690
+ return a - b;
1691
+ }
1692
+ const aString = String(a);
1693
+ const bString = String(b);
1694
+ if (aString < bString) return -1;
1695
+ if (aString > bString) return 1;
1696
+ return 0;
1697
+ }
@@ -4,7 +4,11 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import {LocationFilterMode, ViewportProps} from './types';
7
+ import {
8
+ FlowLinesRenderingMode,
9
+ LocationFilterMode,
10
+ ViewportProps,
11
+ } from './types';
8
12
 
9
13
  export type FlowEndpointsInViewportMode = 'any' | 'both';
10
14
 
@@ -15,7 +19,7 @@ export interface FilterState {
15
19
  }
16
20
 
17
21
  export interface SettingsState {
18
- animationEnabled: boolean;
22
+ flowLinesRenderingMode: FlowLinesRenderingMode;
19
23
  fadeEnabled: boolean;
20
24
  fadeOpacityEnabled: boolean;
21
25
  locationsEnabled: boolean;
@@ -28,7 +32,7 @@ export interface SettingsState {
28
32
  darkMode: boolean;
29
33
  fadeAmount: number;
30
34
  colorScheme: string | string[] | undefined;
31
- highlightColor: string;
35
+ highlightColor: string | number[];
32
36
  maxTopFlowsDisplayNum: number;
33
37
  flowEndpointsInViewportMode: FlowEndpointsInViewportMode;
34
38
  }
package/src/colors.ts CHANGED
@@ -35,6 +35,7 @@ import {scalePow, scaleSequential, scaleSequentialPow} from 'd3-scale';
35
35
  import {interpolateBasis, interpolateRgbBasis} from 'd3-interpolate';
36
36
  import {color as d3color, hcl, rgb as colorRgb} from 'd3-color';
37
37
  import {SettingsState} from './FlowmapState';
38
+ import {FlowLinesRenderingMode} from './types';
38
39
 
39
40
  const DEFAULT_OUTLINE_COLOR = '#fff';
40
41
  const DEFAULT_DIMMED_OPACITY = 0.4;
@@ -347,10 +348,16 @@ export function getFlowmapColors(settings: SettingsState): Colors | DiffColors {
347
348
  settings.fadeEnabled,
348
349
  settings.fadeOpacityEnabled,
349
350
  settings.fadeAmount,
350
- settings.animationEnabled,
351
+ isAnimatedFlowLinesMode(settings.flowLinesRenderingMode),
351
352
  );
352
353
  }
353
354
 
355
+ function isAnimatedFlowLinesMode(
356
+ flowLinesRenderingMode: FlowLinesRenderingMode,
357
+ ): boolean {
358
+ return flowLinesRenderingMode === 'animated-straight';
359
+ }
360
+
354
361
  export function getColors(
355
362
  diffMode: boolean,
356
363
  colorScheme: string | string[] | undefined,
@@ -30,6 +30,14 @@ export const getViewportBoundingBox = (
30
30
  return [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]];
31
31
  };
32
32
 
33
+ export const makeViewportProjector = (viewport: ViewportProps) => {
34
+ const mercatorViewport = new WebMercatorViewport(viewport);
35
+ return (coords: [number, number]): [number, number] => {
36
+ const [x, y] = mercatorViewport.project(coords);
37
+ return [x, y];
38
+ };
39
+ };
40
+
33
41
  export const getFlowThicknessScale = (
34
42
  magnitudeExtent: [number, number] | undefined,
35
43
  ) => {
package/src/types.ts CHANGED
@@ -21,6 +21,10 @@ export interface ViewState {
21
21
 
22
22
  export type FlowAccessor<F, T> = (flow: F) => T; // objectInfo?: AccessorObjectInfo,
23
23
  export type LocationAccessor<L, T> = (location: L) => T;
24
+ export type FlowLinesRenderingMode =
25
+ | 'straight'
26
+ | 'animated-straight'
27
+ | 'curved';
24
28
 
25
29
  export interface FlowAccessors<F> {
26
30
  getFlowOriginId: FlowAccessor<F, string | number>;
@@ -144,7 +148,7 @@ export enum LocationFilterMode {
144
148
  export interface FlowCirclesLayerAttributes {
145
149
  length: number;
146
150
  attributes: {
147
- getPosition: LayersDataAttrValues<Float32Array>;
151
+ getPosition: LayersDataAttrValues<Float64Array>;
148
152
  getColor: LayersDataAttrValues<Uint8Array>;
149
153
  getInRadius: LayersDataAttrValues<Float32Array>;
150
154
  getOutRadius: LayersDataAttrValues<Float32Array>;
@@ -154,12 +158,13 @@ export interface FlowCirclesLayerAttributes {
154
158
  export interface FlowLinesLayerAttributes {
155
159
  length: number;
156
160
  attributes: {
157
- getSourcePosition: LayersDataAttrValues<Float32Array>;
158
- getTargetPosition: LayersDataAttrValues<Float32Array>;
161
+ getSourcePosition: LayersDataAttrValues<Float64Array>;
162
+ getTargetPosition: LayersDataAttrValues<Float64Array>;
159
163
  getThickness: LayersDataAttrValues<Float32Array>;
160
164
  getColor: LayersDataAttrValues<Uint8Array>;
161
165
  getEndpointOffsets: LayersDataAttrValues<Float32Array>;
162
166
  getStaggering?: LayersDataAttrValues<Float32Array>;
167
+ getCurveOffset?: LayersDataAttrValues<Float32Array>;
163
168
  };
164
169
  }
165
170
 
package/src/util.ts CHANGED
@@ -4,9 +4,9 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import {createSelectorCreator, defaultMemoize} from 'reselect';
7
+ import {createSelectorCreator, lruMemoize} from 'reselect';
8
8
 
9
- export const createDebugSelector = createSelectorCreator(defaultMemoize, {
9
+ export const createDebugSelector = createSelectorCreator(lruMemoize, {
10
10
  equalityCheck: (previousVal: any, currentVal: any) => {
11
11
  const rv = currentVal === previousVal;
12
12
  if (!rv) {
package/tsconfig.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "extends": "../../tsconfig.common.json",
3
3
  "compilerOptions": {
4
4
  "noEmit": false,
5
- "outDir": "dist"
5
+ "outDir": "dist",
6
+ "rootDir": "src"
6
7
  },
7
8
  "include": [
8
9
  "src/**/*.ts",