@coinbase/cds-mobile-visualization 3.4.0-beta.1 → 3.4.0-beta.11

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 (187) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dts/chart/CartesianChart.d.ts +57 -33
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/ChartContextBridge.d.ts +28 -0
  5. package/dts/chart/ChartContextBridge.d.ts.map +1 -0
  6. package/dts/chart/Path.d.ts +77 -34
  7. package/dts/chart/Path.d.ts.map +1 -1
  8. package/dts/chart/PeriodSelector.d.ts +2 -2
  9. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  10. package/dts/chart/area/Area.d.ts +42 -27
  11. package/dts/chart/area/Area.d.ts.map +1 -1
  12. package/dts/chart/area/AreaChart.d.ts +51 -10
  13. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  14. package/dts/chart/area/DottedArea.d.ts +21 -2
  15. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  16. package/dts/chart/area/GradientArea.d.ts +19 -13
  17. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  18. package/dts/chart/area/SolidArea.d.ts +17 -2
  19. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  20. package/dts/chart/axis/Axis.d.ts +86 -118
  21. package/dts/chart/axis/Axis.d.ts.map +1 -1
  22. package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
  23. package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
  24. package/dts/chart/axis/XAxis.d.ts +1 -1
  25. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  26. package/dts/chart/axis/YAxis.d.ts +2 -2
  27. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  28. package/dts/chart/axis/index.d.ts +1 -0
  29. package/dts/chart/axis/index.d.ts.map +1 -1
  30. package/dts/chart/bar/Bar.d.ts +16 -13
  31. package/dts/chart/bar/Bar.d.ts.map +1 -1
  32. package/dts/chart/bar/BarChart.d.ts +36 -20
  33. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  34. package/dts/chart/bar/BarPlot.d.ts +2 -1
  35. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  36. package/dts/chart/bar/BarStack.d.ts +39 -48
  37. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  38. package/dts/chart/bar/BarStackGroup.d.ts +1 -0
  39. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  40. package/dts/chart/bar/DefaultBar.d.ts +1 -1
  41. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  42. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  43. package/dts/chart/gradient/Gradient.d.ts +25 -0
  44. package/dts/chart/gradient/Gradient.d.ts.map +1 -0
  45. package/dts/chart/gradient/index.d.ts +2 -0
  46. package/dts/chart/gradient/index.d.ts.map +1 -0
  47. package/dts/chart/index.d.ts +3 -1
  48. package/dts/chart/index.d.ts.map +1 -1
  49. package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
  50. package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
  51. package/dts/chart/line/DottedLine.d.ts +13 -5
  52. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  53. package/dts/chart/line/Line.d.ts +64 -25
  54. package/dts/chart/line/Line.d.ts.map +1 -1
  55. package/dts/chart/line/LineChart.d.ts +43 -9
  56. package/dts/chart/line/LineChart.d.ts.map +1 -1
  57. package/dts/chart/line/ReferenceLine.d.ts +68 -20
  58. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  59. package/dts/chart/line/SolidLine.d.ts +8 -5
  60. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  61. package/dts/chart/line/index.d.ts +1 -1
  62. package/dts/chart/line/index.d.ts.map +1 -1
  63. package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
  64. package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
  65. package/dts/chart/point/Point.d.ts +120 -0
  66. package/dts/chart/point/Point.d.ts.map +1 -0
  67. package/dts/chart/point/index.d.ts +3 -0
  68. package/dts/chart/point/index.d.ts.map +1 -0
  69. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +8 -0
  70. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
  71. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
  72. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
  73. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
  74. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
  75. package/dts/chart/scrubber/Scrubber.d.ts +172 -43
  76. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  77. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +44 -0
  78. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
  79. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +31 -0
  80. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
  81. package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
  82. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  83. package/dts/chart/scrubber/index.d.ts +3 -0
  84. package/dts/chart/scrubber/index.d.ts.map +1 -1
  85. package/dts/chart/text/ChartText.d.ts +151 -77
  86. package/dts/chart/text/ChartText.d.ts.map +1 -1
  87. package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
  88. package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
  89. package/dts/chart/text/index.d.ts +1 -1
  90. package/dts/chart/text/index.d.ts.map +1 -1
  91. package/dts/chart/utils/axis.d.ts +25 -1
  92. package/dts/chart/utils/axis.d.ts.map +1 -1
  93. package/dts/chart/utils/chart.d.ts +34 -7
  94. package/dts/chart/utils/chart.d.ts.map +1 -1
  95. package/dts/chart/utils/context.d.ts +28 -7
  96. package/dts/chart/utils/context.d.ts.map +1 -1
  97. package/dts/chart/utils/gradient.d.ts +117 -0
  98. package/dts/chart/utils/gradient.d.ts.map +1 -0
  99. package/dts/chart/utils/index.d.ts +3 -0
  100. package/dts/chart/utils/index.d.ts.map +1 -1
  101. package/dts/chart/utils/path.d.ts +53 -0
  102. package/dts/chart/utils/path.d.ts.map +1 -1
  103. package/dts/chart/utils/point.d.ts +71 -7
  104. package/dts/chart/utils/point.d.ts.map +1 -1
  105. package/dts/chart/utils/scale.d.ts +102 -0
  106. package/dts/chart/utils/scale.d.ts.map +1 -1
  107. package/dts/chart/utils/scrubber.d.ts +39 -0
  108. package/dts/chart/utils/scrubber.d.ts.map +1 -0
  109. package/dts/chart/utils/transition.d.ts +140 -0
  110. package/dts/chart/utils/transition.d.ts.map +1 -0
  111. package/esm/chart/CartesianChart.js +164 -70
  112. package/esm/chart/ChartContextBridge.js +148 -0
  113. package/esm/chart/Path.js +198 -113
  114. package/esm/chart/PeriodSelector.js +2 -2
  115. package/esm/chart/__stories__/CartesianChart.stories.js +378 -131
  116. package/esm/chart/__stories__/Chart.stories.js +2 -4
  117. package/esm/chart/__stories__/PeriodSelector.stories.js +103 -75
  118. package/esm/chart/area/Area.js +25 -35
  119. package/esm/chart/area/AreaChart.js +17 -12
  120. package/esm/chart/area/DottedArea.js +61 -109
  121. package/esm/chart/area/GradientArea.js +35 -91
  122. package/esm/chart/area/SolidArea.js +22 -8
  123. package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
  124. package/esm/chart/axis/Axis.js +5 -39
  125. package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
  126. package/esm/chart/axis/XAxis.js +148 -66
  127. package/esm/chart/axis/YAxis.js +149 -65
  128. package/esm/chart/axis/__stories__/Axis.stories.js +259 -1
  129. package/esm/chart/axis/index.js +1 -0
  130. package/esm/chart/bar/Bar.js +3 -1
  131. package/esm/chart/bar/BarChart.js +15 -37
  132. package/esm/chart/bar/BarPlot.js +41 -35
  133. package/esm/chart/bar/BarStack.js +75 -38
  134. package/esm/chart/bar/BarStackGroup.js +6 -16
  135. package/esm/chart/bar/DefaultBar.js +26 -48
  136. package/esm/chart/bar/DefaultBarStack.js +23 -58
  137. package/esm/chart/bar/__stories__/BarChart.stories.js +502 -77
  138. package/esm/chart/gradient/Gradient.js +53 -0
  139. package/esm/chart/gradient/index.js +1 -0
  140. package/esm/chart/index.js +3 -1
  141. package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
  142. package/esm/chart/line/DottedLine.js +29 -14
  143. package/esm/chart/line/Line.js +106 -67
  144. package/esm/chart/line/LineChart.js +20 -14
  145. package/esm/chart/line/ReferenceLine.js +80 -63
  146. package/esm/chart/line/SolidLine.js +25 -10
  147. package/esm/chart/line/__stories__/LineChart.stories.js +2101 -1977
  148. package/esm/chart/line/__stories__/ReferenceLine.stories.js +83 -28
  149. package/esm/chart/line/index.js +1 -1
  150. package/esm/chart/point/DefaultPointLabel.js +39 -0
  151. package/esm/chart/point/Point.js +188 -0
  152. package/esm/chart/point/index.js +2 -0
  153. package/esm/chart/scrubber/DefaultScrubberBeacon.js +179 -0
  154. package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
  155. package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
  156. package/esm/chart/scrubber/Scrubber.js +126 -146
  157. package/esm/chart/scrubber/ScrubberBeaconGroup.js +161 -0
  158. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +185 -0
  159. package/esm/chart/scrubber/ScrubberProvider.js +46 -54
  160. package/esm/chart/scrubber/index.js +3 -1
  161. package/esm/chart/text/ChartText.js +242 -174
  162. package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
  163. package/esm/chart/text/index.js +1 -1
  164. package/esm/chart/utils/axis.js +45 -29
  165. package/esm/chart/utils/chart.js +44 -3
  166. package/esm/chart/utils/gradient.js +305 -0
  167. package/esm/chart/utils/index.js +3 -0
  168. package/esm/chart/utils/path.js +76 -8
  169. package/esm/chart/utils/point.js +171 -17
  170. package/esm/chart/utils/scale.js +242 -2
  171. package/esm/chart/utils/scrubber.js +139 -0
  172. package/esm/chart/utils/transition.js +185 -0
  173. package/esm/sparkline/__stories__/Sparkline.stories.js +11 -7
  174. package/esm/sparkline/__stories__/SparklineGradient.stories.js +7 -4
  175. package/esm/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories.js +51 -26
  176. package/esm/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories.js +17 -9
  177. package/package.json +15 -9
  178. package/dts/chart/Point.d.ts +0 -103
  179. package/dts/chart/Point.d.ts.map +0 -1
  180. package/dts/chart/line/GradientLine.d.ts +0 -45
  181. package/dts/chart/line/GradientLine.d.ts.map +0 -1
  182. package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
  183. package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
  184. package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
  185. package/esm/chart/Point.js +0 -111
  186. package/esm/chart/line/GradientLine.js +0 -62
  187. package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
@@ -2,7 +2,7 @@ const _excluded = ["onDimensionsChange"];
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
4
  import { memo, useEffect, useMemo, useState } from 'react';
5
- import { G } from 'react-native-svg';
5
+ import { Group } from '@shopify/react-native-skia';
6
6
  import { ChartText } from './ChartText';
7
7
 
8
8
  /**
@@ -32,12 +32,13 @@ const EPSILON_PX = 0.5;
32
32
  *
33
33
  * The component focuses solely on overlap prevention logic for better separation of concerns.
34
34
  */
35
- export const SmartChartTextGroup = /*#__PURE__*/memo(_ref => {
35
+ export const ChartTextGroup = /*#__PURE__*/memo(_ref => {
36
36
  let {
37
37
  labels,
38
38
  minGap = 8,
39
39
  prioritizeEndLabels = true,
40
- chartTextProps
40
+ chartTextProps,
41
+ LabelComponent = ChartText
41
42
  } = _ref;
42
43
  const [boundingBoxes, setBoundingBoxes] = useState(new Map());
43
44
  const _ref2 = chartTextProps != null ? chartTextProps : {},
@@ -193,11 +194,11 @@ export const SmartChartTextGroup = /*#__PURE__*/memo(_ref => {
193
194
  }
194
195
  return new Set(greedy);
195
196
  }, [isReady, boundingBoxes, minGap, prioritizeEndLabels, labelsWithKeys]);
196
- return /*#__PURE__*/_jsx(G, {
197
+ return /*#__PURE__*/_jsx(Group, {
197
198
  children: labelsWithKeys.map(labelData => {
198
199
  const hasMeasurement = boundingBoxes.has(labelData.key);
199
200
  const isVisible = hasMeasurement && isReady && (visibleKeySet == null ? void 0 : visibleKeySet.has(labelData.key));
200
- return /*#__PURE__*/_jsx(ChartText, _extends({
201
+ return /*#__PURE__*/_jsx(LabelComponent, _extends({
201
202
  opacity: isVisible ? 1 : 0,
202
203
  x: labelData.x,
203
204
  y: labelData.y
@@ -1,4 +1,4 @@
1
1
  // codegen:start {preset: barrel, include: ./*.tsx, exclude: ./__stories__/*.tsx}
2
2
  export * from './ChartText';
3
- export * from './SmartChartTextGroup';
3
+ export * from './ChartTextGroup';
4
4
  // codegen:end
@@ -3,10 +3,41 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
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
4
  import { useCallback, useMemo, useState } from 'react';
5
5
  import { getChartDomain, getChartRange, isValidBounds } from './chart';
6
+ import { getPointOnScale } from './point';
6
7
  import { getCategoricalScale, getNumericScale, isCategoricalScale, isNumericScale } from './scale';
7
8
  export const defaultAxisId = 'DEFAULT_AXIS_ID';
8
9
  export const defaultAxisScaleType = 'linear';
9
10
 
11
+ /**
12
+ * Position options for band scale axis elements (grid lines, tick marks, labels).
13
+ *
14
+ * - `'start'` - At the start of each step (before bar padding)
15
+ * - `'middle'` - At the center of each band
16
+ * - `'end'` - At the end of each step (after bar padding)
17
+ * - `'edges'` - At start of each tick, plus end for the last tick (encloses the chart)
18
+ *
19
+ * @note These properties only apply when using a band scale (`scaleType: 'band'`).
20
+ */
21
+
22
+ /**
23
+ * Converts an AxisBandPlacement to the corresponding PointAnchor for use with getPointOnScale.
24
+ *
25
+ * @param placement - The axis placement value
26
+ * @returns The corresponding PointAnchor for scale calculations
27
+ */
28
+ export const toPointAnchor = placement => {
29
+ switch (placement) {
30
+ case 'edges': // edges uses stepStart for each tick, stepEnd handled separately
31
+ case 'start':
32
+ return 'stepStart';
33
+ case 'end':
34
+ return 'stepEnd';
35
+ case 'middle':
36
+ default:
37
+ return 'middle';
38
+ }
39
+ };
40
+
10
41
  /**
11
42
  * Axis configuration with computed bounds
12
43
  */
@@ -457,6 +488,7 @@ const generateEvenlyDistributedTicks = (scale, tickInterval, possibleTickValues,
457
488
  * // Returns tick positions centered in each selected band
458
489
  */
459
490
  export const getAxisTicksData = _ref4 => {
491
+ var _options$anchor;
460
492
  let {
461
493
  ticks,
462
494
  scaleFunction,
@@ -466,53 +498,37 @@ export const getAxisTicksData = _ref4 => {
466
498
  tickInterval,
467
499
  options
468
500
  } = _ref4;
501
+ const anchor = (_options$anchor = options == null ? void 0 : options.anchor) != null ? _options$anchor : 'middle';
502
+
469
503
  // Handle band scales
470
504
  if (isCategoricalScale(scaleFunction)) {
505
+ const bandScale = scaleFunction;
506
+
471
507
  // If explicit ticks are provided as array, use them
472
508
  if (Array.isArray(ticks)) {
473
- return ticks.filter(index => index >= 0 && index < categories.length).map(index => {
474
- var _bandwidth;
475
- // Band scales expect numeric indices, not category strings
476
- const position = scaleFunction(index);
477
- if (position === undefined) return null;
478
- return {
479
- tick: index,
480
- position: position + ((_bandwidth = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth : 0) / 2
481
- };
482
- }).filter(Boolean);
509
+ return ticks.filter(index => index >= 0 && index < categories.length).map(index => ({
510
+ tick: index,
511
+ position: getPointOnScale(index, bandScale, anchor)
512
+ }));
483
513
  }
484
514
 
485
515
  // If a tick function is provided, use it to filter
486
516
  if (typeof ticks === 'function') {
487
517
  return categories.map((category, index) => {
488
- var _bandwidth2;
489
518
  if (!ticks(index)) return null;
490
-
491
- // Band scales expect numeric indices, not category strings
492
- const position = scaleFunction(index);
493
- if (position === undefined) return null;
494
519
  return {
495
520
  tick: index,
496
- position: position + ((_bandwidth2 = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth2 : 0) / 2
521
+ position: getPointOnScale(index, bandScale, anchor)
497
522
  };
498
523
  }).filter(Boolean);
499
524
  }
500
- if (typeof ticks === 'boolean' && !ticks) {
501
- return [];
502
- }
503
525
 
504
526
  // For band scales without explicit ticks, show all categories
505
527
  // requestedTickCount is ignored for categorical scales - use ticks parameter to control visibility
506
- return categories.map((category, index) => {
507
- var _bandwidth3;
508
- // Band scales expect numeric indices, not category strings
509
- const position = scaleFunction(index);
510
- if (position === undefined) return null;
511
- return {
512
- tick: index,
513
- position: position + ((_bandwidth3 = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth3 : 0) / 2
514
- };
515
- }).filter(Boolean);
528
+ return categories.map((_, index) => ({
529
+ tick: index,
530
+ position: getPointOnScale(index, bandScale, anchor)
531
+ }));
516
532
  }
517
533
 
518
534
  // Handle numeric scales
@@ -1,3 +1,4 @@
1
+ import { isSharedValue } from 'react-native-reanimated';
1
2
  import { stack as d3Stack, stackOffsetDiverging, stackOrderNone } from 'd3-shape';
2
3
  export const defaultStackId = 'DEFAULT_STACK_ID';
3
4
  /**
@@ -19,13 +20,13 @@ export const getChartDomain = (series, min, max) => {
19
20
  return domain;
20
21
  }
21
22
  if (series.length > 0) {
22
- const maxDataLength = Math.max(...series.map(s => {
23
+ const dataLength = Math.max(...series.map(s => {
23
24
  var _s$data;
24
25
  return ((_s$data = s.data) == null ? void 0 : _s$data.length) || 0;
25
26
  }));
26
- if (maxDataLength > 0) {
27
+ if (dataLength > 0) {
27
28
  if (domain.min === undefined) domain.min = 0;
28
- if (domain.max === undefined) domain.max = maxDataLength - 1;
29
+ if (domain.max === undefined) domain.max = dataLength - 1;
29
30
  }
30
31
  }
31
32
  return domain;
@@ -111,6 +112,32 @@ export const getStackedSeriesData = series => {
111
112
  return stackedDataMap;
112
113
  };
113
114
 
115
+ /**
116
+ * Extracts line data values from series data that may contain tuples.
117
+ * For tuple data [[baseline, value]], extracts the last value.
118
+ * For numeric data [value], returns as-is.
119
+ *
120
+ * @param data - Array of numbers, tuples, or null values
121
+ * @returns Array of numbers or null values
122
+ */
123
+ export const getLineData = data => {
124
+ if (!data) return [];
125
+
126
+ // Check if this is tuple data by finding first non-null entry
127
+ const firstNonNull = data.find(d => d !== null);
128
+ if (Array.isArray(firstNonNull)) {
129
+ return data.map(d => {
130
+ var _d$at;
131
+ if (d === null) return null;
132
+ if (Array.isArray(d)) return (_d$at = d.at(-1)) != null ? _d$at : null;
133
+ return d;
134
+ });
135
+ }
136
+
137
+ // Already numeric data
138
+ return data;
139
+ };
140
+
114
141
  /**
115
142
  * Calculates the range of a chart from series data.
116
143
  * Range represents the range of y-values from the data.
@@ -226,4 +253,18 @@ export const getChartInset = (inset, defaults) => {
226
253
  bottom: (_inset$bottom = inset == null ? void 0 : inset.bottom) != null ? _inset$bottom : baseDefaults.bottom,
227
254
  right: (_inset$right = inset == null ? void 0 : inset.right) != null ? _inset$right : baseDefaults.right
228
255
  };
256
+ };
257
+
258
+ /**
259
+ * Unwraps an optionally animated value to get the raw value.
260
+ * @param value - The value to unwrap.
261
+ * @returns The raw value.
262
+ */
263
+ export const unwrapAnimatedValue = value => {
264
+ 'worklet';
265
+
266
+ if (isSharedValue(value)) {
267
+ return value.value;
268
+ }
269
+ return value;
229
270
  };
@@ -0,0 +1,305 @@
1
+ import { Skia } from '@shopify/react-native-skia';
2
+ import { applySerializableScale, isCategoricalScale, isSerializableScale } from './scale';
3
+
4
+ /**
5
+ * Defines a color transition point in the gradient
6
+ */
7
+
8
+ /**
9
+ * Defines a gradient.
10
+ */
11
+
12
+ /**
13
+ * Resolves gradient stops, handling both static arrays and function forms.
14
+ * When stops is a function, calls it with the domain bounds.
15
+ */
16
+ export const getGradientStops = (stops, domain) => {
17
+ if (typeof stops === 'function') {
18
+ return stops(domain);
19
+ }
20
+ return stops;
21
+ };
22
+
23
+ /**
24
+ * Processes Gradient to gradient configuration for SVG linearGradient.
25
+ * Colors are smoothly interpolated between stops by the browser.
26
+ * Multiple stops at the same offset create hard color transitions.
27
+ */
28
+ const processGradientStops = (stops, scale) => {
29
+ if (stops.length === 0) {
30
+ console.warn('Gradient has no stops - falling back to default');
31
+ return;
32
+ }
33
+
34
+ // Check if stops are in ascending order
35
+ const isOutOfOrder = stops.some((stop, i) => {
36
+ return i > 0 && stop.offset < stops[i - 1].offset;
37
+ });
38
+ if (isOutOfOrder) {
39
+ console.warn("Gradient: stop offsets must be in ascending order");
40
+ return;
41
+ }
42
+ const [rangeMin, rangeMax] = scale.range();
43
+ const rangeSpan = Math.abs(rangeMax - rangeMin);
44
+
45
+ // Convert data value offsets to normalized positions (0-1) using scale
46
+ const normalizedStops = stops.map(stop => {
47
+ var _stop$opacity;
48
+ const stopPosition = scale(stop.offset);
49
+ const normalized = stopPosition === undefined ? 0 : Math.max(0, Math.min(1, Math.abs(stopPosition - rangeMin) / rangeSpan));
50
+ return {
51
+ offset: normalized,
52
+ // Now 0-1 normalized (not data space)
53
+ color: stop.color,
54
+ opacity: (_stop$opacity = stop.opacity) != null ? _stop$opacity : 1
55
+ };
56
+ }).sort((a, b) => a.offset - b.offset);
57
+ return normalizedStops;
58
+ };
59
+
60
+ /**
61
+ * Interpolates between two colors using linear interpolation.
62
+ * Returns an rgba string.
63
+ */
64
+ const interpolateColor = (color1, color2, t) => {
65
+ 'worklet';
66
+
67
+ const c1 = Skia.Color(color1);
68
+ const c2 = Skia.Color(color2);
69
+ const r = Math.round((c1[0] + (c2[0] - c1[0]) * t) * 255);
70
+ const g = Math.round((c1[1] + (c2[1] - c1[1]) * t) * 255);
71
+ const b = Math.round((c1[2] + (c2[2] - c1[2]) * t) * 255);
72
+ const a = c1[3] + (c2[3] - c1[3]) * t;
73
+ return "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
74
+ };
75
+
76
+ /**
77
+ * Adds an opacity to a color
78
+ * Returns an rgba string.
79
+ */
80
+ export const getColorWithOpacity = (color1, opacity) => {
81
+ const c = Skia.Color(color1);
82
+ return "rgba(" + c[0] * 255 + ", " + c[1] * 255 + ", " + c[2] * 255 + ", " + opacity + ")";
83
+ };
84
+
85
+ /**
86
+ * Creates a gradient configuration for SVG components.
87
+ * Processes a GradientDefinition into a renderable GradientConfig.
88
+ * Supports both numeric scales (linear, log) and categorical scales (band).
89
+ *
90
+ * @param gradient - GradientDefinition configuration (required)
91
+ * @param xScale - X-axis scale (required)
92
+ * @param yScale - Y-axis scale (required)
93
+ * @returns GradientConfig or null if gradient processing fails
94
+ *
95
+ * @example
96
+ * const gradientConfig = useMemo(() => {
97
+ * if (!gradient || !xScale || !yScale) return;
98
+ * return getGradientConfig(gradient, xScale, yScale);
99
+ * }, [gradient, xScale, yScale]);
100
+ *
101
+ * if (gradientConfig) {
102
+ * return (
103
+ * <defs>
104
+ * <Gradient
105
+ * config={gradientConfig}
106
+ * direction={gradient.axis === 'x' ? 'horizontal' : 'vertical'}
107
+ * id={gradientId}
108
+ * />
109
+ * </defs>
110
+ * );
111
+ * }
112
+ */
113
+ export const getGradientConfig = (gradient, xScale, yScale) => {
114
+ if (!gradient) return;
115
+
116
+ // Get the scale based on axis
117
+ const scale = gradient.axis === 'x' ? xScale : yScale;
118
+ if (!scale) return;
119
+
120
+ // Extract domain from scale
121
+ const scaleDomain = scale.domain();
122
+ let domain;
123
+ if (isCategoricalScale(scale)) {
124
+ const domainArray = scaleDomain;
125
+ domain = {
126
+ min: domainArray[0],
127
+ max: domainArray[domainArray.length - 1]
128
+ };
129
+ } else {
130
+ const [min, max] = scaleDomain;
131
+ domain = {
132
+ min,
133
+ max
134
+ };
135
+ }
136
+ const resolvedStops = getGradientStops(gradient.stops, domain);
137
+ return processGradientStops(resolvedStops, scale);
138
+ };
139
+
140
+ /**
141
+ * Evaluates the color at a specific data value based on the gradient stops, ignoring opacity.
142
+ * @param stops - The gradient stops configuration
143
+ * @param dataValue - The data value to evaluate (for band scales, this is the index)
144
+ * @param scale - The scale to use for value mapping (handles log scales correctly)
145
+ * @returns The color string at this data value, or undefined if invalid
146
+ */
147
+ export const evaluateGradientAtValue = (stops, dataValue, scale) => {
148
+ 'worklet';
149
+
150
+ if (stops.length === 0) return;
151
+
152
+ // Determine range based on scale type
153
+ let rangeMin;
154
+ let rangeMax;
155
+ if (isSerializableScale(scale)) {
156
+ // SerializableScale has range as [number, number]
157
+ [rangeMin, rangeMax] = scale.range;
158
+ } else {
159
+ // ChartScaleFunction has range() method
160
+ const scaleRange = scale.range();
161
+ [rangeMin, rangeMax] = Array.isArray(scaleRange) ? scaleRange : [scaleRange, scaleRange]; // fallback for band scales
162
+ }
163
+ const rangeSpan = Math.abs(rangeMax - rangeMin);
164
+ if (rangeSpan === 0) return stops[0].color;
165
+
166
+ // Map dataValue through scale to get position
167
+ let dataPosition;
168
+ if (isSerializableScale(scale)) {
169
+ dataPosition = applySerializableScale(dataValue, scale);
170
+ } else {
171
+ const result = scale(dataValue);
172
+ if (result === undefined) return stops[0].color;
173
+ dataPosition = result;
174
+ }
175
+
176
+ // Normalize to 0-1 based on range
177
+ const normalizedValue = Math.max(0, Math.min(1, Math.abs(dataPosition - rangeMin) / rangeSpan));
178
+
179
+ // Map stop offsets through scale and normalize to 0-1
180
+ const positions = stops.map(stop => {
181
+ let stopPosition;
182
+ if (isSerializableScale(scale)) {
183
+ stopPosition = applySerializableScale(stop.offset, scale);
184
+ } else {
185
+ const result = scale(stop.offset);
186
+ if (result === undefined) return 0;
187
+ stopPosition = result;
188
+ }
189
+ return Math.max(0, Math.min(1, Math.abs(stopPosition - rangeMin) / rangeSpan));
190
+ });
191
+
192
+ // Find which segment we're in
193
+ if (normalizedValue < positions[0]) {
194
+ return stops[0].color;
195
+ }
196
+ if (normalizedValue >= positions[positions.length - 1]) {
197
+ return stops[stops.length - 1].color;
198
+ }
199
+
200
+ // Check if dataValue matches any stop offset exactly (for hard transitions)
201
+ for (let i = 0; i < stops.length; i++) {
202
+ if (dataValue === stops[i].offset) {
203
+ // Found exact match - check if there are multiple stops at this offset (hard transition)
204
+ // Use the LAST color at this offset for hard transitions
205
+ let lastIndexAtOffset = i;
206
+ while (lastIndexAtOffset + 1 < stops.length && stops[lastIndexAtOffset + 1].offset === stops[i].offset) {
207
+ lastIndexAtOffset++;
208
+ }
209
+ return stops[lastIndexAtOffset].color;
210
+ }
211
+ }
212
+
213
+ // Find the segment to interpolate between
214
+ for (let i = 0; i < positions.length - 1; i++) {
215
+ if (normalizedValue >= positions[i] && normalizedValue <= positions[i + 1]) {
216
+ const segmentStart = positions[i];
217
+ const segmentEnd = positions[i + 1];
218
+ if (segmentEnd === segmentStart) {
219
+ return stops[i].color;
220
+ }
221
+ const t = (normalizedValue - segmentStart) / (segmentEnd - segmentStart);
222
+ return interpolateColor(stops[i].color, stops[i + 1].color, t);
223
+ }
224
+ }
225
+ return stops[0].color;
226
+ };
227
+
228
+ /**
229
+ * Determines the baseline value for the gradient area by finding the value
230
+ * within the axis bounds that is closest to the target baseline.
231
+ *
232
+ * @param axisBounds - The min and max bounds of the axis
233
+ * @param baseline - The target baseline value (defaults to 0)
234
+ * @returns The value within bounds closest to the baseline
235
+ */
236
+ export const getBaseline = function (axisBounds, baseline) {
237
+ if (baseline === void 0) {
238
+ baseline = 0;
239
+ }
240
+ const {
241
+ min,
242
+ max
243
+ } = axisBounds;
244
+
245
+ // Normalize to ensure lowerBound <= upperBound
246
+ const lowerBound = Math.min(min, max);
247
+ const upperBound = Math.max(min, max);
248
+
249
+ // If baseline is within the range, use it
250
+ if (lowerBound <= baseline && baseline <= upperBound) return baseline;
251
+
252
+ // Otherwise, return the bound closest to baseline
253
+ return Math.abs(lowerBound - baseline) < Math.abs(upperBound - baseline) ? lowerBound : upperBound;
254
+ };
255
+
256
+ /**
257
+ * Generates a gradient definition for the area chart based on the axis bounds
258
+ * and styling parameters. Ensures gradient stops are in ascending order.
259
+ *
260
+ * @param axisBounds - The min and max bounds of the axis
261
+ * @param baselineValue - The baseline value for the gradient
262
+ * @param fill - The color to use for the gradient
263
+ * @param peakOpacity - Opacity at the peak of the gradient
264
+ * @param baselineOpacity - Opacity at the baseline
265
+ * @returns A gradient definition with y-axis stops in ascending order
266
+ */
267
+ export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity) => {
268
+ const {
269
+ min,
270
+ max
271
+ } = axisBounds;
272
+ const lowerBound = Math.min(min, max);
273
+ const upperBound = Math.max(min, max);
274
+ if (lowerBound < baselineValue && baselineValue < upperBound) {
275
+ return {
276
+ axis: 'y',
277
+ stops: [{
278
+ offset: lowerBound,
279
+ color: fill,
280
+ opacity: peakOpacity
281
+ }, {
282
+ offset: baselineValue,
283
+ color: fill,
284
+ opacity: baselineOpacity
285
+ }, {
286
+ offset: upperBound,
287
+ color: fill,
288
+ opacity: peakOpacity
289
+ }]
290
+ };
291
+ }
292
+ const peakValue = Math.abs(min - baselineValue) > Math.abs(max - baselineValue) ? min : max;
293
+ return {
294
+ axis: 'y',
295
+ stops: [{
296
+ offset: peakValue,
297
+ color: fill,
298
+ opacity: peakOpacity
299
+ }, {
300
+ offset: baselineValue,
301
+ color: fill,
302
+ opacity: baselineOpacity
303
+ }].sort((a, b) => a.offset - b.offset)
304
+ };
305
+ };
@@ -3,7 +3,10 @@ export * from './axis';
3
3
  export * from './bar';
4
4
  export * from './chart';
5
5
  export * from './context';
6
+ export * from './gradient';
6
7
  export * from './path';
7
8
  export * from './point';
8
9
  export * from './scale';
10
+ export * from './scrubber';
11
+ export * from './transition';
9
12
  // codegen:end
@@ -49,10 +49,11 @@ export const getLinePath = _ref => {
49
49
  var _pathGenerator;
50
50
  let {
51
51
  data,
52
- curve = 'linear',
52
+ curve = 'bump',
53
53
  xScale,
54
54
  yScale,
55
- xData
55
+ xData,
56
+ connectNulls = false
56
57
  } = _ref;
57
58
  if (data.length === 0) {
58
59
  return '';
@@ -64,9 +65,12 @@ export const getLinePath = _ref => {
64
65
  yScale,
65
66
  xData
66
67
  });
67
- const pathGenerator = d3Line().x(d => d.x).y(d => d.y).curve(curveFunction).defined(d => d !== null); // Only draw lines where point is not null
68
68
 
69
- return (_pathGenerator = pathGenerator(dataPoints)) != null ? _pathGenerator : '';
69
+ // When connectNulls is true, filter out null values before rendering
70
+ // When false, use defined() to create gaps in the line
71
+ const filteredPoints = connectNulls ? dataPoints.filter(d => d !== null) : dataPoints;
72
+ const pathGenerator = d3Line().x(d => d.x).y(d => d.y).curve(curveFunction).defined(d => connectNulls || d !== null);
73
+ return (_pathGenerator = pathGenerator(filteredPoints)) != null ? _pathGenerator : '';
70
74
  };
71
75
 
72
76
  /**
@@ -94,10 +98,11 @@ export const getLinePath = _ref => {
94
98
  export const getAreaPath = _ref2 => {
95
99
  let {
96
100
  data,
97
- curve = 'linear',
101
+ curve = 'bump',
98
102
  xScale,
99
103
  yScale,
100
- xData
104
+ xData,
105
+ connectNulls = false
101
106
  } = _ref2;
102
107
  if (data.length === 0) {
103
108
  return '';
@@ -158,6 +163,10 @@ export const getAreaPath = _ref2 => {
158
163
  isValid: true
159
164
  };
160
165
  });
166
+
167
+ // When connectNulls is true, filter out invalid points before rendering
168
+ // When false, use defined() to create gaps in the area
169
+ const filteredPoints = connectNulls ? dataPoints.filter(d => d.isValid) : dataPoints;
161
170
  const areaGenerator = d3Area().x(d => d.x).y0(d => {
162
171
  var _d$low;
163
172
  return (_d$low = d.low) != null ? _d$low : 0;
@@ -166,12 +175,26 @@ export const getAreaPath = _ref2 => {
166
175
  var _d$high;
167
176
  return (_d$high = d.high) != null ? _d$high : 0;
168
177
  }) // Top boundary (high values), fallback to 0
169
- .curve(curveFunction).defined(d => d.isValid && d.low != null && d.high != null); // Only draw where both values exist
178
+ .curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null); // Only draw where both values exist
170
179
 
171
- const result = areaGenerator(dataPoints);
180
+ const result = areaGenerator(filteredPoints);
172
181
  return result != null ? result : '';
173
182
  };
174
183
 
184
+ /**
185
+ * Converts line coordinates to an SVG path string.
186
+ * Useful for rendering axis lines and tick marks.
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * const path = lineToPath(0, 0, 100, 100);
191
+ * // Returns: "M 0 0 L 100 100"
192
+ * ```
193
+ */
194
+ export const lineToPath = (x1, y1, x2, y2) => {
195
+ return "M " + x1 + " " + y1 + " L " + x2 + " " + y2;
196
+ };
197
+
175
198
  /**
176
199
  * Creates an SVG path string for a rectangle with selective corner rounding.
177
200
  * Useful for creating bars in charts with optional rounded corners.
@@ -203,4 +226,49 @@ export const getBarPath = (x, y, width, height, radius, roundTop, roundBottom) =
203
226
  path += " A " + topR + " " + topR + " 0 0 1 " + (x + topR) + " " + y;
204
227
  path += ' Z';
205
228
  return path;
229
+ };
230
+
231
+ /**
232
+ * Generates an SVG path string with circles arranged in a dotted pattern within a bounding area.
233
+ * Creates circles at regular intervals based on the pattern size and dot size parameters.
234
+ *
235
+ * @param bounds - The bounding rectangle to fill with dots
236
+ * @param patternSize - Size of the pattern unit (spacing between dots)
237
+ * @param dotSize - Radius of each dot
238
+ * @returns SVG path string containing all the circles
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const dotsPath = getDottedAreaPath(
243
+ * { x: 0, y: 0, width: 100, height: 50 },
244
+ * 8, // 8px spacing
245
+ * 2 // 2px radius dots
246
+ * );
247
+ * ```
248
+ */
249
+ export const getDottedAreaPath = (bounds, patternSize, dotSize) => {
250
+ if (bounds.width <= 0 || bounds.height <= 0 || patternSize <= 0 || dotSize <= 0) {
251
+ return '';
252
+ }
253
+ let path = '';
254
+
255
+ // Calculate the number of dots that fit in each dimension
256
+ const dotsX = Math.ceil(bounds.width / patternSize);
257
+ const dotsY = Math.ceil(bounds.height / patternSize);
258
+
259
+ // Generate circles in a grid pattern
260
+ for (let row = 0; row < dotsY; row++) {
261
+ for (let col = 0; col < dotsX; col++) {
262
+ const centerX = bounds.x + col * patternSize + patternSize / 2;
263
+ const centerY = bounds.y + row * patternSize + patternSize / 2;
264
+
265
+ // Only draw dots that are within the bounds
266
+ if (centerX >= bounds.x && centerX <= bounds.x + bounds.width && centerY >= bounds.y && centerY <= bounds.y + bounds.height) {
267
+ // Create circle using SVG arc commands
268
+ // M cx,cy-r a r,r 0 1,0 0,2r a r,r 0 1,0 0,-2r
269
+ path += "M " + centerX + "," + (centerY - dotSize) + " a " + dotSize + "," + dotSize + " 0 1,0 0," + dotSize * 2 + " a " + dotSize + "," + dotSize + " 0 1,0 0," + -dotSize * 2 + " ";
270
+ }
271
+ }
272
+ }
273
+ return path.trim();
206
274
  };