@automattic/charts 0.58.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +7 -54
  3. package/dist/index.cjs +9606 -22
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +20 -25
  6. package/dist/index.css.map +1 -1
  7. package/dist/index.d.cts +1612 -33
  8. package/dist/index.d.ts +1612 -33
  9. package/dist/index.js +9640 -56
  10. package/dist/index.js.map +1 -1
  11. package/package.json +8 -125
  12. package/src/charts/bar-chart/bar-chart.module.scss +0 -5
  13. package/src/charts/bar-chart/bar-chart.tsx +131 -137
  14. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +23 -40
  15. package/src/charts/line-chart/line-chart.module.scss +0 -5
  16. package/src/charts/line-chart/line-chart.tsx +190 -183
  17. package/src/charts/line-chart/private/line-chart-annotations-overlay.tsx +1 -2
  18. package/src/charts/pie-chart/pie-chart.module.scss +2 -10
  19. package/src/charts/pie-chart/pie-chart.tsx +198 -199
  20. package/src/charts/pie-chart/test/composition-api.test.tsx +3 -3
  21. package/src/charts/pie-chart/test/pie-chart.test.tsx +42 -35
  22. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +2 -8
  23. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +155 -155
  24. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +25 -25
  25. package/src/charts/private/chart-layout/chart-layout.module.scss +7 -0
  26. package/src/charts/private/chart-layout/chart-layout.tsx +106 -0
  27. package/src/charts/private/chart-layout/index.ts +2 -0
  28. package/src/charts/private/chart-layout/test/chart-layout.test.tsx +167 -0
  29. package/src/charts/private/single-chart-context/single-chart-context.tsx +2 -2
  30. package/src/charts/private/svg-empty-state/index.ts +1 -0
  31. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +7 -0
  32. package/src/charts/private/svg-empty-state/svg-empty-state.tsx +40 -0
  33. package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +12 -8
  34. package/src/components/legend/hooks/use-chart-legend-items.ts +12 -13
  35. package/src/components/legend/legend.tsx +33 -8
  36. package/src/components/legend/test/legend.test.tsx +93 -1
  37. package/src/hooks/index.ts +1 -0
  38. package/src/hooks/use-data-with-percentages.ts +24 -0
  39. package/src/hooks/use-interactive-legend-data.ts +18 -15
  40. package/src/index.ts +65 -2
  41. package/src/providers/chart-context/global-charts-provider.tsx +7 -1
  42. package/src/providers/chart-context/hooks/use-chart-registration.ts +2 -1
  43. package/src/providers/chart-context/types.ts +2 -2
  44. package/src/types.ts +27 -7
  45. package/src/utils/test/resolve-css-var.test.ts +2 -0
  46. package/dist/base-tooltip-DOq93wjU.d.cts +0 -38
  47. package/dist/base-tooltip-DOq93wjU.d.ts +0 -38
  48. package/dist/charts/bar-chart/index.cjs +0 -17
  49. package/dist/charts/bar-chart/index.cjs.map +0 -1
  50. package/dist/charts/bar-chart/index.css +0 -141
  51. package/dist/charts/bar-chart/index.css.map +0 -1
  52. package/dist/charts/bar-chart/index.d.cts +0 -36
  53. package/dist/charts/bar-chart/index.d.ts +0 -36
  54. package/dist/charts/bar-chart/index.js +0 -17
  55. package/dist/charts/bar-chart/index.js.map +0 -1
  56. package/dist/charts/bar-list-chart/index.cjs +0 -18
  57. package/dist/charts/bar-list-chart/index.cjs.map +0 -1
  58. package/dist/charts/bar-list-chart/index.css +0 -141
  59. package/dist/charts/bar-list-chart/index.css.map +0 -1
  60. package/dist/charts/bar-list-chart/index.d.cts +0 -92
  61. package/dist/charts/bar-list-chart/index.d.ts +0 -92
  62. package/dist/charts/bar-list-chart/index.js +0 -18
  63. package/dist/charts/bar-list-chart/index.js.map +0 -1
  64. package/dist/charts/conversion-funnel-chart/index.cjs +0 -11
  65. package/dist/charts/conversion-funnel-chart/index.cjs.map +0 -1
  66. package/dist/charts/conversion-funnel-chart/index.css +0 -157
  67. package/dist/charts/conversion-funnel-chart/index.css.map +0 -1
  68. package/dist/charts/conversion-funnel-chart/index.d.cts +0 -97
  69. package/dist/charts/conversion-funnel-chart/index.d.ts +0 -97
  70. package/dist/charts/conversion-funnel-chart/index.js +0 -11
  71. package/dist/charts/conversion-funnel-chart/index.js.map +0 -1
  72. package/dist/charts/geo-chart/index.cjs +0 -13
  73. package/dist/charts/geo-chart/index.cjs.map +0 -1
  74. package/dist/charts/geo-chart/index.css +0 -23
  75. package/dist/charts/geo-chart/index.css.map +0 -1
  76. package/dist/charts/geo-chart/index.d.cts +0 -67
  77. package/dist/charts/geo-chart/index.d.ts +0 -67
  78. package/dist/charts/geo-chart/index.js +0 -13
  79. package/dist/charts/geo-chart/index.js.map +0 -1
  80. package/dist/charts/leaderboard-chart/index.cjs +0 -21
  81. package/dist/charts/leaderboard-chart/index.cjs.map +0 -1
  82. package/dist/charts/leaderboard-chart/index.css +0 -160
  83. package/dist/charts/leaderboard-chart/index.css.map +0 -1
  84. package/dist/charts/leaderboard-chart/index.d.cts +0 -46
  85. package/dist/charts/leaderboard-chart/index.d.ts +0 -46
  86. package/dist/charts/leaderboard-chart/index.js +0 -21
  87. package/dist/charts/leaderboard-chart/index.js.map +0 -1
  88. package/dist/charts/line-chart/index.cjs +0 -17
  89. package/dist/charts/line-chart/index.cjs.map +0 -1
  90. package/dist/charts/line-chart/index.css +0 -213
  91. package/dist/charts/line-chart/index.css.map +0 -1
  92. package/dist/charts/line-chart/index.d.cts +0 -98
  93. package/dist/charts/line-chart/index.d.ts +0 -98
  94. package/dist/charts/line-chart/index.js +0 -17
  95. package/dist/charts/line-chart/index.js.map +0 -1
  96. package/dist/charts/pie-chart/index.cjs +0 -19
  97. package/dist/charts/pie-chart/index.cjs.map +0 -1
  98. package/dist/charts/pie-chart/index.css +0 -131
  99. package/dist/charts/pie-chart/index.css.map +0 -1
  100. package/dist/charts/pie-chart/index.d.cts +0 -91
  101. package/dist/charts/pie-chart/index.d.ts +0 -91
  102. package/dist/charts/pie-chart/index.js +0 -19
  103. package/dist/charts/pie-chart/index.js.map +0 -1
  104. package/dist/charts/pie-semi-circle-chart/index.cjs +0 -18
  105. package/dist/charts/pie-semi-circle-chart/index.cjs.map +0 -1
  106. package/dist/charts/pie-semi-circle-chart/index.css +0 -132
  107. package/dist/charts/pie-semi-circle-chart/index.css.map +0 -1
  108. package/dist/charts/pie-semi-circle-chart/index.d.cts +0 -88
  109. package/dist/charts/pie-semi-circle-chart/index.d.ts +0 -88
  110. package/dist/charts/pie-semi-circle-chart/index.js +0 -18
  111. package/dist/charts/pie-semi-circle-chart/index.js.map +0 -1
  112. package/dist/charts/sparkline/index.cjs +0 -18
  113. package/dist/charts/sparkline/index.cjs.map +0 -1
  114. package/dist/charts/sparkline/index.css +0 -230
  115. package/dist/charts/sparkline/index.css.map +0 -1
  116. package/dist/charts/sparkline/index.d.cts +0 -113
  117. package/dist/charts/sparkline/index.d.ts +0 -113
  118. package/dist/charts/sparkline/index.js +0 -18
  119. package/dist/charts/sparkline/index.js.map +0 -1
  120. package/dist/chunk-2A34OA5O.cjs +0 -51
  121. package/dist/chunk-2A34OA5O.cjs.map +0 -1
  122. package/dist/chunk-2I67QUIV.js +0 -895
  123. package/dist/chunk-2I67QUIV.js.map +0 -1
  124. package/dist/chunk-2ICEEQOC.js +0 -1071
  125. package/dist/chunk-2ICEEQOC.js.map +0 -1
  126. package/dist/chunk-4B7BL2DD.js +0 -120
  127. package/dist/chunk-4B7BL2DD.js.map +0 -1
  128. package/dist/chunk-4OXMTKAL.js +0 -401
  129. package/dist/chunk-4OXMTKAL.js.map +0 -1
  130. package/dist/chunk-ASLARV7L.cjs +0 -81
  131. package/dist/chunk-ASLARV7L.cjs.map +0 -1
  132. package/dist/chunk-B6NLZFRW.js +0 -617
  133. package/dist/chunk-B6NLZFRW.js.map +0 -1
  134. package/dist/chunk-BBAUQOW6.cjs +0 -120
  135. package/dist/chunk-BBAUQOW6.cjs.map +0 -1
  136. package/dist/chunk-BPYKWMI7.js +0 -194
  137. package/dist/chunk-BPYKWMI7.js.map +0 -1
  138. package/dist/chunk-CMMHCTBX.cjs +0 -373
  139. package/dist/chunk-CMMHCTBX.cjs.map +0 -1
  140. package/dist/chunk-CPPXJATQ.cjs +0 -1071
  141. package/dist/chunk-CPPXJATQ.cjs.map +0 -1
  142. package/dist/chunk-DKU775VC.js +0 -219
  143. package/dist/chunk-DKU775VC.js.map +0 -1
  144. package/dist/chunk-GRA7Y2ZG.cjs +0 -401
  145. package/dist/chunk-GRA7Y2ZG.cjs.map +0 -1
  146. package/dist/chunk-I2276W3I.cjs +0 -66
  147. package/dist/chunk-I2276W3I.cjs.map +0 -1
  148. package/dist/chunk-JJIMABHT.js +0 -351
  149. package/dist/chunk-JJIMABHT.js.map +0 -1
  150. package/dist/chunk-KJHWXOCZ.js +0 -421
  151. package/dist/chunk-KJHWXOCZ.js.map +0 -1
  152. package/dist/chunk-KRWGSOJ2.js +0 -91
  153. package/dist/chunk-KRWGSOJ2.js.map +0 -1
  154. package/dist/chunk-KXRWNFQJ.js +0 -51
  155. package/dist/chunk-KXRWNFQJ.js.map +0 -1
  156. package/dist/chunk-LTFH7SEG.js +0 -373
  157. package/dist/chunk-LTFH7SEG.js.map +0 -1
  158. package/dist/chunk-MUNOKLLE.js +0 -165
  159. package/dist/chunk-MUNOKLLE.js.map +0 -1
  160. package/dist/chunk-MXGLYWVP.cjs +0 -351
  161. package/dist/chunk-MXGLYWVP.cjs.map +0 -1
  162. package/dist/chunk-OP6PHB2U.js +0 -81
  163. package/dist/chunk-OP6PHB2U.js.map +0 -1
  164. package/dist/chunk-OYC34VTO.cjs +0 -3957
  165. package/dist/chunk-OYC34VTO.cjs.map +0 -1
  166. package/dist/chunk-PQL5I3F6.cjs +0 -421
  167. package/dist/chunk-PQL5I3F6.cjs.map +0 -1
  168. package/dist/chunk-REZTQ4PH.cjs +0 -488
  169. package/dist/chunk-REZTQ4PH.cjs.map +0 -1
  170. package/dist/chunk-TZRUHQOH.cjs +0 -91
  171. package/dist/chunk-TZRUHQOH.cjs.map +0 -1
  172. package/dist/chunk-UTYVIOWZ.js +0 -3957
  173. package/dist/chunk-UTYVIOWZ.js.map +0 -1
  174. package/dist/chunk-W2LDIX26.cjs +0 -165
  175. package/dist/chunk-W2LDIX26.cjs.map +0 -1
  176. package/dist/chunk-WSG64BVN.cjs +0 -219
  177. package/dist/chunk-WSG64BVN.cjs.map +0 -1
  178. package/dist/chunk-WTQYGUNF.js +0 -400
  179. package/dist/chunk-WTQYGUNF.js.map +0 -1
  180. package/dist/chunk-WYK7EL5R.cjs +0 -895
  181. package/dist/chunk-WYK7EL5R.cjs.map +0 -1
  182. package/dist/chunk-XC4KHJYX.cjs +0 -617
  183. package/dist/chunk-XC4KHJYX.cjs.map +0 -1
  184. package/dist/chunk-XVBH5XHE.cjs +0 -400
  185. package/dist/chunk-XVBH5XHE.cjs.map +0 -1
  186. package/dist/chunk-XWYZIFZW.js +0 -66
  187. package/dist/chunk-XWYZIFZW.js.map +0 -1
  188. package/dist/chunk-Y3NNQMAX.cjs +0 -194
  189. package/dist/chunk-Y3NNQMAX.cjs.map +0 -1
  190. package/dist/chunk-YAFQVVDI.js +0 -488
  191. package/dist/chunk-YAFQVVDI.js.map +0 -1
  192. package/dist/components/legend/index.cjs +0 -12
  193. package/dist/components/legend/index.cjs.map +0 -1
  194. package/dist/components/legend/index.css +0 -91
  195. package/dist/components/legend/index.css.map +0 -1
  196. package/dist/components/legend/index.d.cts +0 -37
  197. package/dist/components/legend/index.d.ts +0 -37
  198. package/dist/components/legend/index.js +0 -12
  199. package/dist/components/legend/index.js.map +0 -1
  200. package/dist/components/tooltip/index.cjs +0 -12
  201. package/dist/components/tooltip/index.cjs.map +0 -1
  202. package/dist/components/tooltip/index.css +0 -13
  203. package/dist/components/tooltip/index.css.map +0 -1
  204. package/dist/components/tooltip/index.d.cts +0 -71
  205. package/dist/components/tooltip/index.d.ts +0 -71
  206. package/dist/components/tooltip/index.js +0 -12
  207. package/dist/components/tooltip/index.js.map +0 -1
  208. package/dist/components/trend-indicator/index.cjs +0 -8
  209. package/dist/components/trend-indicator/index.cjs.map +0 -1
  210. package/dist/components/trend-indicator/index.css +0 -27
  211. package/dist/components/trend-indicator/index.css.map +0 -1
  212. package/dist/components/trend-indicator/index.d.cts +0 -44
  213. package/dist/components/trend-indicator/index.d.ts +0 -44
  214. package/dist/components/trend-indicator/index.js +0 -8
  215. package/dist/components/trend-indicator/index.js.map +0 -1
  216. package/dist/format-metric-value-MXm5DtQ_.d.cts +0 -24
  217. package/dist/format-metric-value-MXm5DtQ_.d.ts +0 -24
  218. package/dist/hooks/index.cjs +0 -29
  219. package/dist/hooks/index.cjs.map +0 -1
  220. package/dist/hooks/index.css +0 -9
  221. package/dist/hooks/index.css.map +0 -1
  222. package/dist/hooks/index.d.cts +0 -264
  223. package/dist/hooks/index.d.ts +0 -264
  224. package/dist/hooks/index.js +0 -29
  225. package/dist/hooks/index.js.map +0 -1
  226. package/dist/leaderboard-chart-BSbg0ufV.d.cts +0 -79
  227. package/dist/leaderboard-chart-odEYxxEC.d.ts +0 -79
  228. package/dist/legend-DFkosEvC.d.cts +0 -9
  229. package/dist/legend-DLswHhOk.d.ts +0 -9
  230. package/dist/providers/index.cjs +0 -21
  231. package/dist/providers/index.cjs.map +0 -1
  232. package/dist/providers/index.css +0 -9
  233. package/dist/providers/index.css.map +0 -1
  234. package/dist/providers/index.d.cts +0 -28
  235. package/dist/providers/index.d.ts +0 -28
  236. package/dist/providers/index.js +0 -21
  237. package/dist/providers/index.js.map +0 -1
  238. package/dist/themes-D0qc5JaW.d.cts +0 -67
  239. package/dist/themes-itO4Ht5g.d.ts +0 -67
  240. package/dist/types-B5f6XQ7Q.d.cts +0 -19
  241. package/dist/types-BsHooDbM.d.ts +0 -19
  242. package/dist/types-BuSrRM4p.d.ts +0 -49
  243. package/dist/types-ChOUI9-N.d.cts +0 -545
  244. package/dist/types-ChOUI9-N.d.ts +0 -545
  245. package/dist/types-Dfw9VOKI.d.cts +0 -49
  246. package/dist/utils/index.cjs +0 -44
  247. package/dist/utils/index.cjs.map +0 -1
  248. package/dist/utils/index.d.cts +0 -226
  249. package/dist/utils/index.d.ts +0 -226
  250. package/dist/utils/index.js +0 -44
  251. package/dist/utils/index.js.map +0 -1
  252. package/dist/with-responsive-CNfhzAUu.d.cts +0 -18
  253. package/dist/with-responsive-CNfhzAUu.d.ts +0 -18
@@ -7,7 +7,11 @@ import clsx from 'clsx';
7
7
  import { useCallback, useContext, useMemo } from 'react';
8
8
  import { Legend, useChartLegendItems } from '../../components/legend';
9
9
  import { BaseTooltip } from '../../components/tooltip';
10
- import { useElementSize, useInteractiveLegendData, usePrefersReducedMotion } from '../../hooks';
10
+ import {
11
+ useDataWithPercentages,
12
+ useInteractiveLegendData,
13
+ usePrefersReducedMotion,
14
+ } from '../../hooks';
11
15
  import {
12
16
  GlobalChartsProvider,
13
17
  useChartId,
@@ -18,18 +22,20 @@ import {
18
22
  } from '../../providers';
19
23
  import { attachSubComponents } from '../../utils';
20
24
  import { getStringWidth } from '../../visx/text';
21
- import {
22
- ChartSVG,
23
- ChartHTML,
24
- useChartChildren,
25
- renderLegendSlot,
26
- } from '../private/chart-composition';
25
+ import { ChartSVG, ChartHTML, useChartChildren } from '../private/chart-composition';
26
+ import { ChartLayout } from '../private/chart-layout';
27
27
  import { RadialWipeAnimation } from '../private/radial-wipe-animation/';
28
28
  import { SingleChartContext } from '../private/single-chart-context';
29
+ import { SvgEmptyState } from '../private/svg-empty-state';
29
30
  import { withResponsive, ResponsiveConfig } from '../private/with-responsive';
30
31
  import styles from './pie-chart.module.scss';
31
32
  import type { LegendValueDisplay } from '../../components/legend';
32
- import type { BaseChartProps, DataPointPercentage, Optional } from '../../types';
33
+ import type {
34
+ BaseChartProps,
35
+ DataPointPercentage,
36
+ DataPointPercentageCalculated,
37
+ Optional,
38
+ } from '../../types';
33
39
  import type { ChartComponentWithComposition } from '../private/chart-composition';
34
40
  import type { SVGProps, MouseEvent, ReactNode, FC } from 'react';
35
41
 
@@ -38,9 +44,9 @@ import type { SVGProps, MouseEvent, ReactNode, FC } from 'react';
38
44
  */
39
45
  export type PieChartRenderTooltipParams = {
40
46
  /**
41
- * The data point being hovered, including label, value, and percentage.
47
+ * The data point being hovered, including label, value, and calculated percentage.
42
48
  */
43
- tooltipData: DataPointPercentage;
49
+ tooltipData: DataPointPercentageCalculated;
44
50
  };
45
51
 
46
52
  /**
@@ -140,16 +146,15 @@ const validateData = ( data: DataPointPercentage[] ) => {
140
146
  }
141
147
 
142
148
  // Check for negative values
143
- const hasNegativeValues = data.some( item => item.percentage < 0 || item.value < 0 );
149
+ const hasNegativeValues = data.some( item => item.value < 0 );
144
150
  if ( hasNegativeValues ) {
145
151
  return { isValid: false, message: 'Invalid data: Negative values are not allowed' };
146
152
  }
147
153
 
148
- // Validate total percentage
149
- const totalPercentage = data.reduce( ( sum, item ) => sum + item.percentage, 0 );
150
- if ( Math.abs( totalPercentage - 100 ) > 0.01 ) {
151
- // Using small epsilon for floating point comparison
152
- return { isValid: false, message: 'Invalid percentage total: Must equal 100' };
154
+ // Validate total value is greater than 0
155
+ const totalValue = data.reduce( ( sum, item ) => sum + item.value, 0 );
156
+ if ( totalValue <= 0 ) {
157
+ return { isValid: false, message: 'Invalid data: Total value must be greater than 0' };
153
158
  }
154
159
 
155
160
  return { isValid: true, message: '' };
@@ -189,9 +194,8 @@ const PieChartInternal = ( {
189
194
 
190
195
  const providerTheme = useGlobalChartsTheme();
191
196
  const chartId = useChartId( providedChartId );
192
- const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
193
197
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
194
- useTooltip< DataPointPercentage >();
198
+ useTooltip< DataPointPercentageCalculated >();
195
199
 
196
200
  // Set up portal tooltip for better z-index handling
197
201
  // We get containerBounds to cancel out stale offsets in the position calculation
@@ -210,9 +214,12 @@ const PieChartInternal = ( {
210
214
 
211
215
  const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
212
216
 
217
+ // Calculate percentages from values (single source of truth)
218
+ const dataWithPercentages = useDataWithPercentages( data );
219
+
213
220
  // Filter and recalculate data for interactive legends
214
221
  const { visibleData, allSegmentsHidden, legendData } = useInteractiveLegendData( {
215
- data,
222
+ data: dataWithPercentages,
216
223
  chartId,
217
224
  legendInteractive,
218
225
  isSeriesVisible,
@@ -264,34 +271,9 @@ const PieChartInternal = ( {
264
271
  );
265
272
  }
266
273
 
267
- // Calculate the actual pie size:
268
- // - Measure available space from the svg-wrapper
269
- // - If size prop provided: use it as max, but shrink if container is smaller
270
- // - If no size prop: fill available space
271
- const availableWidth = svgWrapperWidth > 0 ? svgWrapperWidth : 300;
272
- const availableHeight = svgWrapperHeight > 0 ? svgWrapperHeight : 300;
273
- const availableSize = Math.min( availableWidth, availableHeight );
274
- const actualSize = size ? Math.min( size, availableSize ) : availableSize;
275
-
276
- const width = actualSize;
277
- const height = actualSize;
278
-
279
- // Calculate radius based on width/height
280
- const radius = Math.min( width, height ) / 2;
281
-
282
- // Center the chart in the available space
283
- const centerX = width / 2;
284
- const centerY = height / 2;
285
-
286
274
  // Calculate the angle between each (use original data length for consistent spacing)
287
275
  const padAngle = gapScale * ( ( 2 * Math.PI ) / data.length );
288
276
 
289
- const outerRadius = radius - padding;
290
- const innerRadius = thickness === 0 ? 0 : outerRadius * ( 1 - thickness );
291
-
292
- const maxCornerRadius = ( outerRadius - innerRadius ) / 2;
293
- const cornerRadius = cornerScale ? Math.min( cornerScale * outerRadius, maxCornerRadius ) : 0;
294
-
295
277
  // Map the data to include index for color assignment
296
278
  // When interactive, we need to find the original index to maintain consistent colors
297
279
  const dataWithIndex = visibleData.map( d => {
@@ -303,8 +285,8 @@ const PieChartInternal = ( {
303
285
  } );
304
286
 
305
287
  const accessors = {
306
- value: ( d: DataPointPercentage ) => d.value,
307
- fill: ( d: DataPointPercentage & { index: number } ) => {
288
+ value: ( d: DataPointPercentageCalculated ) => d.value,
289
+ fill: ( d: DataPointPercentageCalculated & { index: number } ) => {
308
290
  return getElementStyles( { data: d, index: d.index } ).color;
309
291
  },
310
292
  };
@@ -325,16 +307,11 @@ const PieChartInternal = ( {
325
307
  );
326
308
 
327
309
  return (
328
- <SingleChartContext.Provider
329
- value={ {
330
- chartId,
331
- chartWidth: width,
332
- chartHeight: height,
333
- } }
334
- >
335
- <Stack
336
- ref={ containerRef }
337
- direction="column"
310
+ <SingleChartContext.Provider value={ { chartId } }>
311
+ <ChartLayout
312
+ legendPosition={ legendPosition }
313
+ legendElement={ legendElement }
314
+ legendChildren={ legendChildren }
338
315
  gap={ gap }
339
316
  className={ clsx(
340
317
  'pie-chart',
@@ -347,155 +324,177 @@ const PieChartInternal = ( {
347
324
  width: propWidth || undefined,
348
325
  height: propHeight || undefined,
349
326
  } }
327
+ trailingContent={
328
+ <>
329
+ { withTooltips && tooltipOpen && tooltipData && (
330
+ <TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
331
+ <div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
332
+ </TooltipInPortal>
333
+ ) }
334
+ { htmlChildren }
335
+ { otherChildren }
336
+ </>
337
+ }
350
338
  >
351
- { legendPosition === 'top' && legendElement }
352
- { renderLegendSlot( legendChildren, 'top' ) }
353
-
354
- <div className={ styles[ 'pie-chart__svg-wrapper' ] } ref={ svgWrapperRef }>
355
- <svg
356
- viewBox={ `0 0 ${ width } ${ height }` }
357
- preserveAspectRatio="xMidYMid meet"
358
- width={ width }
359
- height={ height }
360
- >
361
- <defs>
362
- <RadialWipeAnimation
363
- id={ `radial-wipe-${ chartId }` }
364
- radius={ outerRadius }
365
- innerRadius={ innerRadius }
366
- />
367
- </defs>
368
-
369
- <Group
370
- top={ centerY }
371
- left={ centerX }
372
- mask={ animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null }
339
+ { ( { contentWidth, contentHeight } ) => {
340
+ const availableWidth = contentWidth > 0 ? contentWidth : 300;
341
+ const availableHeight = contentHeight > 0 ? contentHeight : 300;
342
+ const availableSize = Math.min( availableWidth, availableHeight );
343
+ const actualSize = size ? Math.min( size, availableSize ) : availableSize;
344
+
345
+ const width = actualSize;
346
+ const height = actualSize;
347
+
348
+ const radius = Math.min( width, height ) / 2;
349
+ const centerX = width / 2;
350
+ const centerY = height / 2;
351
+
352
+ const outerRadius = radius - padding;
353
+ const innerRadius = thickness === 0 ? 0 : outerRadius * ( 1 - thickness );
354
+
355
+ const maxCornerRadius = ( outerRadius - innerRadius ) / 2;
356
+ const cornerRadius = cornerScale
357
+ ? Math.min( cornerScale * outerRadius, maxCornerRadius )
358
+ : 0;
359
+
360
+ return (
361
+ <Stack
362
+ ref={ containerRef }
363
+ align="center"
364
+ justify="center"
365
+ className={ styles[ 'pie-chart__centering' ] }
373
366
  >
374
- { allSegmentsHidden ? (
375
- <text
376
- textAnchor="middle"
377
- dy=".33em"
378
- fill={ providerTheme.gridColor || '#ccc' }
379
- fontSize="14"
380
- fontFamily="-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif"
367
+ <svg
368
+ viewBox={ `0 0 ${ width } ${ height }` }
369
+ preserveAspectRatio="xMidYMid meet"
370
+ width={ width }
371
+ height={ height }
372
+ >
373
+ <defs>
374
+ <RadialWipeAnimation
375
+ id={ `radial-wipe-${ chartId }` }
376
+ radius={ outerRadius }
377
+ innerRadius={ innerRadius }
378
+ />
379
+ </defs>
380
+
381
+ <Group
382
+ top={ centerY }
383
+ left={ centerX }
384
+ mask={
385
+ animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null
386
+ }
381
387
  >
382
- { __(
383
- 'All segments are hidden. Click legend items to show data.',
384
- 'jetpack-charts'
385
- ) }
386
- </text>
387
- ) : (
388
- <Pie< DataPointPercentage & { index: number } >
389
- data={ dataWithIndex }
390
- pieValue={ accessors.value }
391
- outerRadius={ outerRadius }
392
- innerRadius={ innerRadius }
393
- padAngle={ padAngle }
394
- cornerRadius={ cornerRadius }
395
- >
396
- { pie => {
397
- return pie.arcs.map( ( arc, index ) => {
398
- const [ centroidX, centroidY ] = pie.path.centroid( arc );
399
- const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.25;
400
- const handleMouseMove = ( event: MouseEvent< SVGElement > ) => {
401
- if ( ! withTooltips ) {
402
- return;
403
- }
404
-
405
- // Don't show tooltip until container bounds are measured
406
- if ( containerBounds.width === 0 || containerBounds.height === 0 ) {
407
- return;
408
- }
409
-
410
- // Use clientX/Y and subtract containerBounds to cancel out any stale offset.
411
- // TooltipInPortal calculates: tooltipLeft + containerBounds.left + scrollX
412
- // By passing (clientX - containerBounds.left), we get:
413
- // (clientX - containerBounds.left) + containerBounds.left + scrollX = clientX + scrollX
414
- // This gives correct page coordinates regardless of stale bounds.
415
- showTooltip( {
416
- tooltipData: arc.data,
417
- tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
418
- tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY,
419
- } );
420
- };
421
-
422
- const pathProps: SVGProps< SVGPathElement > & { 'data-testid'?: string } = {
423
- d: pie.path( arc ) || '',
424
- fill: accessors.fill( arc.data ),
425
- 'data-testid': 'pie-segment',
426
- };
427
-
428
- const groupProps: SVGProps< SVGGElement > = {};
429
- if ( withTooltips ) {
430
- groupProps.onMouseMove = handleMouseMove;
431
- groupProps.onMouseLeave = onMouseLeave;
432
- }
433
-
434
- // Estimate text width more accurately for background sizing
435
- const fontSize = 12;
436
- const estimatedTextWidth = getStringWidth( arc.data.label, { fontSize } );
437
- const labelPadding = 6;
438
- const backgroundWidth = estimatedTextWidth + labelPadding * 2;
439
- const backgroundHeight = fontSize + labelPadding * 2;
440
-
441
- return (
442
- <g key={ `arc-${ index }` } { ...groupProps }>
443
- <path { ...pathProps } />
444
- { showLabels && hasSpaceForLabel && (
445
- <g>
446
- { providerTheme.labelBackgroundColor && (
447
- <rect
448
- x={ centroidX - backgroundWidth / 2 }
449
- y={ centroidY - backgroundHeight / 2 }
450
- width={ backgroundWidth }
451
- height={ backgroundHeight }
452
- fill={ providerTheme.labelBackgroundColor }
453
- rx={ 4 }
454
- ry={ 4 }
455
- pointerEvents="none"
456
- />
388
+ { allSegmentsHidden ? (
389
+ <SvgEmptyState x={ 0 } y={ 0 } width={ width } height={ height }>
390
+ { __(
391
+ 'All segments are hidden. Click legend items to show data.',
392
+ 'jetpack-charts'
393
+ ) }
394
+ </SvgEmptyState>
395
+ ) : (
396
+ <Pie< DataPointPercentageCalculated & { index: number } >
397
+ data={ dataWithIndex }
398
+ pieValue={ accessors.value }
399
+ outerRadius={ outerRadius }
400
+ innerRadius={ innerRadius }
401
+ padAngle={ padAngle }
402
+ cornerRadius={ cornerRadius }
403
+ >
404
+ { pie => {
405
+ return pie.arcs.map( ( arc, index ) => {
406
+ const [ centroidX, centroidY ] = pie.path.centroid( arc );
407
+ const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.25;
408
+ const handleMouseMove = ( event: MouseEvent< SVGElement > ) => {
409
+ if ( ! withTooltips ) {
410
+ return;
411
+ }
412
+
413
+ // Don't show tooltip until container bounds are measured
414
+ if ( containerBounds.width === 0 || containerBounds.height === 0 ) {
415
+ return;
416
+ }
417
+
418
+ // Use clientX/Y and subtract containerBounds to cancel out any stale offset.
419
+ // TooltipInPortal calculates: tooltipLeft + containerBounds.left + scrollX
420
+ // By passing (clientX - containerBounds.left), we get:
421
+ // (clientX - containerBounds.left) + containerBounds.left + scrollX = clientX + scrollX
422
+ // This gives correct page coordinates regardless of stale bounds.
423
+ showTooltip( {
424
+ tooltipData: arc.data,
425
+ tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
426
+ tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY,
427
+ } );
428
+ };
429
+
430
+ const pathProps: SVGProps< SVGPathElement > & {
431
+ 'data-testid'?: string;
432
+ } = {
433
+ d: pie.path( arc ) || '',
434
+ fill: accessors.fill( arc.data ),
435
+ 'data-testid': 'pie-segment',
436
+ };
437
+
438
+ const groupProps: SVGProps< SVGGElement > = {};
439
+ if ( withTooltips ) {
440
+ groupProps.onMouseMove = handleMouseMove;
441
+ groupProps.onMouseLeave = onMouseLeave;
442
+ }
443
+
444
+ // Estimate text width more accurately for background sizing
445
+ const fontSize = 12;
446
+ const estimatedTextWidth = getStringWidth( arc.data.label, {
447
+ fontSize,
448
+ } );
449
+ const labelPadding = 6;
450
+ const backgroundWidth = estimatedTextWidth + labelPadding * 2;
451
+ const backgroundHeight = fontSize + labelPadding * 2;
452
+
453
+ return (
454
+ <g key={ `arc-${ index }` } { ...groupProps }>
455
+ <path { ...pathProps } />
456
+ { showLabels && hasSpaceForLabel && (
457
+ <g>
458
+ { providerTheme.labelBackgroundColor && (
459
+ <rect
460
+ x={ centroidX - backgroundWidth / 2 }
461
+ y={ centroidY - backgroundHeight / 2 }
462
+ width={ backgroundWidth }
463
+ height={ backgroundHeight }
464
+ fill={ providerTheme.labelBackgroundColor }
465
+ rx={ 4 }
466
+ ry={ 4 }
467
+ pointerEvents="none"
468
+ />
469
+ ) }
470
+ <text
471
+ x={ centroidX }
472
+ y={ centroidY }
473
+ dy=".33em"
474
+ fill={ providerTheme.labelTextColor || '#333' }
475
+ fontSize={ fontSize }
476
+ textAnchor="middle"
477
+ pointerEvents="none"
478
+ >
479
+ { arc.data.label }
480
+ </text>
481
+ </g>
457
482
  ) }
458
- <text
459
- x={ centroidX }
460
- y={ centroidY }
461
- dy=".33em"
462
- fill={ providerTheme.labelTextColor || '#333' }
463
- fontSize={ fontSize }
464
- textAnchor="middle"
465
- pointerEvents="none"
466
- >
467
- { arc.data.label }
468
- </text>
469
483
  </g>
470
- ) }
471
- </g>
472
- );
473
- } );
474
- } }
475
- </Pie>
476
- ) }
477
-
478
- { /* Render SVG children (like Group, Text) inside the SVG */ }
479
- { ! allSegmentsHidden && svgChildren }
480
- </Group>
481
- </svg>
482
- </div>
483
-
484
- { legendPosition === 'bottom' && legendElement }
485
- { renderLegendSlot( legendChildren, 'bottom' ) }
486
-
487
- { withTooltips && tooltipOpen && tooltipData && (
488
- <TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
489
- <div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
490
- </TooltipInPortal>
491
- ) }
492
-
493
- { /* Render HTML component children from PieChart.HTML */ }
494
- { htmlChildren }
484
+ );
485
+ } );
486
+ } }
487
+ </Pie>
488
+ ) }
495
489
 
496
- { /* Render other React children for backward compatibility */ }
497
- { otherChildren }
498
- </Stack>
490
+ { /* Render SVG children (like Group, Text) inside the SVG */ }
491
+ { ! allSegmentsHidden && svgChildren }
492
+ </Group>
493
+ </svg>
494
+ </Stack>
495
+ );
496
+ } }
497
+ </ChartLayout>
499
498
  </SingleChartContext.Provider>
500
499
  );
501
500
  };
@@ -6,9 +6,9 @@ import { PieChartUnresponsive as PieChart } from '../index';
6
6
 
7
7
  describe( 'PieChart Composition API', () => {
8
8
  const mockData = [
9
- { label: 'A', value: 30, percentage: 30 },
10
- { label: 'B', value: 40, percentage: 40 },
11
- { label: 'C', value: 30, percentage: 30 },
9
+ { label: 'A', value: 30 },
10
+ { label: 'B', value: 40 },
11
+ { label: 'C', value: 30 },
12
12
  ];
13
13
 
14
14
  const renderWithChildren = ( props = {}, children = undefined ) => {