@coinbase/cds-mobile-visualization 3.4.0-beta.18 → 3.4.0-beta.19

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 (68) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dts/chart/Path.d.ts +35 -13
  3. package/dts/chart/Path.d.ts.map +1 -1
  4. package/dts/chart/area/Area.d.ts +7 -11
  5. package/dts/chart/area/Area.d.ts.map +1 -1
  6. package/dts/chart/area/AreaChart.d.ts +1 -1
  7. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  8. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  9. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  10. package/dts/chart/bar/Bar.d.ts +32 -2
  11. package/dts/chart/bar/Bar.d.ts.map +1 -1
  12. package/dts/chart/bar/BarChart.d.ts +2 -0
  13. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  14. package/dts/chart/bar/BarPlot.d.ts +2 -1
  15. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  16. package/dts/chart/bar/BarStack.d.ts +5 -10
  17. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  18. package/dts/chart/bar/BarStackGroup.d.ts +1 -0
  19. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  20. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  21. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  22. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  23. package/dts/chart/line/Line.d.ts +4 -9
  24. package/dts/chart/line/Line.d.ts.map +1 -1
  25. package/dts/chart/line/LineChart.d.ts +1 -1
  26. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  27. package/dts/chart/point/Point.d.ts +18 -2
  28. package/dts/chart/point/Point.d.ts.map +1 -1
  29. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +9 -1
  30. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
  31. package/dts/chart/scrubber/Scrubber.d.ts +45 -24
  32. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  33. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +9 -1
  34. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -1
  35. package/dts/chart/utils/bar.d.ts +34 -0
  36. package/dts/chart/utils/bar.d.ts.map +1 -1
  37. package/dts/chart/utils/path.d.ts +6 -0
  38. package/dts/chart/utils/path.d.ts.map +1 -1
  39. package/dts/chart/utils/transition.d.ts +59 -21
  40. package/dts/chart/utils/transition.d.ts.map +1 -1
  41. package/esm/chart/Path.js +16 -14
  42. package/esm/chart/__stories__/CartesianChart.stories.js +3 -77
  43. package/esm/chart/__stories__/ChartTransitions.stories.js +629 -0
  44. package/esm/chart/area/Area.js +2 -0
  45. package/esm/chart/area/DottedArea.js +7 -3
  46. package/esm/chart/area/GradientArea.js +4 -2
  47. package/esm/chart/area/SolidArea.js +4 -2
  48. package/esm/chart/bar/Bar.js +2 -0
  49. package/esm/chart/bar/BarChart.js +4 -2
  50. package/esm/chart/bar/BarPlot.js +2 -0
  51. package/esm/chart/bar/BarStack.js +3 -0
  52. package/esm/chart/bar/DefaultBar.js +15 -15
  53. package/esm/chart/bar/DefaultBarStack.js +14 -3
  54. package/esm/chart/bar/__stories__/BarChart.stories.js +448 -52
  55. package/esm/chart/line/DottedLine.js +4 -2
  56. package/esm/chart/line/Line.js +6 -18
  57. package/esm/chart/line/SolidLine.js +4 -2
  58. package/esm/chart/line/__stories__/LineChart.stories.js +17 -226
  59. package/esm/chart/line/__stories__/ReferenceLine.stories.js +95 -1
  60. package/esm/chart/point/Point.js +33 -35
  61. package/esm/chart/scrubber/DefaultScrubberBeacon.js +2 -5
  62. package/esm/chart/scrubber/Scrubber.js +10 -8
  63. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +29 -7
  64. package/esm/chart/utils/bar.js +43 -0
  65. package/esm/chart/utils/path.js +8 -0
  66. package/esm/chart/utils/transition.js +96 -61
  67. package/package.json +5 -5
  68. package/esm/chart/__stories__/Chart.stories.js +0 -77
@@ -1,13 +1,11 @@
1
1
  const _excluded = ["dataX", "dataY"],
2
2
  _excluded2 = ["label"],
3
- _excluded3 = ["style"],
4
- _excluded4 = ["x", "y", "width", "height", "originY", "dataX"];
3
+ _excluded3 = ["style"];
5
4
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
6
5
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
7
- import { forwardRef, memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
6
+ import { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
7
  import { useAnimatedReaction, useDerivedValue, useSharedValue, withDelay, withTiming } from 'react-native-reanimated';
9
8
  import { assets, ethBackground } from '@coinbase/cds-common/internal/data/assets';
10
- import { candles as btcCandles } from '@coinbase/cds-common/internal/data/candles';
11
9
  import { prices } from '@coinbase/cds-common/internal/data/prices';
12
10
  import { sparklineInteractiveData } from '@coinbase/cds-common/internal/visualizations/SparklineInteractiveData';
13
11
  import { useTabsContext } from '@coinbase/cds-common/tabs/TabsContext';
@@ -23,16 +21,15 @@ import { SectionHeader } from '@coinbase/cds-mobile/section-header/SectionHeader
23
21
  import { Pressable } from '@coinbase/cds-mobile/system';
24
22
  import { SegmentedTab } from '@coinbase/cds-mobile/tabs/SegmentedTab';
25
23
  import { Text } from '@coinbase/cds-mobile/typography';
26
- import { Circle, FontWeight, Group, Line as SkiaLine, Rect, Skia, TextAlign } from '@shopify/react-native-skia';
24
+ import { Circle, FontWeight, Group, Skia, TextAlign } from '@shopify/react-native-skia';
27
25
  import { Area, DottedArea } from '../../area';
28
26
  import { DefaultAxisTickLabel, XAxis, YAxis } from '../../axis';
29
- import { BarPlot } from '../../bar';
30
27
  import { CartesianChart } from '../../CartesianChart';
31
28
  import { useCartesianChartContext } from '../../ChartProvider';
32
29
  import { PeriodSelector, PeriodSelectorActiveIndicator } from '../../PeriodSelector';
33
30
  import { Point } from '../../point';
34
31
  import { DefaultScrubberBeacon, Scrubber } from '../../scrubber';
35
- import { buildTransition, defaultTransition, getPointOnSerializableScale, projectPointWithSerializableScale, unwrapAnimatedValue, useScrubberContext } from '../../utils';
32
+ import { buildTransition, defaultTransition, projectPointWithSerializableScale, unwrapAnimatedValue, useScrubberContext } from '../../utils';
36
33
  import { DottedLine, Line, LineChart, ReferenceLine, SolidLine } from '..';
37
34
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
38
35
  function MultipleLine() {
@@ -1180,210 +1177,6 @@ function Performance() {
1180
1177
  })]
1181
1178
  });
1182
1179
  }
1183
- const candlestickStockData = btcCandles.slice(0, 90).reverse();
1184
- const CandlesticksHeader = /*#__PURE__*/memo(_ref11 => {
1185
- let {
1186
- currentIndex
1187
- } = _ref11;
1188
- const formatPrice = useCallback(price => {
1189
- return new Intl.NumberFormat('en-US', {
1190
- style: 'currency',
1191
- currency: 'USD'
1192
- }).format(parseFloat(price));
1193
- }, []);
1194
- const formatThousandsPriceNumber = useCallback(price => {
1195
- const formattedPrice = new Intl.NumberFormat('en-US', {
1196
- style: 'currency',
1197
- currency: 'USD',
1198
- minimumFractionDigits: 0,
1199
- maximumFractionDigits: 0
1200
- }).format(price / 1000);
1201
- return formattedPrice + "k";
1202
- }, []);
1203
- const currentText = useMemo(() => {
1204
- if (currentIndex !== undefined) {
1205
- return "Open: " + formatThousandsPriceNumber(parseFloat(candlestickStockData[currentIndex].open)) + ", Close: " + formatThousandsPriceNumber(parseFloat(candlestickStockData[currentIndex].close)) + ", Volume: " + (parseFloat(candlestickStockData[currentIndex].volume) / 1000).toFixed(2) + "k";
1206
- }
1207
- return formatPrice(candlestickStockData[candlestickStockData.length - 1].close);
1208
- }, [currentIndex, formatThousandsPriceNumber, formatPrice]);
1209
- return /*#__PURE__*/_jsx(Text, {
1210
- "aria-live": "polite",
1211
- font: "headline",
1212
- children: currentText
1213
- });
1214
- });
1215
- const CandlesticksChart = /*#__PURE__*/memo(_ref12 => {
1216
- let {
1217
- infoTextId,
1218
- onScrubberPositionChange
1219
- } = _ref12;
1220
- const theme = useTheme();
1221
- const min = useMemo(() => Math.min(...candlestickStockData.map(data => parseFloat(data.low))), []);
1222
- const ThinSolidLine = /*#__PURE__*/memo(props => /*#__PURE__*/_jsx(SolidLine, _extends({}, props, {
1223
- strokeWidth: 1
1224
- })));
1225
-
1226
- // Custom line component that renders a rect to highlight the entire bandwidth
1227
- const BandwidthHighlight = /*#__PURE__*/memo(_ref13 => {
1228
- let {
1229
- stroke
1230
- } = _ref13;
1231
- const {
1232
- getXSerializableScale,
1233
- drawingArea
1234
- } = useCartesianChartContext();
1235
- const {
1236
- scrubberPosition
1237
- } = useScrubberContext();
1238
- const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
1239
- const rectWidth = useMemo(() => {
1240
- if (xScale !== undefined && xScale.type === 'band') {
1241
- return xScale.bandwidth;
1242
- }
1243
- return 0;
1244
- }, [xScale]);
1245
- const xPos = useDerivedValue(() => {
1246
- const position = unwrapAnimatedValue(scrubberPosition);
1247
- const xPos = position !== undefined && xScale ? getPointOnSerializableScale(position, xScale) : undefined;
1248
- return xPos !== undefined ? xPos - rectWidth / 2 : 0;
1249
- }, [scrubberPosition, xScale]);
1250
- const opacity = useDerivedValue(() => xPos.value !== undefined ? 1 : 0, [xPos]);
1251
- return /*#__PURE__*/_jsx(Rect, {
1252
- color: stroke,
1253
- height: drawingArea.height,
1254
- opacity: opacity,
1255
- width: rectWidth,
1256
- x: xPos,
1257
- y: drawingArea.y
1258
- });
1259
- });
1260
- const candlesData = useMemo(() => candlestickStockData.map(data => [parseFloat(data.low), parseFloat(data.high)]), []);
1261
- const CandlestickBarComponent = /*#__PURE__*/memo(_ref14 => {
1262
- var _yScale, _yScale2;
1263
- let {
1264
- x,
1265
- y,
1266
- width,
1267
- height,
1268
- dataX
1269
- } = _ref14,
1270
- props = _objectWithoutPropertiesLoose(_ref14, _excluded4);
1271
- const {
1272
- getYScale
1273
- } = useCartesianChartContext();
1274
- const yScale = getYScale();
1275
- const wickX = x + width / 2;
1276
- const timePeriodValue = candlestickStockData[dataX];
1277
- const open = parseFloat(timePeriodValue.open);
1278
- const close = parseFloat(timePeriodValue.close);
1279
- const bullish = open < close;
1280
- const theme = useTheme();
1281
- const color = bullish ? theme.color.fgPositive : theme.color.fgNegative;
1282
- const openY = (_yScale = yScale == null ? void 0 : yScale(open)) != null ? _yScale : 0;
1283
- const closeY = (_yScale2 = yScale == null ? void 0 : yScale(close)) != null ? _yScale2 : 0;
1284
- const bodyHeight = Math.abs(openY - closeY);
1285
- const bodyY = openY < closeY ? openY : closeY;
1286
- return /*#__PURE__*/_jsxs(_Fragment, {
1287
- children: [/*#__PURE__*/_jsx(SkiaLine, {
1288
- color: color,
1289
- p1: {
1290
- x: wickX,
1291
- y
1292
- },
1293
- p2: {
1294
- x: wickX,
1295
- y: y + height
1296
- },
1297
- strokeWidth: 1
1298
- }), /*#__PURE__*/_jsx(Rect, {
1299
- color: color,
1300
- height: bodyHeight,
1301
- width: width,
1302
- x: x,
1303
- y: bodyY
1304
- })]
1305
- });
1306
- });
1307
- const formatThousandsPriceNumber = useCallback(price => {
1308
- const formattedPrice = new Intl.NumberFormat('en-US', {
1309
- style: 'currency',
1310
- currency: 'USD',
1311
- minimumFractionDigits: 0,
1312
- maximumFractionDigits: 0
1313
- }).format(price / 1000);
1314
- return formattedPrice + "k";
1315
- }, []);
1316
- const formatTime = useCallback(index => {
1317
- if (index === null || index === undefined || index >= candlestickStockData.length) return '';
1318
- const ts = parseInt(candlestickStockData[index].start);
1319
- return new Date(ts * 1000).toLocaleDateString('en-US', {
1320
- month: 'short',
1321
- day: 'numeric'
1322
- });
1323
- }, []);
1324
- return /*#__PURE__*/_jsxs(CartesianChart, {
1325
- enableScrubbing: true,
1326
- animate: false,
1327
- "aria-labelledby": infoTextId,
1328
- borderRadius: 0,
1329
- height: 150,
1330
- inset: {
1331
- top: 8,
1332
- bottom: 8,
1333
- left: 0,
1334
- right: 0
1335
- },
1336
- onScrubberPositionChange: onScrubberPositionChange,
1337
- series: [{
1338
- id: 'stock-prices',
1339
- data: candlesData
1340
- }],
1341
- xAxis: {
1342
- scaleType: 'band'
1343
- },
1344
- yAxis: {
1345
- domain: {
1346
- min
1347
- }
1348
- },
1349
- children: [/*#__PURE__*/_jsx(XAxis, {
1350
- tickLabelFormatter: formatTime
1351
- }), /*#__PURE__*/_jsx(YAxis, {
1352
- showGrid: true,
1353
- GridLineComponent: ThinSolidLine,
1354
- tickLabelFormatter: formatThousandsPriceNumber,
1355
- width: 40
1356
- }), /*#__PURE__*/_jsx(Scrubber, {
1357
- hideOverlay: true,
1358
- LineComponent: BandwidthHighlight,
1359
- lineStroke: theme.color.fgMuted,
1360
- seriesIds: []
1361
- }), /*#__PURE__*/_jsx(BarPlot, {
1362
- BarComponent: CandlestickBarComponent,
1363
- BarStackComponent: _ref15 => {
1364
- let {
1365
- children
1366
- } = _ref15;
1367
- return /*#__PURE__*/_jsx("g", {
1368
- children: children
1369
- });
1370
- }
1371
- })]
1372
- });
1373
- });
1374
- function Candlesticks() {
1375
- const infoTextId = useId();
1376
- const [currentIndex, setCurrentIndex] = useState();
1377
- return /*#__PURE__*/_jsxs(VStack, {
1378
- gap: 2,
1379
- children: [/*#__PURE__*/_jsx(CandlesticksHeader, {
1380
- currentIndex: currentIndex
1381
- }), /*#__PURE__*/_jsx(CandlesticksChart, {
1382
- infoTextId: infoTextId,
1383
- onScrubberPositionChange: setCurrentIndex
1384
- })]
1385
- });
1386
- }
1387
1180
  function MonotoneAssetPrice() {
1388
1181
  const theme = useTheme();
1389
1182
  const prices = sparklineInteractiveData.hour;
@@ -1457,14 +1250,14 @@ function MonotoneAssetPrice() {
1457
1250
  dy: -12,
1458
1251
  horizontalAlignment: "left"
1459
1252
  })), []);
1460
- const CustomScrubberBeacon = /*#__PURE__*/memo(_ref16 => {
1253
+ const CustomScrubberBeacon = /*#__PURE__*/memo(_ref11 => {
1461
1254
  let {
1462
1255
  dataX,
1463
1256
  dataY,
1464
1257
  seriesId,
1465
1258
  isIdle,
1466
1259
  animate = true
1467
- } = _ref16;
1260
+ } = _ref11;
1468
1261
  const {
1469
1262
  getSeries,
1470
1263
  getXSerializableScale,
@@ -1540,10 +1333,10 @@ function MonotoneAssetPrice() {
1540
1333
  color: theme.color.fg,
1541
1334
  gradient: {
1542
1335
  axis: 'x',
1543
- stops: _ref17 => {
1336
+ stops: _ref12 => {
1544
1337
  let {
1545
1338
  min
1546
- } = _ref17;
1339
+ } = _ref12;
1547
1340
  return [{
1548
1341
  offset: min,
1549
1342
  color: theme.color.fg,
@@ -1557,10 +1350,10 @@ function MonotoneAssetPrice() {
1557
1350
  }
1558
1351
  }],
1559
1352
  xAxis: {
1560
- range: _ref18 => {
1353
+ range: _ref13 => {
1561
1354
  let {
1562
1355
  max
1563
- } = _ref18;
1356
+ } = _ref13;
1564
1357
  return {
1565
1358
  min: 96,
1566
1359
  max
@@ -1611,11 +1404,11 @@ function ServiceAvailability() {
1611
1404
  id: 'availability',
1612
1405
  data: availabilityEvents.map(event => event.availability),
1613
1406
  gradient: {
1614
- stops: _ref19 => {
1407
+ stops: _ref14 => {
1615
1408
  let {
1616
1409
  min,
1617
1410
  max
1618
- } = _ref19;
1411
+ } = _ref14;
1619
1412
  return [{
1620
1413
  offset: min,
1621
1414
  color: theme.color.fgNegative
@@ -1642,11 +1435,11 @@ function ServiceAvailability() {
1642
1435
  data: availabilityEvents.map(event => event.date.getTime())
1643
1436
  },
1644
1437
  yAxis: {
1645
- domain: _ref20 => {
1438
+ domain: _ref15 => {
1646
1439
  let {
1647
1440
  min,
1648
1441
  max
1649
- } = _ref20;
1442
+ } = _ref15;
1650
1443
  return {
1651
1444
  min: Math.max(min - 2, 0),
1652
1445
  max: Math.min(max + 2, 100)
@@ -2059,11 +1852,11 @@ function ExampleNavigator() {
2059
1852
  }],
2060
1853
  xAxis: {
2061
1854
  // Give space before the end of the chart for the scrubber
2062
- range: _ref21 => {
1855
+ range: _ref16 => {
2063
1856
  let {
2064
1857
  min,
2065
1858
  max
2066
- } = _ref21;
1859
+ } = _ref16;
2067
1860
  return {
2068
1861
  min,
2069
1862
  max: max - 24
@@ -2094,9 +1887,6 @@ function ExampleNavigator() {
2094
1887
  }, {
2095
1888
  title: 'Performance',
2096
1889
  component: /*#__PURE__*/_jsx(Performance, {})
2097
- }, {
2098
- title: 'Candlesticks',
2099
- component: /*#__PURE__*/_jsx(Candlesticks, {})
2100
1890
  }, {
2101
1891
  title: 'Monotone Asset Price',
2102
1892
  component: /*#__PURE__*/_jsx(MonotoneAssetPrice, {})
@@ -2118,6 +1908,7 @@ function ExampleNavigator() {
2118
1908
  setCurrentIndex(prev => (prev + 1 + examples.length) % examples.length);
2119
1909
  }, [examples.length]);
2120
1910
  return /*#__PURE__*/_jsx(ExampleScreen, {
1911
+ paddingX: 0,
2121
1912
  children: /*#__PURE__*/_jsxs(VStack, {
2122
1913
  gap: 4,
2123
1914
  children: [/*#__PURE__*/_jsxs(HStack, {
@@ -1,9 +1,14 @@
1
1
  const _excluded = ["accentColor", "yellowColor"];
2
2
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
3
3
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
4
- import { memo, useCallback } from 'react';
4
+ import { memo, useCallback, useMemo } from 'react';
5
+ import { useDerivedValue, withTiming } from 'react-native-reanimated';
6
+ import { sparklineInteractiveData } from '@coinbase/cds-common/internal/visualizations/SparklineInteractiveData';
5
7
  import { useTheme } from '@coinbase/cds-mobile';
6
8
  import { Example, ExampleScreen } from '@coinbase/cds-mobile/examples/ExampleScreen';
9
+ import { useCartesianChartContext } from '../../ChartProvider';
10
+ import { Scrubber } from '../../scrubber';
11
+ import { getPointOnSerializableScale, useScrubberContext } from '../../utils';
7
12
  import { DefaultReferenceLineLabel } from '../DefaultReferenceLineLabel';
8
13
  import { DottedLine } from '../DottedLine';
9
14
  import { LineChart } from '../LineChart';
@@ -126,7 +131,96 @@ const ReferenceLineStories = () => {
126
131
  stroke: theme.color.bgWarning
127
132
  })
128
133
  })
134
+ }), /*#__PURE__*/_jsx(Example, {
135
+ title: "Start Price Reference Line",
136
+ children: /*#__PURE__*/_jsx(StartPriceReferenceLine, {})
129
137
  })]
130
138
  });
131
139
  };
140
+ const FADE_ZONE = 128;
141
+ const StartPriceLabel = /*#__PURE__*/memo(props => {
142
+ const theme = useTheme();
143
+ const {
144
+ scrubberPosition
145
+ } = useScrubberContext();
146
+ const {
147
+ getXSerializableScale,
148
+ drawingArea
149
+ } = useCartesianChartContext();
150
+ const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
151
+ const opacity = useDerivedValue(() => {
152
+ if (scrubberPosition.value === undefined) return withTiming(0, {
153
+ duration: 250
154
+ });
155
+ if (!xScale) return withTiming(1, {
156
+ duration: 250
157
+ });
158
+ const scrubX = getPointOnSerializableScale(scrubberPosition.value, xScale);
159
+ const rightEdge = drawingArea.x + drawingArea.width;
160
+ const target = rightEdge - scrubX >= FADE_ZONE ? 1 : 0;
161
+ return withTiming(target, {
162
+ duration: 250
163
+ });
164
+ }, [scrubberPosition, xScale, drawingArea]);
165
+ return /*#__PURE__*/_jsx(DefaultReferenceLineLabel, _extends({}, props, {
166
+ background: theme.color.bgSecondary,
167
+ borderRadius: 12.5,
168
+ color: theme.color.fg,
169
+ font: "label1",
170
+ inset: {
171
+ top: 4,
172
+ bottom: 4,
173
+ left: 8,
174
+ right: 8
175
+ },
176
+ opacity: opacity
177
+ }));
178
+ });
179
+ function StartPriceReferenceLine() {
180
+ const theme = useTheme();
181
+ const hourData = useMemo(() => sparklineInteractiveData.hour, []);
182
+ const startPrice = hourData[0].value;
183
+ const endPrice = hourData[hourData.length - 1].value;
184
+ const isPositive = endPrice >= startPrice;
185
+ const seriesColor = isPositive ? theme.color.fgPositive : theme.color.fgNegative;
186
+ return /*#__PURE__*/_jsxs(LineChart, {
187
+ enableScrubbing: true,
188
+ showArea: true,
189
+ areaType: "dotted",
190
+ height: 300,
191
+ inset: 0,
192
+ series: [{
193
+ id: 'hourly-prices',
194
+ data: hourData.map(d => d.value),
195
+ color: seriesColor
196
+ }],
197
+ xAxis: {
198
+ range: _ref2 => {
199
+ let {
200
+ min,
201
+ max
202
+ } = _ref2;
203
+ return {
204
+ min,
205
+ max: max - 24
206
+ };
207
+ }
208
+ },
209
+ children: [/*#__PURE__*/_jsx(Scrubber, {}), /*#__PURE__*/_jsx(ReferenceLine, {
210
+ LabelComponent: StartPriceLabel,
211
+ LineComponent: props => /*#__PURE__*/_jsx(DottedLine, _extends({}, props, {
212
+ dashIntervals: [0, 16],
213
+ strokeWidth: 3
214
+ })),
215
+ dataY: startPrice,
216
+ label: startPrice.toLocaleString('en-US', {
217
+ minimumFractionDigits: 2,
218
+ maximumFractionDigits: 2
219
+ }),
220
+ labelDx: -12,
221
+ labelHorizontalAlignment: "right",
222
+ stroke: theme.color.fgMuted
223
+ })]
224
+ });
225
+ }
132
226
  export default ReferenceLineStories;
@@ -5,7 +5,7 @@ import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
5
5
  import { Circle, Group, interpolateColors } from '@shopify/react-native-skia';
6
6
  import { useCartesianChartContext } from '../ChartProvider';
7
7
  import { projectPoint } from '../utils';
8
- import { buildTransition, defaultTransition } from '../utils/transition';
8
+ import { buildTransition, defaultAccessoryEnterTransition, defaultTransition, getTransition } from '../utils/transition';
9
9
  import { DefaultPointLabel } from './DefaultPointLabel';
10
10
 
11
11
  /**
@@ -27,7 +27,8 @@ export const Point = /*#__PURE__*/memo(_ref => {
27
27
  labelPosition = 'center',
28
28
  labelOffset,
29
29
  labelFont,
30
- transition = defaultTransition,
30
+ transitions,
31
+ transition,
31
32
  animate: animateProp
32
33
  } = _ref;
33
34
  const theme = useTheme();
@@ -43,6 +44,8 @@ export const Point = /*#__PURE__*/memo(_ref => {
43
44
  const xScale = getXScale();
44
45
  const yScale = getYScale(yAxisId);
45
46
  const shouldAnimate = animate != null ? animate : false;
47
+ const updateTransition = useMemo(() => getTransition((transitions == null ? void 0 : transitions.update) !== undefined ? transitions.update : transition, animate, defaultTransition), [animate, transitions == null ? void 0 : transitions.update, transition]);
48
+ const enterTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.enter, animate, defaultAccessoryEnterTransition), [animate, transitions == null ? void 0 : transitions.enter]);
46
49
 
47
50
  // Calculate pixel coordinates from data coordinates
48
51
  const pixelCoordinate = useMemo(() => {
@@ -62,9 +65,13 @@ export const Point = /*#__PURE__*/memo(_ref => {
62
65
  // Animated values for position
63
66
  const animatedX = useSharedValue(0);
64
67
  const animatedY = useSharedValue(0);
65
-
66
- // Animated value for color interpolation (0 = old color, 1 = new color)
68
+ const enterOpacity = useSharedValue(shouldAnimate ? 0 : 1);
67
69
  const colorProgress = useSharedValue(1);
70
+ const isReady = !!xScale && !!yScale;
71
+ useEffect(() => {
72
+ if (!shouldAnimate || !isReady) return;
73
+ enterOpacity.value = buildTransition(1, enterTransition);
74
+ }, [shouldAnimate, isReady, enterTransition, enterOpacity]);
68
75
 
69
76
  // Update position when coordinates change
70
77
  useEffect(() => {
@@ -72,26 +79,26 @@ export const Point = /*#__PURE__*/memo(_ref => {
72
79
  return;
73
80
  }
74
81
  if (shouldAnimate && previousPixelCoordinate) {
75
- animatedX.value = buildTransition(pixelCoordinate.x, transition);
76
- animatedY.value = buildTransition(pixelCoordinate.y, transition);
82
+ animatedX.value = buildTransition(pixelCoordinate.x, updateTransition);
83
+ animatedY.value = buildTransition(pixelCoordinate.y, updateTransition);
77
84
  } else {
78
85
  cancelAnimation(animatedX);
79
86
  cancelAnimation(animatedY);
80
87
  animatedX.value = pixelCoordinate.x;
81
88
  animatedY.value = pixelCoordinate.y;
82
89
  }
83
- }, [pixelCoordinate, shouldAnimate, previousPixelCoordinate, animatedX, animatedY, transition]);
90
+ }, [pixelCoordinate, shouldAnimate, previousPixelCoordinate, animatedX, animatedY, updateTransition]);
84
91
 
85
92
  // Update color when fill changes
86
93
  useEffect(() => {
87
94
  if (shouldAnimate && previousFill && previousFill !== fill) {
88
95
  colorProgress.value = 0;
89
- colorProgress.value = buildTransition(1, transition);
96
+ colorProgress.value = buildTransition(1, updateTransition);
90
97
  } else {
91
98
  cancelAnimation(colorProgress);
92
99
  colorProgress.value = 1;
93
100
  }
94
- }, [fill, shouldAnimate, previousFill, colorProgress, transition]);
101
+ }, [fill, shouldAnimate, previousFill, colorProgress, updateTransition]);
95
102
 
96
103
  // Create animated point for circles
97
104
  const animatedPoint = useDerivedValue(() => {
@@ -108,24 +115,19 @@ export const Point = /*#__PURE__*/memo(_ref => {
108
115
  }
109
116
  return interpolateColors(colorProgress.value, [0, 1], [previousFill, fill]);
110
117
  }, [colorProgress, previousFill, fill]);
111
-
112
- // Check if point is within drawing area
113
- const isWithinDrawingArea = useDerivedValue(() => {
114
- return animatedX.value >= drawingArea.x && animatedX.value <= drawingArea.x + drawingArea.width && animatedY.value >= drawingArea.y && animatedY.value <= drawingArea.y + drawingArea.height;
115
- }, [animatedX, animatedY, drawingArea]);
116
-
117
- // Compute effective opacity based on drawing area bounds
118
+ const isWithinDrawingArea = useMemo(() => {
119
+ if (!pixelCoordinate) return false;
120
+ return pixelCoordinate.x >= drawingArea.x && pixelCoordinate.x <= drawingArea.x + drawingArea.width && pixelCoordinate.y >= drawingArea.y && pixelCoordinate.y <= drawingArea.y + drawingArea.height;
121
+ }, [pixelCoordinate, drawingArea]);
118
122
  const effectiveOpacity = useDerivedValue(() => {
119
123
  const baseOpacity = opacity != null ? opacity : 1;
120
- return isWithinDrawingArea.value ? baseOpacity : 0;
121
- }, [isWithinDrawingArea, opacity]);
124
+ return isWithinDrawingArea ? baseOpacity * enterOpacity.value : 0;
125
+ }, [isWithinDrawingArea, opacity, enterOpacity]);
122
126
  const offset = useMemo(() => labelOffset != null ? labelOffset : radius * 2, [labelOffset, radius]);
123
127
  if (!pixelCoordinate) {
124
128
  return null;
125
129
  }
126
-
127
- // If animation is disabled or on first render, use static rendering
128
- if (!shouldAnimate || !previousPixelCoordinate) {
130
+ if (!shouldAnimate) {
129
131
  const isWithinBounds = pixelCoordinate.x >= drawingArea.x && pixelCoordinate.x <= drawingArea.x + drawingArea.width && pixelCoordinate.y >= drawingArea.y && pixelCoordinate.y <= drawingArea.y + drawingArea.height;
130
132
  const staticOpacity = isWithinBounds ? opacity != null ? opacity : 1 : 0;
131
133
  return /*#__PURE__*/_jsxs(_Fragment, {
@@ -159,20 +161,16 @@ export const Point = /*#__PURE__*/memo(_ref => {
159
161
  })]
160
162
  });
161
163
  }
162
-
163
- // Animated rendering
164
- return /*#__PURE__*/_jsxs(_Fragment, {
165
- children: [/*#__PURE__*/_jsxs(Group, {
166
- opacity: effectiveOpacity,
167
- children: [strokeWidth > 0 && /*#__PURE__*/_jsx(Circle, {
168
- c: animatedPoint,
169
- color: stroke,
170
- r: radius + strokeWidth / 2
171
- }), /*#__PURE__*/_jsx(Circle, {
172
- c: animatedPoint,
173
- color: animatedFillColor,
174
- r: radius - strokeWidth / 2
175
- })]
164
+ return /*#__PURE__*/_jsxs(Group, {
165
+ opacity: effectiveOpacity,
166
+ children: [strokeWidth > 0 && /*#__PURE__*/_jsx(Circle, {
167
+ c: animatedPoint,
168
+ color: stroke,
169
+ r: radius + strokeWidth / 2
170
+ }), /*#__PURE__*/_jsx(Circle, {
171
+ c: animatedPoint,
172
+ color: animatedFillColor,
173
+ r: radius - strokeWidth / 2
176
174
  }), label && /*#__PURE__*/_jsx(LabelComponent, {
177
175
  dataX: dataX,
178
176
  dataY: dataY,
@@ -5,7 +5,7 @@ import { Circle, Group } from '@shopify/react-native-skia';
5
5
  import { useCartesianChartContext } from '../ChartProvider';
6
6
  import { unwrapAnimatedValue } from '../utils';
7
7
  import { projectPointWithSerializableScale } from '../utils/point';
8
- import { buildTransition, defaultTransition } from '../utils/transition';
8
+ import { buildTransition, defaultTransition, getTransition } from '../utils/transition';
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  const defaultRadius = 5;
11
11
  const defaultStrokeWidth = 2;
@@ -48,10 +48,7 @@ export const DefaultScrubberBeacon = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((
48
48
  var _ref2;
49
49
  return (_ref2 = colorProp != null ? colorProp : targetSeries == null ? void 0 : targetSeries.color) != null ? _ref2 : theme.color.fgPrimary;
50
50
  }, [colorProp, targetSeries == null ? void 0 : targetSeries.color, theme.color.fgPrimary]);
51
- const updateTransition = useMemo(() => {
52
- var _transitions$update;
53
- return (_transitions$update = transitions == null ? void 0 : transitions.update) != null ? _transitions$update : defaultTransition;
54
- }, [transitions == null ? void 0 : transitions.update]);
51
+ const updateTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.update, animate, defaultTransition), [transitions == null ? void 0 : transitions.update, animate]);
55
52
  const pulseTransition = useMemo(() => {
56
53
  var _transitions$pulse;
57
54
  return (_transitions$pulse = transitions == null ? void 0 : transitions.pulse) != null ? _transitions$pulse : defaultPulseTransition;
@@ -1,10 +1,11 @@
1
1
  import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react';
2
- import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue, withDelay, withTiming } from 'react-native-reanimated';
2
+ import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated';
3
3
  import { useTheme } from '@coinbase/cds-mobile';
4
4
  import { Group, Rect } from '@shopify/react-native-skia';
5
5
  import { useCartesianChartContext } from '../ChartProvider';
6
6
  import { ReferenceLine } from '../line';
7
- import { accessoryFadeTransitionDelay, accessoryFadeTransitionDuration, getPointOnSerializableScale, useScrubberContext } from '../utils';
7
+ import { defaultAccessoryEnterTransition, getPointOnSerializableScale, getTransition, useScrubberContext } from '../utils';
8
+ import { buildTransition } from '../utils/transition';
8
9
  import { DefaultScrubberBeacon } from './DefaultScrubberBeacon';
9
10
  import { DefaultScrubberLabel } from './DefaultScrubberLabel';
10
11
  import { ScrubberBeaconGroup } from './ScrubberBeaconGroup';
@@ -35,6 +36,7 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
35
36
  beaconLabelFont,
36
37
  idlePulse,
37
38
  beaconTransitions,
39
+ transitions = beaconTransitions,
38
40
  beaconStroke
39
41
  } = _ref;
40
42
  const theme = useTheme();
@@ -124,13 +126,12 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
124
126
  }))) != null ? _series$filter$filter : [];
125
127
  }, [series, filteredSeriesIds]);
126
128
  const isReady = !!xScale;
129
+ const groupEnterTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.enter, animate, defaultAccessoryEnterTransition), [transitions == null ? void 0 : transitions.enter, animate]);
127
130
  useEffect(() => {
128
131
  if (animate && isReady) {
129
- scrubberOpacity.value = withDelay(accessoryFadeTransitionDelay, withTiming(1, {
130
- duration: accessoryFadeTransitionDuration
131
- }));
132
+ scrubberOpacity.value = buildTransition(1, groupEnterTransition);
132
133
  }
133
- }, [animate, isReady, scrubberOpacity]);
134
+ }, [animate, isReady, scrubberOpacity, groupEnterTransition]);
134
135
  if (!isReady) return;
135
136
  return /*#__PURE__*/_jsxs(Group, {
136
137
  opacity: scrubberOpacity,
@@ -157,14 +158,15 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
157
158
  idlePulse: idlePulse,
158
159
  seriesIds: filteredSeriesIds,
159
160
  stroke: beaconStroke,
160
- transitions: beaconTransitions
161
+ transitions: transitions
161
162
  }), !hideBeaconLabels && beaconLabels.length > 0 && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
162
163
  BeaconLabelComponent: BeaconLabelComponent,
163
164
  labelFont: beaconLabelFont,
164
165
  labelHorizontalOffset: beaconLabelHorizontalOffset,
165
166
  labelMinGap: beaconLabelMinGap,
166
167
  labelPreferredSide: beaconLabelPreferredSide,
167
- labels: beaconLabels
168
+ labels: beaconLabels,
169
+ transitions: transitions
168
170
  })]
169
171
  });
170
172
  }));