@automattic/charts 0.56.5 → 0.56.7
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.
- package/AGENTS.md +135 -0
- package/CHANGELOG.md +17 -0
- package/README.md +2 -1
- package/dist/charts/bar-chart/index.cjs +6 -6
- package/dist/charts/bar-chart/index.css +1 -4
- package/dist/charts/bar-chart/index.css.map +1 -1
- package/dist/charts/bar-chart/index.d.cts +2 -8
- package/dist/charts/bar-chart/index.d.ts +2 -8
- package/dist/charts/bar-chart/index.js +5 -5
- package/dist/charts/bar-list-chart/index.cjs +7 -7
- package/dist/charts/bar-list-chart/index.css +1 -4
- package/dist/charts/bar-list-chart/index.css.map +1 -1
- package/dist/charts/bar-list-chart/index.d.cts +2 -2
- package/dist/charts/bar-list-chart/index.d.ts +2 -2
- package/dist/charts/bar-list-chart/index.js +6 -6
- package/dist/charts/conversion-funnel-chart/index.cjs +5 -5
- package/dist/charts/conversion-funnel-chart/index.css +1 -4
- package/dist/charts/conversion-funnel-chart/index.css.map +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.cts +2 -1
- package/dist/charts/conversion-funnel-chart/index.d.ts +2 -1
- package/dist/charts/conversion-funnel-chart/index.js +4 -4
- package/dist/charts/geo-chart/index.cjs +4 -4
- package/dist/charts/geo-chart/index.css +1 -4
- package/dist/charts/geo-chart/index.css.map +1 -1
- package/dist/charts/geo-chart/index.d.cts +2 -1
- package/dist/charts/geo-chart/index.d.ts +2 -1
- package/dist/charts/geo-chart/index.js +3 -3
- package/dist/charts/leaderboard-chart/index.cjs +5 -5
- package/dist/charts/leaderboard-chart/index.css +1 -4
- package/dist/charts/leaderboard-chart/index.css.map +1 -1
- package/dist/charts/leaderboard-chart/index.d.cts +3 -2
- package/dist/charts/leaderboard-chart/index.d.ts +3 -2
- package/dist/charts/leaderboard-chart/index.js +4 -4
- package/dist/charts/line-chart/index.cjs +6 -6
- package/dist/charts/line-chart/index.css +1 -4
- package/dist/charts/line-chart/index.css.map +1 -1
- package/dist/charts/line-chart/index.d.cts +2 -8
- package/dist/charts/line-chart/index.d.ts +2 -8
- package/dist/charts/line-chart/index.js +5 -5
- package/dist/charts/pie-chart/index.cjs +6 -4
- package/dist/charts/pie-chart/index.cjs.map +1 -1
- package/dist/charts/pie-chart/index.css +13 -7
- package/dist/charts/pie-chart/index.css.map +1 -1
- package/dist/charts/pie-chart/index.d.cts +2 -1
- package/dist/charts/pie-chart/index.d.ts +2 -1
- package/dist/charts/pie-chart/index.js +5 -3
- package/dist/charts/pie-semi-circle-chart/index.cjs +6 -4
- package/dist/charts/pie-semi-circle-chart/index.cjs.map +1 -1
- package/dist/charts/pie-semi-circle-chart/index.css +12 -13
- package/dist/charts/pie-semi-circle-chart/index.css.map +1 -1
- package/dist/charts/pie-semi-circle-chart/index.d.cts +5 -2
- package/dist/charts/pie-semi-circle-chart/index.d.ts +5 -2
- package/dist/charts/pie-semi-circle-chart/index.js +5 -3
- package/dist/charts/sparkline/index.cjs +7 -7
- package/dist/charts/sparkline/index.css +1 -4
- package/dist/charts/sparkline/index.css.map +1 -1
- package/dist/charts/sparkline/index.js +6 -6
- package/dist/{chunk-NGHXTIUE.cjs → chunk-3EXJP67N.cjs} +7 -7
- package/dist/{chunk-NGHXTIUE.cjs.map → chunk-3EXJP67N.cjs.map} +1 -1
- package/dist/{chunk-FIFSYVN6.cjs → chunk-55ZCOYDF.cjs} +117 -132
- package/dist/chunk-55ZCOYDF.cjs.map +1 -0
- package/dist/{chunk-LT4YOIMM.js → chunk-7FDQGBY7.js} +145 -119
- package/dist/chunk-7FDQGBY7.js.map +1 -0
- package/dist/{chunk-7QDEU3KN.cjs → chunk-ASLARV7L.cjs} +6 -6
- package/dist/chunk-ASLARV7L.cjs.map +1 -0
- package/dist/chunk-BXFD7JIG.cjs +401 -0
- package/dist/chunk-BXFD7JIG.cjs.map +1 -0
- package/dist/{chunk-XCXAWMJQ.cjs → chunk-CAFJRZPZ.cjs} +12 -12
- package/dist/{chunk-XCXAWMJQ.cjs.map → chunk-CAFJRZPZ.cjs.map} +1 -1
- package/dist/{chunk-KHRPRH4V.js → chunk-E62LCBGD.js} +4 -4
- package/dist/{chunk-PCOI2GT5.js → chunk-GWBS65VC.js} +3 -3
- package/dist/{chunk-MEIVKY4K.js → chunk-IS5YYLTV.js} +18 -18
- package/dist/{chunk-MEIVKY4K.js.map → chunk-IS5YYLTV.js.map} +1 -1
- package/dist/{chunk-Q6G3BGCL.cjs → chunk-K6TGILHX.cjs} +8 -8
- package/dist/{chunk-Q6G3BGCL.cjs.map → chunk-K6TGILHX.cjs.map} +1 -1
- package/dist/{chunk-X6GX4QUJ.js → chunk-KHQPN77E.js} +3 -3
- package/dist/{chunk-SEKPIG5K.js → chunk-KNIMXN6Z.js} +2 -2
- package/dist/{chunk-SEKPIG5K.js.map → chunk-KNIMXN6Z.js.map} +1 -1
- package/dist/{chunk-AFWQR3SM.js → chunk-MDRCAGKZ.js} +4 -4
- package/dist/{chunk-TKPK4RFS.cjs → chunk-NQJE2CC7.cjs} +120 -98
- package/dist/chunk-NQJE2CC7.cjs.map +1 -0
- package/dist/{chunk-FY325WQ4.cjs → chunk-O2JIANHK.cjs} +25 -25
- package/dist/chunk-O2JIANHK.cjs.map +1 -0
- package/dist/{chunk-DLSUC7RN.js → chunk-OMS5QIJN.js} +6 -6
- package/dist/chunk-OMS5QIJN.js.map +1 -0
- package/dist/{chunk-TYIH5LMV.js → chunk-OP6PHB2U.js} +6 -6
- package/dist/chunk-OP6PHB2U.js.map +1 -0
- package/dist/{chunk-32ESS4MV.js → chunk-RFSHE3HL.js} +17 -7
- package/dist/chunk-RFSHE3HL.js.map +1 -0
- package/dist/{chunk-KXSLMOW5.js → chunk-SSFFCBCF.js} +6 -6
- package/dist/chunk-SSFFCBCF.js.map +1 -0
- package/dist/{chunk-I5467ZJ5.cjs → chunk-SUDERBUA.cjs} +2 -2
- package/dist/{chunk-I5467ZJ5.cjs.map → chunk-SUDERBUA.cjs.map} +1 -1
- package/dist/{chunk-SH32YSZO.cjs → chunk-UFRBUT2D.cjs} +19 -19
- package/dist/{chunk-SH32YSZO.cjs.map → chunk-UFRBUT2D.cjs.map} +1 -1
- package/dist/{chunk-7TQSPLIN.js → chunk-VPAEBI2F.js} +109 -87
- package/dist/chunk-VPAEBI2F.js.map +1 -0
- package/dist/{chunk-IHESL7H5.cjs → chunk-X7JL2NYJ.cjs} +24 -24
- package/dist/chunk-X7JL2NYJ.cjs.map +1 -0
- package/dist/{chunk-DBY6C4O2.js → chunk-XD2HV7M5.js} +77 -92
- package/dist/chunk-XD2HV7M5.js.map +1 -0
- package/dist/{chunk-LTPJPIDP.cjs → chunk-YAXY5L7I.cjs} +7 -7
- package/dist/{chunk-LTPJPIDP.cjs.map → chunk-YAXY5L7I.cjs.map} +1 -1
- package/dist/{chunk-EJJO2QNB.cjs → chunk-YDVHT7GS.cjs} +17 -7
- package/dist/chunk-YDVHT7GS.cjs.map +1 -0
- package/dist/components/legend/index.cjs +2 -2
- package/dist/components/legend/index.css +1 -4
- package/dist/components/legend/index.css.map +1 -1
- package/dist/components/legend/index.d.cts +2 -1
- package/dist/components/legend/index.d.ts +2 -1
- package/dist/components/legend/index.js +1 -1
- package/dist/components/tooltip/index.d.cts +2 -1
- package/dist/components/tooltip/index.d.ts +2 -1
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.css +1 -4
- package/dist/hooks/index.css.map +1 -1
- package/dist/hooks/index.d.cts +10 -7
- package/dist/hooks/index.d.ts +10 -7
- package/dist/hooks/index.js +3 -3
- package/dist/index.cjs +14 -14
- package/dist/index.css +24 -16
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -13
- package/dist/{leaderboard-chart-B5gWcqe7.d.ts → leaderboard-chart-BSgEw_Um.d.ts} +1 -1
- package/dist/{leaderboard-chart-C_6QDcqj.d.cts → leaderboard-chart-COtgamhe.d.cts} +1 -1
- package/dist/providers/index.cjs +2 -2
- package/dist/providers/index.css +1 -4
- package/dist/providers/index.css.map +1 -1
- package/dist/providers/index.d.cts +3 -2
- package/dist/providers/index.d.ts +3 -2
- package/dist/providers/index.js +1 -1
- package/dist/{themes-BDVaIfBz.d.cts → themes-CVR5rmIs.d.cts} +1 -1
- package/dist/{themes-mcS8QNkQ.d.ts → themes-DQzmaSze.d.ts} +1 -1
- package/dist/{types-BCFQlzTM.d.ts → types-CzdN7rUe.d.cts} +12 -3
- package/dist/{types-BCFQlzTM.d.cts → types-CzdN7rUe.d.ts} +12 -3
- package/dist/utils/index.d.cts +2 -1
- package/dist/utils/index.d.ts +2 -1
- package/package.json +9 -9
- package/src/charts/bar-chart/bar-chart.tsx +2 -9
- package/src/charts/bar-chart/test/bar-chart.test.tsx +3 -3
- package/src/charts/line-chart/line-chart.tsx +2 -2
- package/src/charts/line-chart/test/line-chart.test.tsx +3 -3
- package/src/charts/line-chart/types.ts +0 -7
- package/src/charts/pie-chart/pie-chart.module.scss +14 -3
- package/src/charts/pie-chart/pie-chart.tsx +172 -148
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +17 -11
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +147 -119
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +46 -6
- package/src/charts/private/with-responsive/test/with-responsive.test.tsx +5 -5
- package/src/charts/private/with-responsive/with-responsive.tsx +8 -7
- package/src/hooks/index.ts +1 -1
- package/src/hooks/test/{use-element-height.test.tsx → use-element-size.test.tsx} +45 -36
- package/src/hooks/use-element-size.ts +43 -0
- package/src/hooks/use-tooltip-portal-relocator.module.scss +1 -4
- package/src/hooks/use-tooltip-portal-relocator.ts +11 -0
- package/src/types.ts +13 -3
- package/dist/chunk-32ESS4MV.js.map +0 -1
- package/dist/chunk-7QDEU3KN.cjs.map +0 -1
- package/dist/chunk-7TQSPLIN.js.map +0 -1
- package/dist/chunk-DBY6C4O2.js.map +0 -1
- package/dist/chunk-DLSUC7RN.js.map +0 -1
- package/dist/chunk-EJJO2QNB.cjs.map +0 -1
- package/dist/chunk-FIFSYVN6.cjs.map +0 -1
- package/dist/chunk-FY325WQ4.cjs.map +0 -1
- package/dist/chunk-IHESL7H5.cjs.map +0 -1
- package/dist/chunk-JL4ZKKZU.cjs +0 -375
- package/dist/chunk-JL4ZKKZU.cjs.map +0 -1
- package/dist/chunk-KXSLMOW5.js.map +0 -1
- package/dist/chunk-LT4YOIMM.js.map +0 -1
- package/dist/chunk-TKPK4RFS.cjs.map +0 -1
- package/dist/chunk-TYIH5LMV.js.map +0 -1
- package/src/hooks/use-element-height.ts +0 -37
- /package/dist/{chunk-KHRPRH4V.js.map → chunk-E62LCBGD.js.map} +0 -0
- /package/dist/{chunk-PCOI2GT5.js.map → chunk-GWBS65VC.js.map} +0 -0
- /package/dist/{chunk-X6GX4QUJ.js.map → chunk-KHQPN77E.js.map} +0 -0
- /package/dist/{chunk-AFWQR3SM.js.map → chunk-MDRCAGKZ.js.map} +0 -0
|
@@ -3,11 +3,12 @@ import { Pie } from '@visx/shape';
|
|
|
3
3
|
import { Text } from '@visx/text';
|
|
4
4
|
import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
|
+
import { Stack } from '@wordpress/ui';
|
|
6
7
|
import clsx from 'clsx';
|
|
7
8
|
import { useCallback, useContext, useMemo } from 'react';
|
|
8
9
|
import { Legend, useChartLegendItems } from '../../components/legend';
|
|
9
10
|
import { BaseTooltip } from '../../components/tooltip';
|
|
10
|
-
import {
|
|
11
|
+
import { useElementSize, useInteractiveLegendData, usePrefersReducedMotion } from '../../hooks';
|
|
11
12
|
import {
|
|
12
13
|
GlobalChartsProvider,
|
|
13
14
|
useChartId,
|
|
@@ -52,10 +53,13 @@ const renderDefaultPieSemiCircleTooltip = ( {
|
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
const PAD_ANGLE = 0.03; // Padding between segments
|
|
56
|
+
const DEFAULT_WIDTH = 400;
|
|
55
57
|
|
|
56
58
|
export interface PieSemiCircleChartProps extends BaseChartProps< DataPointPercentage[] > {
|
|
57
59
|
/**
|
|
58
|
-
*
|
|
60
|
+
* Explicit width of the chart container in pixels.
|
|
61
|
+
* When omitted, the chart fills its parent container's width.
|
|
62
|
+
* The chart always maintains a 2:1 width-to-height ratio, constrained by available space.
|
|
59
63
|
*/
|
|
60
64
|
width?: number;
|
|
61
65
|
|
|
@@ -157,7 +161,8 @@ const validateData = ( data: DataPointPercentage[] ) => {
|
|
|
157
161
|
const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
158
162
|
data,
|
|
159
163
|
chartId: providedChartId,
|
|
160
|
-
width
|
|
164
|
+
width: propWidth,
|
|
165
|
+
height: propHeight,
|
|
161
166
|
thickness = 0.4,
|
|
162
167
|
clockwise = true,
|
|
163
168
|
withTooltips = false,
|
|
@@ -179,9 +184,11 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
179
184
|
tooltipOffsetX = 0,
|
|
180
185
|
tooltipOffsetY = -15,
|
|
181
186
|
renderTooltip = renderDefaultPieSemiCircleTooltip,
|
|
187
|
+
gap = 'md',
|
|
182
188
|
} ) => {
|
|
183
189
|
const chartId = useChartId( providedChartId );
|
|
184
|
-
|
|
190
|
+
// Measure the SVG wrapper to calculate constrained dimensions
|
|
191
|
+
const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
|
|
185
192
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
|
|
186
193
|
useTooltip< DataPointPercentage >();
|
|
187
194
|
|
|
@@ -295,10 +302,17 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
295
302
|
|
|
296
303
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
297
304
|
|
|
305
|
+
const effectiveWidth = propWidth || DEFAULT_WIDTH;
|
|
306
|
+
|
|
298
307
|
if ( ! isValid ) {
|
|
308
|
+
const errorWidth = propHeight
|
|
309
|
+
? Math.min( propWidth || propHeight * 2, propHeight * 2 )
|
|
310
|
+
: effectiveWidth;
|
|
311
|
+
const errorHeight = errorWidth / 2;
|
|
312
|
+
|
|
299
313
|
return (
|
|
300
314
|
<div className={ styles[ 'pie-semi-circle-chart' ] }>
|
|
301
|
-
<svg width={
|
|
315
|
+
<svg width={ errorWidth } height={ errorHeight } data-testid="pie-chart-svg">
|
|
302
316
|
<text x="50%" y="50%" textAnchor="middle" className={ styles.error }>
|
|
303
317
|
{ message }
|
|
304
318
|
</text>
|
|
@@ -307,12 +321,16 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
307
321
|
);
|
|
308
322
|
}
|
|
309
323
|
|
|
310
|
-
// Calculate chart dimensions
|
|
311
|
-
//
|
|
324
|
+
// Calculate chart dimensions maintaining the 2:1 width-to-height ratio.
|
|
325
|
+
// Use measured SVG wrapper dimensions to respect height constraints, falling back
|
|
326
|
+
// to explicit props during initial render before measurement is available.
|
|
327
|
+
const availableWidth = svgWrapperWidth > 0 ? svgWrapperWidth : effectiveWidth;
|
|
328
|
+
const availableHeight =
|
|
329
|
+
svgWrapperHeight > 0 ? svgWrapperHeight : propHeight || effectiveWidth / 2;
|
|
330
|
+
// Constrain width so that height (= width / 2) never exceeds the available height
|
|
331
|
+
const width = Math.min( availableWidth, availableHeight * 2 );
|
|
312
332
|
const height = width / 2;
|
|
313
|
-
|
|
314
|
-
const chartHeight = height - ( showLegend && legendPosition === 'top' ? legendHeight : 0 );
|
|
315
|
-
const radius = Math.min( width / 2, chartHeight );
|
|
333
|
+
const radius = height; // For a semi-circle, radius equals the SVG height
|
|
316
334
|
const innerRadius = radius * ( 1 - thickness );
|
|
317
335
|
|
|
318
336
|
// Map data with index for color assignment
|
|
@@ -329,119 +347,144 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
329
347
|
const startAngle = clockwise ? -Math.PI / 2 : Math.PI / 2;
|
|
330
348
|
const endAngle = clockwise ? Math.PI / 2 : -Math.PI / 2;
|
|
331
349
|
|
|
350
|
+
const legendElement = showLegend && (
|
|
351
|
+
<Legend
|
|
352
|
+
orientation={ legendOrientation }
|
|
353
|
+
position={ legendPosition }
|
|
354
|
+
alignment={ legendAlignment }
|
|
355
|
+
maxWidth={ legendMaxWidth }
|
|
356
|
+
textOverflow={ legendTextOverflow }
|
|
357
|
+
legendItemClassName={ legendItemClassName }
|
|
358
|
+
shape={ legendShape }
|
|
359
|
+
chartId={ chartId }
|
|
360
|
+
interactive={ legendInteractive }
|
|
361
|
+
/>
|
|
362
|
+
);
|
|
363
|
+
|
|
332
364
|
return (
|
|
333
365
|
<SingleChartContext.Provider
|
|
334
366
|
value={ {
|
|
335
367
|
chartId,
|
|
336
368
|
chartWidth: width,
|
|
337
|
-
chartHeight:
|
|
369
|
+
chartHeight: height,
|
|
338
370
|
} }
|
|
339
371
|
>
|
|
340
|
-
<
|
|
372
|
+
<Stack
|
|
341
373
|
ref={ containerRef }
|
|
374
|
+
direction="column"
|
|
375
|
+
gap={ gap }
|
|
342
376
|
className={ clsx(
|
|
343
377
|
'pie-semi-circle-chart',
|
|
344
378
|
styles[ 'pie-semi-circle-chart' ],
|
|
345
379
|
{
|
|
346
|
-
[ styles[ 'pie-semi-circle-chart--
|
|
347
|
-
showLegend && legendPosition === 'top',
|
|
380
|
+
[ styles[ 'pie-semi-circle-chart--responsive' ] ]: ! propWidth && ! propHeight,
|
|
348
381
|
},
|
|
349
382
|
className
|
|
350
383
|
) }
|
|
384
|
+
style={ {
|
|
385
|
+
width: propWidth || undefined,
|
|
386
|
+
height: propHeight || undefined,
|
|
387
|
+
} }
|
|
351
388
|
data-testid="pie-chart-container"
|
|
352
389
|
>
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
id={ `radial-wipe-${ chartId }` }
|
|
362
|
-
radius={ radius }
|
|
363
|
-
innerRadius={ innerRadius }
|
|
364
|
-
startAngle="-180deg"
|
|
365
|
-
wipePercentage={ 50 }
|
|
366
|
-
/>
|
|
367
|
-
</defs>
|
|
368
|
-
|
|
369
|
-
{ /* Main chart group centered horizontally and positioned at bottom */ }
|
|
370
|
-
<Group
|
|
371
|
-
top={ chartHeight }
|
|
372
|
-
left={ width / 2 }
|
|
373
|
-
mask={ animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null }
|
|
390
|
+
{ legendPosition === 'top' && legendElement }
|
|
391
|
+
|
|
392
|
+
<div ref={ svgWrapperRef } className={ styles[ 'pie-semi-circle-chart__svg-wrapper' ] }>
|
|
393
|
+
<svg
|
|
394
|
+
width={ width }
|
|
395
|
+
height={ height }
|
|
396
|
+
viewBox={ `0 0 ${ width } ${ height }` }
|
|
397
|
+
data-testid="pie-chart-svg"
|
|
374
398
|
>
|
|
375
|
-
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
startAngle={ startAngle }
|
|
399
|
-
endAngle={ endAngle }
|
|
400
|
-
pieSort={ accessors.sort }
|
|
399
|
+
<defs>
|
|
400
|
+
<RadialWipeAnimation
|
|
401
|
+
id={ `radial-wipe-${ chartId }` }
|
|
402
|
+
radius={ radius }
|
|
403
|
+
innerRadius={ innerRadius }
|
|
404
|
+
startAngle="-180deg"
|
|
405
|
+
wipePercentage={ 50 }
|
|
406
|
+
/>
|
|
407
|
+
</defs>
|
|
408
|
+
|
|
409
|
+
{ /* Main chart group centered horizontally and positioned at bottom */ }
|
|
410
|
+
<Group
|
|
411
|
+
top={ height }
|
|
412
|
+
left={ width / 2 }
|
|
413
|
+
mask={ animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null }
|
|
414
|
+
>
|
|
415
|
+
{ allSegmentsHidden ? (
|
|
416
|
+
<text
|
|
417
|
+
textAnchor="middle"
|
|
418
|
+
y={ -radius / 2 }
|
|
419
|
+
fill="#ccc"
|
|
420
|
+
fontSize="14"
|
|
421
|
+
fontFamily="-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif"
|
|
401
422
|
>
|
|
402
|
-
{
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
<Group>
|
|
421
|
-
<Text
|
|
422
|
-
textAnchor="middle"
|
|
423
|
-
verticalAnchor="start"
|
|
424
|
-
y={ -40 } // Position above the chart with space for note
|
|
425
|
-
className={ styles.label }
|
|
426
|
-
>
|
|
427
|
-
{ label }
|
|
428
|
-
</Text>
|
|
429
|
-
<Text
|
|
430
|
-
textAnchor="middle"
|
|
431
|
-
verticalAnchor="start"
|
|
432
|
-
y={ -20 } // Position between label and chart
|
|
433
|
-
className={ styles.note }
|
|
423
|
+
{ __(
|
|
424
|
+
'All segments are hidden. Click legend items to show data.',
|
|
425
|
+
'jetpack-charts'
|
|
426
|
+
) }
|
|
427
|
+
</text>
|
|
428
|
+
) : (
|
|
429
|
+
<>
|
|
430
|
+
{ /* Pie chart */ }
|
|
431
|
+
<Pie< DataPointPercentage & { index: number } >
|
|
432
|
+
data={ dataWithIndex }
|
|
433
|
+
pieValue={ accessors.value }
|
|
434
|
+
outerRadius={ radius }
|
|
435
|
+
innerRadius={ innerRadius }
|
|
436
|
+
cornerRadius={ 3 }
|
|
437
|
+
padAngle={ PAD_ANGLE }
|
|
438
|
+
startAngle={ startAngle }
|
|
439
|
+
endAngle={ endAngle }
|
|
440
|
+
pieSort={ accessors.sort }
|
|
434
441
|
>
|
|
435
|
-
{
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
442
|
+
{ pie => {
|
|
443
|
+
return pie.arcs.map( arc => (
|
|
444
|
+
<g
|
|
445
|
+
key={ arc.data.label }
|
|
446
|
+
onMouseMove={ withTooltips ? handleArcMouseMove( arc ) : undefined }
|
|
447
|
+
onMouseLeave={ withTooltips ? handleMouseLeave : undefined }
|
|
448
|
+
>
|
|
449
|
+
<path
|
|
450
|
+
d={ pie.path( arc ) || '' }
|
|
451
|
+
fill={ accessors.fill( arc.data ) }
|
|
452
|
+
data-testid="pie-segment"
|
|
453
|
+
/>
|
|
454
|
+
</g>
|
|
455
|
+
) );
|
|
456
|
+
} }
|
|
457
|
+
</Pie>
|
|
458
|
+
|
|
459
|
+
{ /* Label and note text */ }
|
|
460
|
+
<Group>
|
|
461
|
+
<Text
|
|
462
|
+
textAnchor="middle"
|
|
463
|
+
verticalAnchor="start"
|
|
464
|
+
y={ -40 } // Position above the chart with space for note
|
|
465
|
+
className={ styles.label }
|
|
466
|
+
>
|
|
467
|
+
{ label }
|
|
468
|
+
</Text>
|
|
469
|
+
<Text
|
|
470
|
+
textAnchor="middle"
|
|
471
|
+
verticalAnchor="start"
|
|
472
|
+
y={ -20 } // Position between label and chart
|
|
473
|
+
className={ styles.note }
|
|
474
|
+
>
|
|
475
|
+
{ note }
|
|
476
|
+
</Text>
|
|
477
|
+
</Group>
|
|
478
|
+
|
|
479
|
+
{ /* Render SVG children from composition API */ }
|
|
480
|
+
{ ! allSegmentsHidden && svgChildren }
|
|
481
|
+
</>
|
|
482
|
+
) }
|
|
483
|
+
</Group>
|
|
484
|
+
</svg>
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
{ legendPosition !== 'top' && legendElement }
|
|
445
488
|
|
|
446
489
|
{ withTooltips && tooltipOpen && tooltipData && (
|
|
447
490
|
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
@@ -449,27 +492,12 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
449
492
|
</TooltipInPortal>
|
|
450
493
|
) }
|
|
451
494
|
|
|
452
|
-
{ showLegend && (
|
|
453
|
-
<Legend
|
|
454
|
-
orientation={ legendOrientation }
|
|
455
|
-
position={ legendPosition }
|
|
456
|
-
alignment={ legendAlignment }
|
|
457
|
-
maxWidth={ legendMaxWidth }
|
|
458
|
-
textOverflow={ legendTextOverflow }
|
|
459
|
-
legendItemClassName={ legendItemClassName }
|
|
460
|
-
shape={ legendShape }
|
|
461
|
-
ref={ legendRef }
|
|
462
|
-
chartId={ chartId }
|
|
463
|
-
interactive={ legendInteractive }
|
|
464
|
-
/>
|
|
465
|
-
) }
|
|
466
|
-
|
|
467
495
|
{ /* Render HTML children from composition API */ }
|
|
468
496
|
{ htmlChildren }
|
|
469
497
|
|
|
470
498
|
{ /* Render any other children that aren't compound components */ }
|
|
471
499
|
{ otherChildren }
|
|
472
|
-
</
|
|
500
|
+
</Stack>
|
|
473
501
|
</SingleChartContext.Provider>
|
|
474
502
|
);
|
|
475
503
|
};
|
|
@@ -3,6 +3,15 @@ import userEvent from '@testing-library/user-event';
|
|
|
3
3
|
import { GlobalChartsProvider } from '../../../providers';
|
|
4
4
|
import PieSemiCircleChart from '../pie-semi-circle-chart';
|
|
5
5
|
|
|
6
|
+
// Mock useParentSize so the responsive wrapper returns predictable dimensions in tests
|
|
7
|
+
jest.mock( '@visx/responsive', () => ( {
|
|
8
|
+
useParentSize: jest.fn( () => ( {
|
|
9
|
+
parentRef: { current: null },
|
|
10
|
+
width: 400,
|
|
11
|
+
height: 200,
|
|
12
|
+
} ) ),
|
|
13
|
+
} ) );
|
|
14
|
+
|
|
6
15
|
// Mock data for testing
|
|
7
16
|
const mockData = [
|
|
8
17
|
{
|
|
@@ -167,15 +176,15 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
167
176
|
expect( thinPathD ).not.toBe( thickPathD );
|
|
168
177
|
} );
|
|
169
178
|
|
|
170
|
-
it( 'renders with correct dimensions', () => {
|
|
171
|
-
|
|
172
|
-
render( <PieSemiCircleChart data={ mockData }
|
|
179
|
+
it( 'renders with correct dimensions from measured container', () => {
|
|
180
|
+
// Mock returns width:400, height:200 — chart should render at 400×200 (2:1 ratio)
|
|
181
|
+
render( <PieSemiCircleChart data={ mockData } /> );
|
|
173
182
|
|
|
174
183
|
const svg = screen.getByTestId( 'pie-chart-svg' );
|
|
175
184
|
|
|
176
|
-
expect( svg ).toHaveAttribute( 'width',
|
|
177
|
-
expect( svg ).toHaveAttribute( 'height',
|
|
178
|
-
expect( svg ).toHaveAttribute( 'viewBox',
|
|
185
|
+
expect( svg ).toHaveAttribute( 'width', '400' );
|
|
186
|
+
expect( svg ).toHaveAttribute( 'height', '200' );
|
|
187
|
+
expect( svg ).toHaveAttribute( 'viewBox', '0 0 400 200' );
|
|
179
188
|
} );
|
|
180
189
|
|
|
181
190
|
describe( 'Data Validation', () => {
|
|
@@ -216,6 +225,37 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
216
225
|
} );
|
|
217
226
|
} );
|
|
218
227
|
|
|
228
|
+
describe( 'Responsive wrapper', () => {
|
|
229
|
+
it( 'fills parent container (height:100%) by default', () => {
|
|
230
|
+
render( <PieSemiCircleChart data={ mockData } /> );
|
|
231
|
+
const wrapper = screen.getByTestId( 'responsive-wrapper' );
|
|
232
|
+
expect( wrapper ).toHaveStyle( { height: '100%' } );
|
|
233
|
+
} );
|
|
234
|
+
|
|
235
|
+
it( 'constrains chart to 2:1 ratio from measured dimensions', () => {
|
|
236
|
+
// Mock returns width:400, height:200, so chart renders at 400×200 (2:1 ratio)
|
|
237
|
+
render( <PieSemiCircleChart data={ mockData } /> );
|
|
238
|
+
const svg = screen.getByTestId( 'pie-chart-svg' );
|
|
239
|
+
expect( svg ).toHaveAttribute( 'width', '400' );
|
|
240
|
+
expect( svg ).toHaveAttribute( 'height', '200' );
|
|
241
|
+
} );
|
|
242
|
+
|
|
243
|
+
it( 'constrains chart width when container height is shorter than 2:1 ratio', () => {
|
|
244
|
+
// If parent height is 100px, chart should be at most 200×100 (not 400×200)
|
|
245
|
+
const { useParentSize } = jest.requireMock( '@visx/responsive' );
|
|
246
|
+
useParentSize.mockReturnValueOnce( {
|
|
247
|
+
parentRef: { current: null },
|
|
248
|
+
width: 400,
|
|
249
|
+
height: 100,
|
|
250
|
+
} );
|
|
251
|
+
render( <PieSemiCircleChart data={ mockData } /> );
|
|
252
|
+
const svg = screen.getByTestId( 'pie-chart-svg' );
|
|
253
|
+
// chartWidth = min(400, 100*2) = 200, chartHeight = 100
|
|
254
|
+
expect( svg ).toHaveAttribute( 'width', '200' );
|
|
255
|
+
expect( svg ).toHaveAttribute( 'height', '100' );
|
|
256
|
+
} );
|
|
257
|
+
} );
|
|
258
|
+
|
|
219
259
|
describe( 'Interactive Legend', () => {
|
|
220
260
|
test( 'filters segments when interactive legend is enabled and segment is toggled', async () => {
|
|
221
261
|
const user = userEvent.setup();
|
|
@@ -47,10 +47,10 @@ describe( 'withResponsive', () => {
|
|
|
47
47
|
expect( component ).toHaveStyle( { width: '400px' } );
|
|
48
48
|
} );
|
|
49
49
|
|
|
50
|
-
test( 'passes size prop
|
|
51
|
-
render( <ResponsiveComponent data={ [] } /> );
|
|
50
|
+
test( 'passes explicit size prop through to component', () => {
|
|
51
|
+
render( <ResponsiveComponent data={ [] } size={ 200 } /> );
|
|
52
52
|
const component = screen.getByTestId( 'mock-component' );
|
|
53
|
-
expect( component ).toHaveAttribute( 'data-size', '
|
|
53
|
+
expect( component ).toHaveAttribute( 'data-size', '200' );
|
|
54
54
|
} );
|
|
55
55
|
} );
|
|
56
56
|
|
|
@@ -61,8 +61,8 @@ describe( 'withResponsive', () => {
|
|
|
61
61
|
expect( wrapper ).toHaveStyle( { width: '100%', height: '100%' } );
|
|
62
62
|
} );
|
|
63
63
|
|
|
64
|
-
test( 'wrapper uses
|
|
65
|
-
render( <ResponsiveComponent data={ [] }
|
|
64
|
+
test( 'wrapper uses explicit width/height for dimensions when provided', () => {
|
|
65
|
+
render( <ResponsiveComponent data={ [] } width={ 200 } height={ 200 } /> );
|
|
66
66
|
const wrapper = screen.getByTestId( 'responsive-wrapper' );
|
|
67
67
|
expect( wrapper ).toHaveStyle( { width: '200px', height: '200px' } );
|
|
68
68
|
} );
|
|
@@ -86,10 +86,11 @@ export function withResponsive< T extends Exclude< BaseChartProps< unknown >, 'o
|
|
|
86
86
|
aspectRatio,
|
|
87
87
|
} );
|
|
88
88
|
|
|
89
|
-
// Use measured dimensions, but fall back to explicit props if measurement returns 0
|
|
90
|
-
// (e.g., during initial render or in test environments without DOM measurement)
|
|
91
|
-
|
|
92
|
-
const
|
|
89
|
+
// Use measured dimensions, but fall back to explicit width/height props if measurement returns 0
|
|
90
|
+
// (e.g., during initial render or in test environments without DOM measurement).
|
|
91
|
+
// Do not use size here — size controls chart element dimensions (e.g. pie diameter), not container dimensions.
|
|
92
|
+
const effectiveWidth = measuredWidth || width || 0;
|
|
93
|
+
const effectiveHeight = measuredHeight || height || 0;
|
|
93
94
|
|
|
94
95
|
const defaultHeight = hasAspectRatio ? 'auto' : '100%';
|
|
95
96
|
|
|
@@ -99,14 +100,14 @@ export function withResponsive< T extends Exclude< BaseChartProps< unknown >, 'o
|
|
|
99
100
|
data-testid="responsive-wrapper"
|
|
100
101
|
className={ styles.container }
|
|
101
102
|
style={ {
|
|
102
|
-
width:
|
|
103
|
-
height:
|
|
103
|
+
width: width ?? '100%',
|
|
104
|
+
height: height ?? defaultHeight,
|
|
104
105
|
} }
|
|
105
106
|
>
|
|
106
107
|
<WrappedComponent
|
|
107
108
|
width={ effectiveWidth }
|
|
108
109
|
height={ effectiveHeight }
|
|
109
|
-
size={
|
|
110
|
+
size={ size }
|
|
110
111
|
{ ...( chartProps as T ) }
|
|
111
112
|
/>
|
|
112
113
|
</div>
|
package/src/hooks/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { useChartMouseHandler } from './use-chart-mouse-handler';
|
|
|
3
3
|
export { useXYChartTheme } from './use-xychart-theme';
|
|
4
4
|
export { useChartDataTransform } from './use-chart-data-transform';
|
|
5
5
|
export { useChartMargin } from './use-chart-margin';
|
|
6
|
-
export {
|
|
6
|
+
export { useElementSize } from './use-element-size';
|
|
7
7
|
export { useHasLegendChild } from './use-has-legend-child';
|
|
8
8
|
export { useTextTruncation } from './use-text-truncation';
|
|
9
9
|
export { useZeroValueDisplay } from './use-zero-value-display';
|