@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
@@ -3,9 +3,21 @@ import { SingleChartContext } from '../../charts/private/single-chart-context';
3
3
  import { GlobalChartsContext } from '../../providers';
4
4
  import { BaseLegend } from './private';
5
5
  import type { LegendProps } from './types';
6
+ import type { ChartType } from '../../types';
7
+ import type { LegendShape } from '@visx/legend/lib/types';
8
+
9
+ const defaultShapeByChartType: Partial<
10
+ Record< ChartType, Extract< LegendShape< unknown, unknown >, string > >
11
+ > = {
12
+ line: 'line',
13
+ bar: 'rect',
14
+ pie: 'circle',
15
+ 'pie-semi-circle': 'circle',
16
+ leaderboard: 'circle',
17
+ };
6
18
 
7
19
  export const Legend = forwardRef< HTMLDivElement, LegendProps >(
8
- ( { chartId, items, ...props }, ref ) => {
20
+ ( { chartId, items, shape, ...props }, ref ) => {
9
21
  // Get context but don't throw if it doesn't exist
10
22
  const context = useContext( GlobalChartsContext );
11
23
  const singleChartContext = useContext( SingleChartContext );
@@ -14,12 +26,17 @@ export const Legend = forwardRef< HTMLDivElement, LegendProps >(
14
26
  // When chartId is not provided, we use the context's chartId, meaning it is in a single chart context
15
27
  const contextChartId = chartId ?? singleChartContext?.chartId;
16
28
 
17
- // Use useMemo to ensure re-rendering when context changes
18
- const contextItems = useMemo( () => {
19
- return contextChartId && context
20
- ? context.getChartData( contextChartId )?.legendItems
21
- : undefined;
22
- }, [ contextChartId, context ] );
29
+ const chartData = useMemo(
30
+ () => ( contextChartId && context ? context.getChartData( contextChartId ) : undefined ),
31
+ [ contextChartId, context ]
32
+ );
33
+
34
+ const contextItems = chartData?.legendItems;
35
+
36
+ // Derive the default legend shape from the chart type when no explicit shape is provided
37
+ const resolvedShape =
38
+ shape ??
39
+ ( chartData?.chartType ? defaultShapeByChartType[ chartData.chartType ] : undefined );
23
40
 
24
41
  // Provided items take precedence over context items
25
42
  const legendItems = ( items || contextItems ) as typeof items;
@@ -28,6 +45,14 @@ export const Legend = forwardRef< HTMLDivElement, LegendProps >(
28
45
  return null;
29
46
  }
30
47
 
31
- return <BaseLegend ref={ ref } items={ legendItems } { ...props } chartId={ contextChartId } />;
48
+ return (
49
+ <BaseLegend
50
+ ref={ ref }
51
+ items={ legendItems }
52
+ shape={ resolvedShape }
53
+ { ...props }
54
+ chartId={ contextChartId }
55
+ />
56
+ );
32
57
  }
33
58
  );
@@ -1,8 +1,12 @@
1
1
  /* eslint-disable react/jsx-no-bind */
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
- import { GlobalChartsProvider } from '../../../providers';
4
+ import { useMemo } from 'react';
5
+ import { SingleChartContext } from '../../../charts/private/single-chart-context';
6
+ import { GlobalChartsProvider, useChartId, useChartRegistration } from '../../../providers';
7
+ import { Legend } from '../legend';
5
8
  import { BaseLegend } from '../private/base-legend';
9
+ import type { ChartType } from '../../../types';
6
10
  import type { LegendProps } from '../types';
7
11
 
8
12
  const TestShape: LegendProps[ 'shape' ] = props => {
@@ -425,6 +429,94 @@ describe( 'BaseLegend', () => {
425
429
  } );
426
430
  } );
427
431
 
432
+ describe( 'Legend shape defaults from chart type', () => {
433
+ const legendItems = [
434
+ { label: 'Series 1', color: '#ff0000' },
435
+ { label: 'Series 2', color: '#00ff00' },
436
+ ];
437
+
438
+ const CustomShape: LegendProps[ 'shape' ] = props => (
439
+ <span data-testid="custom-shape" style={ { color: props.fill as string } } />
440
+ );
441
+
442
+ const ChartRegistrar = ( {
443
+ chartType,
444
+ chartId,
445
+ }: {
446
+ chartType: ChartType;
447
+ chartId: string;
448
+ } ) => {
449
+ const resolvedId = useChartId( chartId );
450
+ const metadata = useMemo( () => ( {} ), [] );
451
+ useChartRegistration( {
452
+ chartId: resolvedId,
453
+ legendItems,
454
+ chartType,
455
+ isDataValid: true,
456
+ metadata,
457
+ } );
458
+ return null;
459
+ };
460
+
461
+ const renderLegendWithChartType = (
462
+ chartType: ChartType,
463
+ explicitShape?: LegendProps[ 'shape' ]
464
+ ) => {
465
+ const chartId = `test-${ chartType }`;
466
+
467
+ return render(
468
+ <GlobalChartsProvider>
469
+ <ChartRegistrar chartType={ chartType } chartId={ chartId } />
470
+ <SingleChartContext.Provider value={ { chartId } }>
471
+ <Legend shape={ explicitShape } />
472
+ </SingleChartContext.Provider>
473
+ </GlobalChartsProvider>
474
+ );
475
+ };
476
+
477
+ it( 'uses line shape for line chart type', () => {
478
+ renderLegendWithChartType( 'line' );
479
+ expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
480
+ expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
481
+
482
+ const html = document.body.innerHTML;
483
+ expect( html ).toContain( '<line' );
484
+ } );
485
+
486
+ it( 'uses rect shape for bar chart type', () => {
487
+ renderLegendWithChartType( 'bar' );
488
+ expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
489
+
490
+ // visx ShapeRect renders a <div> with inline background style inside
491
+ // .visx-legend-shape. No testids or roles on these elements, so direct
492
+ // node access is necessary.
493
+ // eslint-disable-next-line testing-library/no-node-access
494
+ const shapes = document.querySelectorAll( '.visx-legend-shape > div' );
495
+ expect( shapes ).toHaveLength( 2 );
496
+ shapes.forEach( shape => {
497
+ expect( ( shape as HTMLElement ).style.background ).toBeTruthy();
498
+ } );
499
+ } );
500
+
501
+ it( 'uses circle shape for pie chart type', () => {
502
+ renderLegendWithChartType( 'pie' );
503
+ expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
504
+
505
+ const html = document.body.innerHTML;
506
+ expect( html ).toContain( '<circle' );
507
+ expect( html ).not.toContain( '<line' );
508
+ } );
509
+
510
+ it( 'allows explicit shape to override chart type default', () => {
511
+ renderLegendWithChartType( 'line', CustomShape );
512
+ expect( screen.getByRole( 'list' ) ).toBeInTheDocument();
513
+ expect( screen.getAllByTestId( 'custom-shape' ) ).toHaveLength( 2 );
514
+
515
+ const html = document.body.innerHTML;
516
+ expect( html ).not.toContain( '<line' );
517
+ } );
518
+ } );
519
+
428
520
  describe( 'Interactive legend', () => {
429
521
  it( 'renders interactive legend items with proper attributes', () => {
430
522
  render(
@@ -6,6 +6,7 @@ export { useChartMargin } from './use-chart-margin';
6
6
  export { useElementSize } from './use-element-size';
7
7
  export { useTextTruncation } from './use-text-truncation';
8
8
  export { useZeroValueDisplay } from './use-zero-value-display';
9
+ export { useDataWithPercentages } from './use-data-with-percentages';
9
10
  export { useInteractiveLegendData } from './use-interactive-legend-data';
10
11
  export { usePrefersReducedMotion } from './use-prefers-reduced-motion';
11
12
  export { useTooltipPortalRelocator } from './use-tooltip-portal-relocator';
@@ -0,0 +1,24 @@
1
+ import { useMemo } from 'react';
2
+
3
+ interface DataPointWithValue {
4
+ value: number;
5
+ }
6
+
7
+ /**
8
+ * Hook to calculate percentages from values for chart data.
9
+ * Ensures percentages are always derived from values (single source of truth).
10
+ *
11
+ * @param data - Array of data points with values
12
+ * @return Data with calculated percentages
13
+ */
14
+ export const useDataWithPercentages = < T extends DataPointWithValue >(
15
+ data: T[]
16
+ ): ( T & { percentage: number } )[] => {
17
+ return useMemo( () => {
18
+ const totalValue = data.reduce( ( sum, segment ) => sum + segment.value, 0 );
19
+ return data.map( segment => ( {
20
+ ...segment,
21
+ percentage: totalValue > 0 ? ( segment.value / totalValue ) * 100 : 0,
22
+ } ) );
23
+ }, [ data ] );
24
+ };
@@ -2,7 +2,8 @@ import { useMemo } from 'react';
2
2
 
3
3
  /**
4
4
  * Data point interface for charts with interactive legends.
5
- * Requires label for series identification, value for calculations, and percentage for display.
5
+ * Requires label for series identification, value for calculations,
6
+ * and percentage (should be pre-calculated by the chart component).
6
7
  */
7
8
  interface DataPointWithPercentage {
8
9
  label: string;
@@ -14,7 +15,7 @@ interface DataPointWithPercentage {
14
15
  * Parameters for the useInteractiveLegendData hook.
15
16
  */
16
17
  interface UseInteractiveLegendDataParams< T extends DataPointWithPercentage > {
17
- /** The chart data to filter based on legend visibility */
18
+ /** The chart data with pre-calculated percentages */
18
19
  data: T[];
19
20
  /** Unique chart identifier, required for interactive legends */
20
21
  chartId: string | undefined;
@@ -33,9 +34,9 @@ interface UseInteractiveLegendDataResult< T extends DataPointWithPercentage > {
33
34
  /** Boolean indicating if all segments are hidden */
34
35
  allSegmentsHidden: boolean;
35
36
  /**
36
- * Legend data with recalculated percentages for visible items.
37
- * Uses original data for hidden items, but shows recalculated percentages for visible ones.
38
- * This ensures the legend displays accurate percentages while maintaining all entries.
37
+ * Legend data with stable percentage formatting.
38
+ * Hidden items keep their original percentage.
39
+ * Visible items show recalculated percentages that total 100%.
39
40
  */
40
41
  legendData: T[];
41
42
  }
@@ -85,8 +86,9 @@ export const useInteractiveLegendData = < T extends DataPointWithPercentage >( {
85
86
  isSeriesVisible,
86
87
  }: UseInteractiveLegendDataParams< T > ): UseInteractiveLegendDataResult< T > => {
87
88
  // Filter and recalculate data for interactive legends
89
+ // Note: data should already have percentages calculated by the chart component
88
90
  const visibleData = useMemo( () => {
89
- // If interactive mode is disabled or no chartId, return all data unchanged
91
+ // If interactive mode is disabled or no chartId, return data as-is
90
92
  if ( ! chartId || ! legendInteractive ) {
91
93
  return data;
92
94
  }
@@ -99,9 +101,8 @@ export const useInteractiveLegendData = < T extends DataPointWithPercentage >( {
99
101
  return [];
100
102
  }
101
103
 
102
- // Recalculate percentages so visible segments total 100%
104
+ // Recalculate percentages from values so visible segments total 100%
103
105
  const totalValue = filtered.reduce( ( sum, segment ) => sum + segment.value, 0 );
104
-
105
106
  return filtered.map( segment => ( {
106
107
  ...segment,
107
108
  percentage: totalValue > 0 ? ( segment.value / totalValue ) * 100 : 0,
@@ -113,24 +114,26 @@ export const useInteractiveLegendData = < T extends DataPointWithPercentage >( {
113
114
  return legendInteractive && visibleData.length === 0;
114
115
  }, [ legendInteractive, visibleData ] );
115
116
 
116
- // Prepare legend data with recalculated percentages for visible items
117
- // This maintains all legend entries but shows updated percentages for visible segments
117
+ // Prepare legend data with percentages
118
+ // Hidden items keep their original percentage (calculated from all values)
119
+ // Visible items show recalculated percentages (totaling 100%)
118
120
  const legendData = useMemo( () => {
119
121
  if ( ! legendInteractive || ! chartId ) {
120
122
  return data;
121
123
  }
122
124
 
123
- // Map original data to show recalculated percentages for visible items
125
+ // Build a Map for O(1) lookups instead of O(n) find() calls
126
+ const visibleDataMap = new Map( visibleData.map( d => [ d.label, d ] ) );
127
+
124
128
  return data.map( segment => {
125
129
  const isVisible = isSeriesVisible( chartId, segment.label );
126
130
  if ( ! isVisible ) {
127
- // Return original data for hidden items
131
+ // Hidden items keep original percentage
128
132
  return segment;
129
133
  }
130
134
 
131
- // For visible items, find the recalculated percentage from visibleData
132
- const recalculated = visibleData.find( d => d.label === segment.label );
133
- return recalculated || segment;
135
+ // For visible items, get the recalculated percentage from visibleData
136
+ return visibleDataMap.get( segment.label ) || segment;
134
137
  } );
135
138
  }, [ data, visibleData, legendInteractive, chartId, isSeriesVisible ] );
136
139
 
package/src/index.ts CHANGED
@@ -28,8 +28,32 @@ export {
28
28
  defaultTheme,
29
29
  } from './providers';
30
30
 
31
- // Types
32
- export type * from './types';
31
+ // Types - explicit exports (excludes internal types like DataPointPercentageCalculated)
32
+ export type {
33
+ Optional,
34
+ OrientationType,
35
+ AnnotationStyles,
36
+ DataPoint,
37
+ GeoData,
38
+ DataPointDate,
39
+ LeaderboardEntry,
40
+ GradientStop,
41
+ SeriesDataOptions,
42
+ SeriesData,
43
+ MultipleDataPointsDate,
44
+ DataPointPercentage,
45
+ ChartTheme,
46
+ CompleteChartTheme,
47
+ AxisOptions,
48
+ ScaleOptions,
49
+ LegendItemStyles,
50
+ LegendLabelStyles,
51
+ LegendShapeStyles,
52
+ LegendPosition,
53
+ ChartLegendConfig,
54
+ BaseChartProps,
55
+ GridProps,
56
+ } from './types';
33
57
  export type * from './visx/types';
34
58
  export type { PieChartProps, PieChartRenderTooltipParams } from './charts/pie-chart';
35
59
  export type {
@@ -45,3 +69,42 @@ export type {
45
69
  GoogleDataTableRow,
46
70
  GoogleDataTableColumnRoleType,
47
71
  } from 'react-google-charts';
72
+
73
+ // Re-exports from removed individual entry points
74
+ export { useLeaderboardLegendItems } from './charts/leaderboard-chart/hooks';
75
+
76
+ // Previously available via '@automattic/charts/tooltip', '@automattic/charts/legend'
77
+ export { AccessibleTooltip } from './components/tooltip';
78
+ export type { BaseTooltipProps, TooltipData, TooltipProps } from './components/tooltip';
79
+ export type { LegendProps, BaseLegendProps, ChartLegendOptions } from './components/legend';
80
+
81
+ // Previously available via '@automattic/charts/bar-chart', '@automattic/charts/line-chart', etc.
82
+ export type { BarChartProps } from './charts/bar-chart';
83
+ export type {
84
+ BarListChartProps,
85
+ RenderLabelProps,
86
+ RenderValueProps,
87
+ } from './charts/bar-list-chart';
88
+ export type {
89
+ ConversionFunnelChartProps,
90
+ FunnelStep,
91
+ StepLabelRenderProps,
92
+ StepRateRenderProps,
93
+ MainMetricRenderProps,
94
+ TooltipRenderProps,
95
+ } from './charts/conversion-funnel-chart';
96
+ export type { LeaderboardChartProps } from './charts/leaderboard-chart';
97
+ export type {
98
+ LineChartProps,
99
+ LineChartAnnotationProps,
100
+ RenderLineGlyphProps,
101
+ TooltipDatum,
102
+ CurveType,
103
+ } from './charts/line-chart';
104
+ export type { ArcData } from './charts/pie-semi-circle-chart';
105
+ export type { SparklineProps, GradientConfig, SparklineDataPoint } from './charts/sparkline';
106
+
107
+ // Utilities
108
+ export { parseAsLocalDate, formatMetricValue, formatPercentage, mergeThemes } from './utils';
109
+ export * from './utils/color-utils';
110
+ export type { MetricValueType } from './utils';
@@ -200,7 +200,13 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
200
200
  const getElementStyles = useCallback< GlobalChartsContextValue[ 'getElementStyles' ] >(
201
201
  ( { data, index, overrideColor, legendShape } ) => {
202
202
  const isSeriesData = data && typeof data === 'object' && 'data' in data && 'options' in data;
203
- const isPointPercentageData = data && typeof data === 'object' && 'percentage' in data;
203
+ // DataPointPercentage has a numeric 'value' directly, unlike SeriesData which has 'data' array
204
+ const isPointPercentageData =
205
+ data &&
206
+ typeof data === 'object' &&
207
+ 'value' in data &&
208
+ typeof data.value === 'number' &&
209
+ ! ( 'data' in data );
204
210
 
205
211
  return {
206
212
  color: resolveColor( {
@@ -2,6 +2,7 @@ import { useEffect, useMemo } from 'react';
2
2
  import { useDeepMemo } from '../../../hooks';
3
3
  import { useGlobalChartsContext } from './use-global-charts-context';
4
4
  import type { BaseLegendItem } from '../../../components/legend';
5
+ import type { ChartType } from '../../../types';
5
6
 
6
7
  export const useChartRegistration = ( {
7
8
  chartId,
@@ -12,7 +13,7 @@ export const useChartRegistration = ( {
12
13
  }: {
13
14
  chartId: string;
14
15
  legendItems: BaseLegendItem[];
15
- chartType: string;
16
+ chartType: ChartType;
16
17
  isDataValid: boolean;
17
18
  metadata?: Record< string, unknown >;
18
19
  } ): void => {
@@ -1,12 +1,12 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
2
  import type { BaseLegendItem } from '../../components/legend';
3
- import type { CompleteChartTheme, DataPointPercentage, SeriesData } from '../../types';
3
+ import type { ChartType, CompleteChartTheme, DataPointPercentage, SeriesData } from '../../types';
4
4
  import type { LegendShape } from '@visx/legend/lib/types';
5
5
  import type { GlyphProps, LineStyles } from '@visx/xychart';
6
6
 
7
7
  export interface ChartRegistration {
8
8
  legendItems: BaseLegendItem[];
9
- chartType: string;
9
+ chartType: ChartType;
10
10
  metadata?: Record< string, unknown >;
11
11
  }
12
12
 
package/src/types.ts CHANGED
@@ -15,6 +15,14 @@ type ValueOf< T > = T[ keyof T ];
15
15
 
16
16
  export type Optional< T, K extends keyof T > = Pick< Partial< T >, K > & Omit< T, K >;
17
17
 
18
+ export type ChartType =
19
+ | 'bar'
20
+ | 'conversion-funnel'
21
+ | 'leaderboard'
22
+ | 'line'
23
+ | 'pie'
24
+ | 'pie-semi-circle';
25
+
18
26
  export type OrientationType = ValueOf< typeof Orientation >;
19
27
 
20
28
  export type AnnotationStyles = {
@@ -157,23 +165,24 @@ export type MultipleDataPointsDate = {
157
165
  data: DataPointDate[];
158
166
  };
159
167
 
168
+ /**
169
+ * Input data point for percentage-based charts (pie, donut, semi-circle).
170
+ * Provide values; percentages will be calculated automatically.
171
+ */
160
172
  export type DataPointPercentage = {
161
173
  /**
162
174
  * Label for the data point
163
175
  */
164
176
  label: string;
165
177
  /**
166
- * Numerical value
178
+ * Numerical value used for slice sizing.
179
+ * Percentages are calculated automatically from values.
167
180
  */
168
181
  value: number;
169
182
  /**
170
- * Formatted value for display
183
+ * Formatted value for display (e.g., "30K" instead of 30000)
171
184
  */
172
185
  valueDisplay?: string;
173
- /**
174
- * Percentage value
175
- */
176
- percentage: number;
177
186
  /**
178
187
  * Color code for the segment, by default colours are taken from the theme but this property can overrides it
179
188
  */
@@ -184,6 +193,17 @@ export type DataPointPercentage = {
184
193
  group?: string;
185
194
  };
186
195
 
196
+ /**
197
+ * Internal type with calculated percentage.
198
+ * Used internally after percentage calculation from values.
199
+ */
200
+ export type DataPointPercentageCalculated = DataPointPercentage & {
201
+ /**
202
+ * Calculated percentage (0-100) based on value relative to total
203
+ */
204
+ percentage: number;
205
+ };
206
+
187
207
  /**
188
208
  * Base theme configuration for chart components with optional properties
189
209
  */
@@ -296,7 +316,7 @@ export type CompleteChartTheme = Required< ChartTheme > & {
296
316
  };
297
317
  };
298
318
 
299
- declare type AxisOptions = {
319
+ export type AxisOptions = {
300
320
  orientation?: OrientationType;
301
321
  numTicks?: number;
302
322
  axisClassName?: string;
@@ -414,6 +414,7 @@ describe( 'resolveCssVariable', () => {
414
414
  if ( element === themedElement ) {
415
415
  return {
416
416
  getPropertyValue: ( prop: string ) => {
417
+ // eslint-disable-next-line @wordpress/no-unknown-ds-tokens -- Thinks this is a use rather than a test.
417
418
  if ( prop === '--wpds-color-bg-interactive-brand-weak' ) {
418
419
  return '#c029dc'; // User's custom accent color
419
420
  }
@@ -427,6 +428,7 @@ describe( 'resolveCssVariable', () => {
427
428
  } );
428
429
  window.getComputedStyle = mockGetComputedStyle as unknown as typeof window.getComputedStyle;
429
430
 
431
+ // eslint-disable-next-line @wordpress/no-unknown-ds-tokens -- Thinks this is a use rather than a test.
430
432
  const result = resolveCssVariable( '--wpds-color-bg-interactive-brand-weak', themedElement );
431
433
  expect( result ).toBe( '#c029dc' );
432
434
  } );
@@ -1,38 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { CSSProperties, ComponentType, ReactNode } from 'react';
3
-
4
- type TooltipData = {
5
- label: string;
6
- value: number;
7
- valueDisplay?: string;
8
- };
9
- type TooltipComponentProps = {
10
- data: TooltipData;
11
- className?: string;
12
- };
13
- type TooltipCommonProps = {
14
- top: number;
15
- left: number;
16
- style?: CSSProperties;
17
- className?: string;
18
- /**
19
- * Whether to render the tooltip container div. When false, only renders the content.
20
- * Useful when the tooltip is rendered inside a portal or custom container.
21
- * @default true
22
- */
23
- renderContainer?: boolean;
24
- };
25
- type DefaultDataTooltip = {
26
- data: TooltipData;
27
- component?: ComponentType<TooltipComponentProps>;
28
- children?: never;
29
- };
30
- type CustomTooltip = {
31
- children: ReactNode;
32
- data?: never;
33
- component?: never;
34
- };
35
- type BaseTooltipProps = TooltipCommonProps & (DefaultDataTooltip | CustomTooltip);
36
- declare const BaseTooltip: ({ data, top, left, component: Component, children, className, style, renderContainer, }: BaseTooltipProps) => string | number | true | Iterable<ReactNode> | react_jsx_runtime.JSX.Element;
37
-
38
- export { BaseTooltip as B, type TooltipData as T, type BaseTooltipProps as a };
@@ -1,38 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { CSSProperties, ComponentType, ReactNode } from 'react';
3
-
4
- type TooltipData = {
5
- label: string;
6
- value: number;
7
- valueDisplay?: string;
8
- };
9
- type TooltipComponentProps = {
10
- data: TooltipData;
11
- className?: string;
12
- };
13
- type TooltipCommonProps = {
14
- top: number;
15
- left: number;
16
- style?: CSSProperties;
17
- className?: string;
18
- /**
19
- * Whether to render the tooltip container div. When false, only renders the content.
20
- * Useful when the tooltip is rendered inside a portal or custom container.
21
- * @default true
22
- */
23
- renderContainer?: boolean;
24
- };
25
- type DefaultDataTooltip = {
26
- data: TooltipData;
27
- component?: ComponentType<TooltipComponentProps>;
28
- children?: never;
29
- };
30
- type CustomTooltip = {
31
- children: ReactNode;
32
- data?: never;
33
- component?: never;
34
- };
35
- type BaseTooltipProps = TooltipCommonProps & (DefaultDataTooltip | CustomTooltip);
36
- declare const BaseTooltip: ({ data, top, left, component: Component, children, className, style, renderContainer, }: BaseTooltipProps) => string | number | true | Iterable<ReactNode> | react_jsx_runtime.JSX.Element;
37
-
38
- export { BaseTooltip as B, type TooltipData as T, type BaseTooltipProps as a };
@@ -1,17 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
-
3
-
4
- var _chunkXC4KHJYXcjs = require('../../chunk-XC4KHJYX.cjs');
5
- require('../../chunk-TZRUHQOH.cjs');
6
- require('../../chunk-XVBH5XHE.cjs');
7
- require('../../chunk-Y3NNQMAX.cjs');
8
- require('../../chunk-REZTQ4PH.cjs');
9
- require('../../chunk-ASLARV7L.cjs');
10
- require('../../chunk-WYK7EL5R.cjs');
11
- require('../../chunk-MXGLYWVP.cjs');
12
- require('../../chunk-EMMSS5I5.cjs');
13
-
14
-
15
-
16
- exports.BarChart = _chunkXC4KHJYXcjs.BarChartResponsive; exports.BarChartUnresponsive = _chunkXC4KHJYXcjs.BarChart;
17
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/jetpack/jetpack/projects/js-packages/charts/dist/charts/bar-chart/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACF,mHAAC","file":"/home/runner/work/jetpack/jetpack/projects/js-packages/charts/dist/charts/bar-chart/index.cjs"}