@coinbase/cds-mobile-visualization 3.4.0-beta.2 → 3.4.0-beta.20

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 (211) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dts/chart/CartesianChart.d.ts +92 -34
  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/ChartProvider.d.ts +3 -0
  7. package/dts/chart/ChartProvider.d.ts.map +1 -1
  8. package/dts/chart/Path.d.ts +97 -32
  9. package/dts/chart/Path.d.ts.map +1 -1
  10. package/dts/chart/PeriodSelector.d.ts +6 -13
  11. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  12. package/dts/chart/area/Area.d.ts +39 -28
  13. package/dts/chart/area/Area.d.ts.map +1 -1
  14. package/dts/chart/area/AreaChart.d.ts +51 -10
  15. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  16. package/dts/chart/area/DottedArea.d.ts +21 -2
  17. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  18. package/dts/chart/area/GradientArea.d.ts +19 -13
  19. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  20. package/dts/chart/area/SolidArea.d.ts +17 -2
  21. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  22. package/dts/chart/axis/Axis.d.ts +86 -118
  23. package/dts/chart/axis/Axis.d.ts.map +1 -1
  24. package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
  25. package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
  26. package/dts/chart/axis/XAxis.d.ts +1 -1
  27. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  28. package/dts/chart/axis/YAxis.d.ts +2 -2
  29. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  30. package/dts/chart/axis/index.d.ts +1 -0
  31. package/dts/chart/axis/index.d.ts.map +1 -1
  32. package/dts/chart/bar/Bar.d.ts +49 -12
  33. package/dts/chart/bar/Bar.d.ts.map +1 -1
  34. package/dts/chart/bar/BarChart.d.ts +40 -19
  35. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  36. package/dts/chart/bar/BarPlot.d.ts +3 -1
  37. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  38. package/dts/chart/bar/BarStack.d.ts +41 -46
  39. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  40. package/dts/chart/bar/BarStackGroup.d.ts +2 -0
  41. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  42. package/dts/chart/bar/DefaultBar.d.ts +1 -1
  43. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  44. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  45. package/dts/chart/gradient/Gradient.d.ts +25 -0
  46. package/dts/chart/gradient/Gradient.d.ts.map +1 -0
  47. package/dts/chart/gradient/index.d.ts +2 -0
  48. package/dts/chart/gradient/index.d.ts.map +1 -0
  49. package/dts/chart/index.d.ts +4 -1
  50. package/dts/chart/index.d.ts.map +1 -1
  51. package/dts/chart/legend/DefaultLegendEntry.d.ts +5 -0
  52. package/dts/chart/legend/DefaultLegendEntry.d.ts.map +1 -0
  53. package/dts/chart/legend/DefaultLegendShape.d.ts +5 -0
  54. package/dts/chart/legend/DefaultLegendShape.d.ts.map +1 -0
  55. package/dts/chart/legend/Legend.d.ts +168 -0
  56. package/dts/chart/legend/Legend.d.ts.map +1 -0
  57. package/dts/chart/legend/index.d.ts +4 -0
  58. package/dts/chart/legend/index.d.ts.map +1 -0
  59. package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
  60. package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
  61. package/dts/chart/line/DottedLine.d.ts +13 -5
  62. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  63. package/dts/chart/line/Line.d.ts +61 -27
  64. package/dts/chart/line/Line.d.ts.map +1 -1
  65. package/dts/chart/line/LineChart.d.ts +43 -9
  66. package/dts/chart/line/LineChart.d.ts.map +1 -1
  67. package/dts/chart/line/ReferenceLine.d.ts +68 -20
  68. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  69. package/dts/chart/line/SolidLine.d.ts +8 -5
  70. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  71. package/dts/chart/line/index.d.ts +1 -1
  72. package/dts/chart/line/index.d.ts.map +1 -1
  73. package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
  74. package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
  75. package/dts/chart/point/Point.d.ts +136 -0
  76. package/dts/chart/point/Point.d.ts.map +1 -0
  77. package/dts/chart/point/index.d.ts +3 -0
  78. package/dts/chart/point/index.d.ts.map +1 -0
  79. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +38 -0
  80. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
  81. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
  82. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
  83. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
  84. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
  85. package/dts/chart/scrubber/Scrubber.d.ts +230 -42
  86. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  87. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +54 -0
  88. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
  89. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +46 -0
  90. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
  91. package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
  92. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  93. package/dts/chart/scrubber/index.d.ts +3 -0
  94. package/dts/chart/scrubber/index.d.ts.map +1 -1
  95. package/dts/chart/text/ChartText.d.ts +151 -77
  96. package/dts/chart/text/ChartText.d.ts.map +1 -1
  97. package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
  98. package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
  99. package/dts/chart/text/index.d.ts +1 -1
  100. package/dts/chart/text/index.d.ts.map +1 -1
  101. package/dts/chart/utils/axis.d.ts +25 -1
  102. package/dts/chart/utils/axis.d.ts.map +1 -1
  103. package/dts/chart/utils/bar.d.ts +34 -0
  104. package/dts/chart/utils/bar.d.ts.map +1 -1
  105. package/dts/chart/utils/chart.d.ts +52 -7
  106. package/dts/chart/utils/chart.d.ts.map +1 -1
  107. package/dts/chart/utils/context.d.ts +28 -7
  108. package/dts/chart/utils/context.d.ts.map +1 -1
  109. package/dts/chart/utils/gradient.d.ts +117 -0
  110. package/dts/chart/utils/gradient.d.ts.map +1 -0
  111. package/dts/chart/utils/index.d.ts +3 -0
  112. package/dts/chart/utils/index.d.ts.map +1 -1
  113. package/dts/chart/utils/path.d.ts +59 -0
  114. package/dts/chart/utils/path.d.ts.map +1 -1
  115. package/dts/chart/utils/point.d.ts +71 -7
  116. package/dts/chart/utils/point.d.ts.map +1 -1
  117. package/dts/chart/utils/scale.d.ts +102 -0
  118. package/dts/chart/utils/scale.d.ts.map +1 -1
  119. package/dts/chart/utils/scrubber.d.ts +40 -0
  120. package/dts/chart/utils/scrubber.d.ts.map +1 -0
  121. package/dts/chart/utils/transition.d.ts +178 -0
  122. package/dts/chart/utils/transition.d.ts.map +1 -0
  123. package/esm/chart/CartesianChart.js +199 -75
  124. package/esm/chart/ChartContextBridge.js +159 -0
  125. package/esm/chart/ChartProvider.js +2 -2
  126. package/esm/chart/Path.js +200 -114
  127. package/esm/chart/PeriodSelector.js +7 -3
  128. package/esm/chart/__stories__/CartesianChart.stories.js +307 -134
  129. package/esm/chart/__stories__/ChartTransitions.stories.js +629 -0
  130. package/esm/chart/__stories__/PeriodSelector.stories.js +201 -75
  131. package/esm/chart/area/Area.js +27 -35
  132. package/esm/chart/area/AreaChart.js +17 -12
  133. package/esm/chart/area/DottedArea.js +64 -108
  134. package/esm/chart/area/GradientArea.js +37 -91
  135. package/esm/chart/area/SolidArea.js +24 -8
  136. package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
  137. package/esm/chart/axis/Axis.js +5 -39
  138. package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
  139. package/esm/chart/axis/XAxis.js +148 -66
  140. package/esm/chart/axis/YAxis.js +149 -65
  141. package/esm/chart/axis/__stories__/Axis.stories.js +259 -1
  142. package/esm/chart/axis/index.js +1 -0
  143. package/esm/chart/bar/Bar.js +7 -1
  144. package/esm/chart/bar/BarChart.js +17 -37
  145. package/esm/chart/bar/BarPlot.js +43 -35
  146. package/esm/chart/bar/BarStack.js +84 -37
  147. package/esm/chart/bar/BarStackGroup.js +7 -17
  148. package/esm/chart/bar/DefaultBar.js +29 -51
  149. package/esm/chart/bar/DefaultBarStack.js +34 -58
  150. package/esm/chart/bar/__stories__/BarChart.stories.js +948 -88
  151. package/esm/chart/gradient/Gradient.js +53 -0
  152. package/esm/chart/gradient/index.js +1 -0
  153. package/esm/chart/index.js +4 -1
  154. package/esm/chart/legend/DefaultLegendEntry.js +42 -0
  155. package/esm/chart/legend/DefaultLegendShape.js +64 -0
  156. package/esm/chart/legend/Legend.js +59 -0
  157. package/esm/chart/legend/__stories__/Legend.stories.js +574 -0
  158. package/esm/chart/legend/index.js +3 -0
  159. package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
  160. package/esm/chart/line/DottedLine.js +31 -14
  161. package/esm/chart/line/Line.js +96 -68
  162. package/esm/chart/line/LineChart.js +21 -14
  163. package/esm/chart/line/ReferenceLine.js +80 -63
  164. package/esm/chart/line/SolidLine.js +27 -10
  165. package/esm/chart/line/__stories__/LineChart.stories.js +1748 -2048
  166. package/esm/chart/line/__stories__/ReferenceLine.stories.js +177 -28
  167. package/esm/chart/line/index.js +1 -1
  168. package/esm/chart/point/DefaultPointLabel.js +39 -0
  169. package/esm/chart/point/Point.js +186 -0
  170. package/esm/chart/point/index.js +2 -0
  171. package/esm/chart/scrubber/DefaultScrubberBeacon.js +180 -0
  172. package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
  173. package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
  174. package/esm/chart/scrubber/Scrubber.js +130 -144
  175. package/esm/chart/scrubber/ScrubberBeaconGroup.js +165 -0
  176. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +208 -0
  177. package/esm/chart/scrubber/ScrubberProvider.js +46 -54
  178. package/esm/chart/scrubber/__stories__/Scrubber.stories.js +760 -0
  179. package/esm/chart/scrubber/index.js +3 -1
  180. package/esm/chart/text/ChartText.js +242 -174
  181. package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
  182. package/esm/chart/text/index.js +1 -1
  183. package/esm/chart/utils/axis.js +47 -31
  184. package/esm/chart/utils/bar.js +43 -0
  185. package/esm/chart/utils/chart.js +57 -3
  186. package/esm/chart/utils/gradient.js +305 -0
  187. package/esm/chart/utils/index.js +3 -0
  188. package/esm/chart/utils/path.js +84 -8
  189. package/esm/chart/utils/point.js +171 -17
  190. package/esm/chart/utils/scale.js +242 -2
  191. package/esm/chart/utils/scrubber.js +146 -0
  192. package/esm/chart/utils/transition.js +220 -0
  193. package/esm/sparkline/__figma__/Sparkline.figma.js +1 -1
  194. package/esm/sparkline/__stories__/Sparkline.stories.js +11 -7
  195. package/esm/sparkline/__stories__/SparklineGradient.stories.js +7 -4
  196. package/esm/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.js +1 -1
  197. package/esm/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories.js +51 -26
  198. package/esm/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.js +1 -1
  199. package/esm/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories.js +17 -9
  200. package/package.json +15 -10
  201. package/dts/chart/Point.d.ts +0 -103
  202. package/dts/chart/Point.d.ts.map +0 -1
  203. package/dts/chart/line/GradientLine.d.ts +0 -45
  204. package/dts/chart/line/GradientLine.d.ts.map +0 -1
  205. package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
  206. package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
  207. package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
  208. package/esm/chart/Point.js +0 -111
  209. package/esm/chart/__stories__/Chart.stories.js +0 -79
  210. package/esm/chart/line/GradientLine.js +0 -62
  211. package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
@@ -1,2 +1,4 @@
1
- // Only export Scrubber component
1
+ export * from './DefaultScrubberBeacon';
2
+ export * from './DefaultScrubberBeaconLabel';
3
+ export * from './DefaultScrubberLabel';
2
4
  export * from './Scrubber';
@@ -1,11 +1,28 @@
1
- import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
2
- import { G, Rect as SvgRect, Text } from 'react-native-svg';
1
+ import { memo, useMemo } from 'react';
2
+ import { runOnJS, useAnimatedReaction, useDerivedValue } from 'react-native-reanimated';
3
3
  import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
4
+ import { FontSlant, FontWeight, Group, Paint, Paragraph, RoundedRect, Shadow, Skia, TextAlign } from '@shopify/react-native-skia';
4
5
  import { useCartesianChartContext } from '../ChartProvider';
5
- import { getChartInset } from '../utils';
6
+ import { getChartInset, getColorWithOpacity, unwrapAnimatedValue } from '../utils';
7
+
8
+ /**
9
+ * Converts a fontWeight from Theme to a Skia FontWeight
10
+ * @note this only works when the fontWeight is a valid number (ie not 'bold')
11
+ * @param theme - The theme to use
12
+ * @param font - The font to use
13
+ * @returns The FontWeight or undefined if the fontWeight is not a valid number
14
+ */
15
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
+ const getFontWeight = (theme, font) => {
17
+ const themeFontWeight = theme.fontWeight[font];
18
+ const numericWeight = typeof themeFontWeight === 'string' ? Number(themeFontWeight) : themeFontWeight;
19
+ const validFontWeights = Object.values(FontWeight).filter(value => typeof value === 'number');
20
+ return numericWeight !== undefined && validFontWeights.includes(numericWeight) ? numericWeight : undefined;
21
+ };
6
22
 
7
23
  /**
8
24
  * The supported content types for ChartText.
25
+ * Pass a string for simple text, or a SkParagraph for advanced rich text formatting.
9
26
  */
10
27
 
11
28
  /**
@@ -15,137 +32,156 @@ import { getChartInset } from '../utils';
15
32
  /**
16
33
  * Vertical alignment options for chart text.
17
34
  */
18
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
- /**
20
- * Maps horizontal alignment to SVG textAnchor.
21
- * This abstraction allows us to provide a consistent alignment API across web and mobile platforms,
22
- * hiding the platform-specific SVG property differences.
23
- */
24
- const getTextAnchor = alignment => {
25
- switch (alignment) {
26
- case 'left':
27
- return 'start';
28
- case 'center':
29
- return 'middle';
30
- case 'right':
31
- return 'end';
32
- }
33
- };
34
35
 
35
- /**
36
- * Maps vertical alignment to SVG alignmentBaseline.
37
- * This abstraction allows us to provide a consistent alignment API across web and mobile platforms,
38
- * hiding the platform-specific SVG property differences.
39
- */
40
- const getAlignmentBaseline = alignment => {
41
- switch (alignment) {
42
- case 'top':
43
- return 'hanging';
44
- case 'middle':
45
- return 'central';
46
- case 'bottom':
47
- return 'ideographic';
48
- }
49
- };
50
- const ChartTextVisible = /*#__PURE__*/memo(_ref => {
51
- let {
52
- children,
53
- background,
54
- textAnchor,
55
- alignmentBaseline,
56
- fontSize,
57
- fontWeight,
58
- fill,
59
- borderRadius,
60
- inset: insetInput,
61
- textDimensions,
62
- dx,
63
- dy
64
- } = _ref;
65
- const theme = useTheme();
66
- const inset = useMemo(() => getChartInset(insetInput), [insetInput]);
67
- const rectHeight = useMemo(() => textDimensions.height + inset.top + inset.bottom, [textDimensions, inset]);
68
- const rectWidth = useMemo(() => textDimensions.width + inset.left + inset.right, [textDimensions, inset]);
69
- return /*#__PURE__*/_jsxs(G, {
70
- children: [background !== 'transparent' && /*#__PURE__*/_jsx(SvgRect, {
71
- fill: background,
72
- height: rectHeight,
73
- rx: borderRadius,
74
- ry: borderRadius,
75
- width: rectWidth,
76
- x: textDimensions.x - inset.left,
77
- y: textDimensions.y - inset.top
78
- }), /*#__PURE__*/_jsx(Text, {
79
- alignmentBaseline: alignmentBaseline,
80
- dx: dx,
81
- dy: dy,
82
- fill: fill != null ? fill : theme.color.fgMuted,
83
- fontSize: fontSize,
84
- fontWeight: fontWeight,
85
- textAnchor: textAnchor,
86
- children: children
87
- })]
88
- });
89
- });
90
- export const ChartText = /*#__PURE__*/memo(_ref2 => {
36
+ export const ChartText = /*#__PURE__*/memo(_ref => {
37
+ var _elevationShadow$shad, _elevationShadow$shad2, _elevationShadow$shad3, _elevationShadow$shad4, _elevationShadow$shad5, _elevationShadow$shad6, _elevationShadow$shad7;
91
38
  let {
92
39
  children,
93
40
  x,
94
41
  y,
42
+ dx = 0,
43
+ dy = 0,
95
44
  horizontalAlignment = 'center',
96
45
  verticalAlignment = 'middle',
97
- dx,
98
- dy,
46
+ paragraphAlignment = TextAlign.Left,
99
47
  disableRepositioning = false,
100
48
  bounds,
101
- testID,
102
- fontSize = 12,
103
- fontWeight,
104
49
  color,
105
- background = 'transparent',
106
- borderRadius,
50
+ background: backgroundProp,
51
+ borderRadius = 4,
107
52
  inset: insetInput,
108
53
  onDimensionsChange,
109
- opacity = 1
110
- } = _ref2;
54
+ opacity = 1,
55
+ fontFamilies,
56
+ font = 'label2',
57
+ fontSize,
58
+ fontWeight,
59
+ fontStyle: fontStyleProp = FontSlant.Upright,
60
+ elevated
61
+ } = _ref;
62
+ const theme = useTheme();
111
63
  const {
112
64
  width: chartWidth,
113
- height: chartHeight
65
+ height: chartHeight,
66
+ fontFamilies: contextFontFamilies,
67
+ fontProvider
114
68
  } = useCartesianChartContext();
115
- const textAnchor = useMemo(() => getTextAnchor(horizontalAlignment), [horizontalAlignment]);
116
- const alignmentBaseline = useMemo(() => getAlignmentBaseline(verticalAlignment), [verticalAlignment]);
117
- const fullChartBounds = useMemo(() => ({
118
- x: 0,
119
- y: 0,
120
- width: chartWidth,
121
- height: chartHeight
122
- }), [chartWidth, chartHeight]);
123
- const [textSize, setTextSize] = useState(null);
124
- const textBBox = useMemo(() => {
125
- if (!textSize) {
126
- return null;
69
+ const inset = useMemo(() => getChartInset(insetInput), [insetInput]);
70
+ const background = backgroundProp != null ? backgroundProp : elevated ? theme.color.bgElevation1 : 'transparent';
71
+ const defaultParagraphStyle = useMemo(() => {
72
+ var _ref2;
73
+ return {
74
+ fontFamilies: (_ref2 = fontFamilies != null ? fontFamilies : contextFontFamilies) != null ? _ref2 : [],
75
+ fontSize: fontSize != null ? fontSize : theme.fontSize[font],
76
+ fontStyle: {
77
+ weight: fontWeight != null ? fontWeight : getFontWeight(theme, font),
78
+ slant: fontStyleProp
79
+ },
80
+ color: Skia.Color(color != null ? color : theme.color.fgMuted)
81
+ };
82
+ }, [fontFamilies, contextFontFamilies, fontSize, theme, font, fontWeight, fontStyleProp, color]);
83
+ const paragraph = useDerivedValue(() => {
84
+ const childrenValue = unwrapAnimatedValue(children);
85
+ if (typeof childrenValue !== 'string') {
86
+ return childrenValue;
127
87
  }
88
+ const builder = Skia.ParagraphBuilder.Make({
89
+ textAlign: TextAlign.Left
90
+ }, fontProvider);
91
+ builder.pushStyle(defaultParagraphStyle);
92
+ builder.addText(childrenValue);
93
+ builder.pop();
94
+ const para = builder.build();
95
+ para.layout(chartWidth);
96
+ return para;
97
+ }, [children, fontProvider, defaultParagraphStyle, chartWidth]);
98
+ const textDimensions = useDerivedValue(() => {
99
+ const unwrappedParagraph = paragraph.value;
100
+ if (!unwrappedParagraph) return {
101
+ width: 0,
102
+ height: 0
103
+ };
128
104
  return {
129
- x: x + textSize.x,
130
- y: y + textSize.y,
131
- width: textSize.width,
132
- height: textSize.height
105
+ width: unwrappedParagraph.getLongestLine(),
106
+ height: unwrappedParagraph.getHeight()
133
107
  };
134
- }, [x, y, textSize]);
135
- const isDimensionsReady = disableRepositioning || textBBox !== null;
136
- const backgroundRectDimensions = useMemo(() => {
137
- if (!textBBox) {
138
- return null;
108
+ }, [paragraph]);
109
+ const backgroundRectSize = useDerivedValue(() => ({
110
+ width: textDimensions.value.width + inset.left + inset.right,
111
+ height: textDimensions.value.height + inset.top + inset.bottom
112
+ }), [textDimensions, inset]);
113
+
114
+ // Calculate background rect position based on alignment
115
+ const backgroundRect = useDerivedValue(() => {
116
+ const horAlignment = unwrapAnimatedValue(horizontalAlignment);
117
+ const verAlignment = unwrapAnimatedValue(verticalAlignment);
118
+ // By default the value is top left
119
+ let rectX = unwrapAnimatedValue(x);
120
+ let rectY = unwrapAnimatedValue(y);
121
+ const rectSize = backgroundRectSize.value;
122
+
123
+ // Adjust for horizontal alignment
124
+ switch (horAlignment) {
125
+ case 'center':
126
+ rectX = rectX - rectSize.width / 2;
127
+ break;
128
+ case 'right':
129
+ rectX = rectX - rectSize.width;
130
+ break;
131
+ }
132
+
133
+ // Adjust for vertical alignment
134
+ switch (verAlignment) {
135
+ case 'middle':
136
+ rectY = rectY - rectSize.height / 2;
137
+ break;
138
+ case 'bottom':
139
+ rectY = rectY - rectSize.height;
140
+ break;
139
141
  }
140
- const inset = getChartInset(insetInput);
141
142
  return {
142
- x: textBBox.x - inset.left,
143
- y: textBBox.y - inset.top,
144
- width: textBBox.width + inset.left + inset.right,
145
- height: textBBox.height + inset.top + inset.bottom
143
+ x: rectX,
144
+ y: rectY,
145
+ width: rectSize.width,
146
+ height: rectSize.height
146
147
  };
147
- }, [textBBox, insetInput]);
148
- const overflowAmount = useMemo(() => {
148
+ }, [x, y, backgroundRectSize, horizontalAlignment, verticalAlignment]);
149
+
150
+ // Paragraph uses top-left positioning
151
+ const textPosition = useDerivedValue(() => {
152
+ const textDims = textDimensions.value;
153
+
154
+ // Calculate horizontal offset based on paragraph alignment
155
+ let horizontalOffset = 0;
156
+ switch (paragraphAlignment) {
157
+ case TextAlign.Center:
158
+ horizontalOffset = -textDims.width / 2;
159
+ break;
160
+ case TextAlign.Right:
161
+ case TextAlign.End:
162
+ horizontalOffset = -textDims.width;
163
+ break;
164
+ default:
165
+ // Left-aligned text needs no offset
166
+ horizontalOffset = 0;
167
+ break;
168
+ }
169
+ return {
170
+ x: backgroundRect.value.x + inset.left + horizontalOffset,
171
+ y: backgroundRect.value.y + inset.top,
172
+ width: textDims.width,
173
+ height: textDims.height
174
+ };
175
+ }, [backgroundRect, textDimensions, inset, paragraphAlignment]);
176
+
177
+ // Calculate overflow and repositioning
178
+ const fullChartBounds = useMemo(() => ({
179
+ x: 0,
180
+ y: 0,
181
+ width: chartWidth,
182
+ height: chartHeight
183
+ }), [chartWidth, chartHeight]);
184
+ const overflowAmount = useDerivedValue(() => {
149
185
  if (disableRepositioning) {
150
186
  return {
151
187
  x: 0,
@@ -153,85 +189,117 @@ export const ChartText = /*#__PURE__*/memo(_ref2 => {
153
189
  };
154
190
  }
155
191
  const parentBounds = bounds != null ? bounds : fullChartBounds;
156
- if (!backgroundRectDimensions || !parentBounds || parentBounds.width <= 0 || parentBounds.height <= 0) {
192
+ if (!parentBounds || parentBounds.width <= 0 || parentBounds.height <= 0) {
157
193
  return {
158
194
  x: 0,
159
195
  y: 0
160
196
  };
161
197
  }
162
- let x = 0;
163
- let y = 0;
198
+ let offsetX = 0;
199
+ let offsetY = 0;
164
200
 
165
201
  // X-axis overflow
166
- if (backgroundRectDimensions.x < parentBounds.x) {
167
- x = parentBounds.x - backgroundRectDimensions.x; // positive = shift right
168
- } else if (backgroundRectDimensions.x + backgroundRectDimensions.width > parentBounds.x + parentBounds.width) {
169
- x = parentBounds.x + parentBounds.width - (backgroundRectDimensions.x + backgroundRectDimensions.width); // negative = shift left
202
+ if (backgroundRect.value.x < parentBounds.x) {
203
+ offsetX = parentBounds.x - backgroundRect.value.x;
204
+ } else if (backgroundRect.value.x + backgroundRect.value.width > parentBounds.x + parentBounds.width) {
205
+ offsetX = parentBounds.x + parentBounds.width - (backgroundRect.value.x + backgroundRect.value.width);
170
206
  }
171
207
 
172
208
  // Y-axis overflow
173
- if (backgroundRectDimensions.y < parentBounds.y) {
174
- y = parentBounds.y - backgroundRectDimensions.y; // positive = shift down
175
- } else if (backgroundRectDimensions.y + backgroundRectDimensions.height > parentBounds.y + parentBounds.height) {
176
- y = parentBounds.y + parentBounds.height - (backgroundRectDimensions.y + backgroundRectDimensions.height); // negative = shift up
209
+ if (backgroundRect.value.y < parentBounds.y) {
210
+ offsetY = parentBounds.y - backgroundRect.value.y;
211
+ } else if (backgroundRect.value.y + backgroundRect.value.height > parentBounds.y + parentBounds.height) {
212
+ offsetY = parentBounds.y + parentBounds.height - (backgroundRect.value.y + backgroundRect.value.height);
177
213
  }
178
214
  return {
179
- x,
180
- y
215
+ x: offsetX,
216
+ y: offsetY
181
217
  };
182
- }, [backgroundRectDimensions, fullChartBounds, bounds, disableRepositioning]);
183
- const reportedRect = useMemo(() => {
184
- if (!backgroundRectDimensions) return null;
218
+ }, [backgroundRect, fullChartBounds, bounds, disableRepositioning]);
219
+
220
+ // Final adjusted positions
221
+ const backgroundRectWithOffset = useDerivedValue(() => {
222
+ const offsetX = unwrapAnimatedValue(dx);
223
+ const offsetY = unwrapAnimatedValue(dy);
185
224
  return {
186
- x: backgroundRectDimensions.x + overflowAmount.x,
187
- y: backgroundRectDimensions.y + overflowAmount.y,
188
- width: backgroundRectDimensions.width,
189
- height: backgroundRectDimensions.height
225
+ x: backgroundRect.value.x + overflowAmount.value.x + offsetX,
226
+ y: backgroundRect.value.y + overflowAmount.value.y + offsetY,
227
+ width: backgroundRect.value.width,
228
+ height: backgroundRect.value.height
190
229
  };
191
- }, [backgroundRectDimensions, overflowAmount.x, overflowAmount.y]);
192
- useEffect(() => {
193
- if (onDimensionsChange && reportedRect !== null) {
194
- onDimensionsChange(reportedRect);
230
+ }, [backgroundRect, overflowAmount, dx, dy]);
231
+ const textWithOffsetX = useDerivedValue(() => textPosition.value.x + overflowAmount.value.x + unwrapAnimatedValue(dx), [textPosition, overflowAmount, dx]);
232
+ const textWithOffsetY = useDerivedValue(() => textPosition.value.y + overflowAmount.value.y + unwrapAnimatedValue(dy), [textPosition, overflowAmount, dy]);
233
+ useAnimatedReaction(() => backgroundRectWithOffset.value, (rect, previous) => {
234
+ if (onDimensionsChange && rect !== previous) {
235
+ runOnJS(onDimensionsChange)(rect);
195
236
  }
196
- }, [reportedRect, onDimensionsChange]);
197
- const onLayout = useCallback(event => {
198
- if (event.nativeEvent.layout.width > 0 && event.nativeEvent.layout.height > 0) {
199
- setTextSize(event.nativeEvent.layout);
237
+ }, [onDimensionsChange]);
238
+
239
+ // Show group if we are ready
240
+ const groupOpacity = useDerivedValue(() => {
241
+ const textSize = textDimensions.value;
242
+ const hasValidContent = paragraph.value && textSize.width > 0 && textSize.height > 0;
243
+ return hasValidContent ? unwrapAnimatedValue(opacity) : 0;
244
+ }, [paragraph, textDimensions, opacity]);
245
+ const backgroundRectHeight = useDerivedValue(() => backgroundRectWithOffset.value.height, [backgroundRectWithOffset]);
246
+ const backgroundRectWidth = useDerivedValue(() => backgroundRectWithOffset.value.width, [backgroundRectWithOffset]);
247
+ const backgroundRectX = useDerivedValue(() => backgroundRectWithOffset.value.x, [backgroundRectWithOffset]);
248
+ const backgroundRectY = useDerivedValue(() => backgroundRectWithOffset.value.y, [backgroundRectWithOffset]);
249
+ const elevationShadow = elevated ? theme.shadow.elevation1 : undefined;
250
+
251
+ // Calculate the paragraph's internal x offset from line metrics based on text alignment
252
+ const paragraphTransform = useDerivedValue(() => {
253
+ if (!paragraph.value || !paragraphAlignment) return [];
254
+ const rects = paragraph.value.getLineMetrics();
255
+ if (rects.length === 0) return [];
256
+ let minOffset;
257
+ switch (paragraphAlignment) {
258
+ case TextAlign.Center:
259
+ // For center-aligned text, account for half the width
260
+ minOffset = Math.min(...rects.map(rect => rect.x - rect.width / 2));
261
+ break;
262
+ case TextAlign.Right:
263
+ case TextAlign.End:
264
+ // For right-aligned text, account for the full width
265
+ minOffset = Math.min(...rects.map(rect => rect.x - rect.width));
266
+ break;
267
+ default:
268
+ // For left-aligned text, use the x position directly
269
+ minOffset = Math.min(...rects.map(rect => rect.x));
270
+ break;
200
271
  }
201
- }, []);
202
- return /*#__PURE__*/_jsxs(G, {
203
- opacity: isDimensionsReady ? opacity : 0,
204
- children: [textSize && /*#__PURE__*/_jsx(G, {
205
- transform: [{
206
- translateX: x + overflowAmount.x
207
- }, {
208
- translateY: y + overflowAmount.y
209
- }],
210
- children: /*#__PURE__*/_jsx(ChartTextVisible, {
211
- alignmentBaseline: alignmentBaseline,
212
- background: background,
213
- borderRadius: borderRadius,
214
- dx: dx,
215
- dy: dy,
216
- fill: color,
217
- fontSize: fontSize,
218
- fontWeight: fontWeight,
219
- inset: insetInput,
220
- textAnchor: textAnchor,
221
- textDimensions: textSize,
222
- children: children
272
+ return [{
273
+ translateX: -minOffset
274
+ }];
275
+ }, [paragraph, paragraphAlignment]);
276
+
277
+ // Opacity on a group doesn't impact the paragraph so we need to apply it to Group
278
+ return /*#__PURE__*/_jsxs(Group, {
279
+ layer: /*#__PURE__*/_jsx(Paint, {
280
+ opacity: groupOpacity
281
+ }),
282
+ children: [background !== 'transparent' && /*#__PURE__*/_jsx(RoundedRect, {
283
+ color: background,
284
+ height: backgroundRectHeight,
285
+ r: borderRadius,
286
+ width: backgroundRectWidth,
287
+ x: backgroundRectX,
288
+ y: backgroundRectY,
289
+ children: elevationShadow && /*#__PURE__*/_jsx(Shadow, {
290
+ blur: Number((_elevationShadow$shad = elevationShadow.shadowRadius) != null ? _elevationShadow$shad : 0),
291
+ color: getColorWithOpacity(String((_elevationShadow$shad2 = elevationShadow.shadowColor) != null ? _elevationShadow$shad2 : '#000000'), Number((_elevationShadow$shad3 = elevationShadow.shadowOpacity) != null ? _elevationShadow$shad3 : 1)),
292
+ dx: Number((_elevationShadow$shad4 = (_elevationShadow$shad5 = elevationShadow.shadowOffset) == null ? void 0 : _elevationShadow$shad5.width) != null ? _elevationShadow$shad4 : 0),
293
+ dy: Number((_elevationShadow$shad6 = (_elevationShadow$shad7 = elevationShadow.shadowOffset) == null ? void 0 : _elevationShadow$shad7.height) != null ? _elevationShadow$shad6 : 0)
294
+ })
295
+ }), /*#__PURE__*/_jsx(Group, {
296
+ transform: paragraphTransform,
297
+ children: /*#__PURE__*/_jsx(Paragraph, {
298
+ paragraph: paragraph,
299
+ width: chartWidth,
300
+ x: textWithOffsetX,
301
+ y: textWithOffsetY
223
302
  })
224
- }), /*#__PURE__*/_jsx(Text, {
225
- alignmentBaseline: alignmentBaseline,
226
- dx: dx,
227
- dy: dy,
228
- fill: "transparent",
229
- fontSize: fontSize,
230
- fontWeight: fontWeight,
231
- onLayout: onLayout,
232
- opacity: 0,
233
- textAnchor: textAnchor,
234
- children: children
235
303
  })]
236
304
  });
237
305
  });
@@ -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
  */
@@ -52,7 +83,7 @@ export const getAxisScale = _ref => {
52
83
  max: (_config$domain$max = config.domain.max) != null ? _config$domain$max : dataDomain.max
53
84
  };
54
85
  }
55
- if (!isValidBounds(adjustedDomain)) throw new Error('Invalid domain bounds. See https://cds.coinbase.com/http://localhost:3000/components/graphs/XAxis/#domain');
86
+ if (!isValidBounds(adjustedDomain)) throw new Error('Invalid domain bounds. See https://cds.coinbase.com/components/charts/XAxis/#domain');
56
87
  if (scaleType === 'band') {
57
88
  var _config$categoryPaddi;
58
89
  return getCategoricalScale({
@@ -106,7 +137,7 @@ export const getAxisConfig = function (type, axes, defaultId, defaultScaleType)
106
137
  } = _ref2;
107
138
  return id === undefined;
108
139
  })) {
109
- throw new Error('When defining multiple axes, each must have a unique id. See https://cds.coinbase.com/components/graphs/YAxis/#multiple-y-axes.');
140
+ throw new Error('When defining multiple axes, each must have a unique id. See https://cds.coinbase.com/components/charts/YAxis/#multiple-y-axes.');
110
141
  }
111
142
  return axes.map(_ref3 => {
112
143
  let {
@@ -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