@automattic/charts 0.58.0 → 0.59.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.
- package/CHANGELOG.md +20 -0
- package/README.md +7 -54
- package/dist/index.cjs +9606 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +20 -25
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +1612 -33
- package/dist/index.d.ts +1612 -33
- package/dist/index.js +9640 -56
- package/dist/index.js.map +1 -1
- package/package.json +8 -125
- package/src/charts/bar-chart/bar-chart.module.scss +0 -5
- package/src/charts/bar-chart/bar-chart.tsx +131 -137
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +23 -40
- package/src/charts/line-chart/line-chart.module.scss +0 -5
- package/src/charts/line-chart/line-chart.tsx +190 -183
- package/src/charts/line-chart/private/line-chart-annotations-overlay.tsx +1 -2
- package/src/charts/pie-chart/pie-chart.module.scss +2 -10
- package/src/charts/pie-chart/pie-chart.tsx +198 -199
- package/src/charts/pie-chart/test/composition-api.test.tsx +3 -3
- package/src/charts/pie-chart/test/pie-chart.test.tsx +42 -35
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +2 -8
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +155 -155
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +25 -25
- package/src/charts/private/chart-layout/chart-layout.module.scss +7 -0
- package/src/charts/private/chart-layout/chart-layout.tsx +106 -0
- package/src/charts/private/chart-layout/index.ts +2 -0
- package/src/charts/private/chart-layout/test/chart-layout.test.tsx +167 -0
- package/src/charts/private/single-chart-context/single-chart-context.tsx +2 -2
- package/src/charts/private/svg-empty-state/index.ts +1 -0
- package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +7 -0
- package/src/charts/private/svg-empty-state/svg-empty-state.tsx +40 -0
- package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +12 -8
- package/src/components/legend/hooks/use-chart-legend-items.ts +12 -13
- package/src/components/legend/legend.tsx +33 -8
- package/src/components/legend/test/legend.test.tsx +93 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-data-with-percentages.ts +24 -0
- package/src/hooks/use-interactive-legend-data.ts +18 -15
- package/src/index.ts +65 -2
- package/src/providers/chart-context/global-charts-provider.tsx +7 -1
- package/src/providers/chart-context/hooks/use-chart-registration.ts +2 -1
- package/src/providers/chart-context/types.ts +2 -2
- package/src/types.ts +27 -7
- package/src/utils/test/resolve-css-var.test.ts +2 -0
- package/dist/base-tooltip-DOq93wjU.d.cts +0 -38
- package/dist/base-tooltip-DOq93wjU.d.ts +0 -38
- package/dist/charts/bar-chart/index.cjs +0 -17
- package/dist/charts/bar-chart/index.cjs.map +0 -1
- package/dist/charts/bar-chart/index.css +0 -141
- package/dist/charts/bar-chart/index.css.map +0 -1
- package/dist/charts/bar-chart/index.d.cts +0 -36
- package/dist/charts/bar-chart/index.d.ts +0 -36
- package/dist/charts/bar-chart/index.js +0 -17
- package/dist/charts/bar-chart/index.js.map +0 -1
- package/dist/charts/bar-list-chart/index.cjs +0 -18
- package/dist/charts/bar-list-chart/index.cjs.map +0 -1
- package/dist/charts/bar-list-chart/index.css +0 -141
- package/dist/charts/bar-list-chart/index.css.map +0 -1
- package/dist/charts/bar-list-chart/index.d.cts +0 -92
- package/dist/charts/bar-list-chart/index.d.ts +0 -92
- package/dist/charts/bar-list-chart/index.js +0 -18
- package/dist/charts/bar-list-chart/index.js.map +0 -1
- package/dist/charts/conversion-funnel-chart/index.cjs +0 -11
- package/dist/charts/conversion-funnel-chart/index.cjs.map +0 -1
- package/dist/charts/conversion-funnel-chart/index.css +0 -157
- package/dist/charts/conversion-funnel-chart/index.css.map +0 -1
- package/dist/charts/conversion-funnel-chart/index.d.cts +0 -97
- package/dist/charts/conversion-funnel-chart/index.d.ts +0 -97
- package/dist/charts/conversion-funnel-chart/index.js +0 -11
- package/dist/charts/conversion-funnel-chart/index.js.map +0 -1
- package/dist/charts/geo-chart/index.cjs +0 -13
- package/dist/charts/geo-chart/index.cjs.map +0 -1
- package/dist/charts/geo-chart/index.css +0 -23
- package/dist/charts/geo-chart/index.css.map +0 -1
- package/dist/charts/geo-chart/index.d.cts +0 -67
- package/dist/charts/geo-chart/index.d.ts +0 -67
- package/dist/charts/geo-chart/index.js +0 -13
- package/dist/charts/geo-chart/index.js.map +0 -1
- package/dist/charts/leaderboard-chart/index.cjs +0 -21
- package/dist/charts/leaderboard-chart/index.cjs.map +0 -1
- package/dist/charts/leaderboard-chart/index.css +0 -160
- package/dist/charts/leaderboard-chart/index.css.map +0 -1
- package/dist/charts/leaderboard-chart/index.d.cts +0 -46
- package/dist/charts/leaderboard-chart/index.d.ts +0 -46
- package/dist/charts/leaderboard-chart/index.js +0 -21
- package/dist/charts/leaderboard-chart/index.js.map +0 -1
- package/dist/charts/line-chart/index.cjs +0 -17
- package/dist/charts/line-chart/index.cjs.map +0 -1
- package/dist/charts/line-chart/index.css +0 -213
- package/dist/charts/line-chart/index.css.map +0 -1
- package/dist/charts/line-chart/index.d.cts +0 -98
- package/dist/charts/line-chart/index.d.ts +0 -98
- package/dist/charts/line-chart/index.js +0 -17
- package/dist/charts/line-chart/index.js.map +0 -1
- package/dist/charts/pie-chart/index.cjs +0 -19
- package/dist/charts/pie-chart/index.cjs.map +0 -1
- package/dist/charts/pie-chart/index.css +0 -131
- package/dist/charts/pie-chart/index.css.map +0 -1
- package/dist/charts/pie-chart/index.d.cts +0 -91
- package/dist/charts/pie-chart/index.d.ts +0 -91
- package/dist/charts/pie-chart/index.js +0 -19
- package/dist/charts/pie-chart/index.js.map +0 -1
- package/dist/charts/pie-semi-circle-chart/index.cjs +0 -18
- package/dist/charts/pie-semi-circle-chart/index.cjs.map +0 -1
- package/dist/charts/pie-semi-circle-chart/index.css +0 -132
- package/dist/charts/pie-semi-circle-chart/index.css.map +0 -1
- package/dist/charts/pie-semi-circle-chart/index.d.cts +0 -88
- package/dist/charts/pie-semi-circle-chart/index.d.ts +0 -88
- package/dist/charts/pie-semi-circle-chart/index.js +0 -18
- package/dist/charts/pie-semi-circle-chart/index.js.map +0 -1
- package/dist/charts/sparkline/index.cjs +0 -18
- package/dist/charts/sparkline/index.cjs.map +0 -1
- package/dist/charts/sparkline/index.css +0 -230
- package/dist/charts/sparkline/index.css.map +0 -1
- package/dist/charts/sparkline/index.d.cts +0 -113
- package/dist/charts/sparkline/index.d.ts +0 -113
- package/dist/charts/sparkline/index.js +0 -18
- package/dist/charts/sparkline/index.js.map +0 -1
- package/dist/chunk-2A34OA5O.cjs +0 -51
- package/dist/chunk-2A34OA5O.cjs.map +0 -1
- package/dist/chunk-2I67QUIV.js +0 -895
- package/dist/chunk-2I67QUIV.js.map +0 -1
- package/dist/chunk-2ICEEQOC.js +0 -1071
- package/dist/chunk-2ICEEQOC.js.map +0 -1
- package/dist/chunk-4B7BL2DD.js +0 -120
- package/dist/chunk-4B7BL2DD.js.map +0 -1
- package/dist/chunk-4OXMTKAL.js +0 -401
- package/dist/chunk-4OXMTKAL.js.map +0 -1
- package/dist/chunk-ASLARV7L.cjs +0 -81
- package/dist/chunk-ASLARV7L.cjs.map +0 -1
- package/dist/chunk-B6NLZFRW.js +0 -617
- package/dist/chunk-B6NLZFRW.js.map +0 -1
- package/dist/chunk-BBAUQOW6.cjs +0 -120
- package/dist/chunk-BBAUQOW6.cjs.map +0 -1
- package/dist/chunk-BPYKWMI7.js +0 -194
- package/dist/chunk-BPYKWMI7.js.map +0 -1
- package/dist/chunk-CMMHCTBX.cjs +0 -373
- package/dist/chunk-CMMHCTBX.cjs.map +0 -1
- package/dist/chunk-CPPXJATQ.cjs +0 -1071
- package/dist/chunk-CPPXJATQ.cjs.map +0 -1
- package/dist/chunk-DKU775VC.js +0 -219
- package/dist/chunk-DKU775VC.js.map +0 -1
- package/dist/chunk-GRA7Y2ZG.cjs +0 -401
- package/dist/chunk-GRA7Y2ZG.cjs.map +0 -1
- package/dist/chunk-I2276W3I.cjs +0 -66
- package/dist/chunk-I2276W3I.cjs.map +0 -1
- package/dist/chunk-JJIMABHT.js +0 -351
- package/dist/chunk-JJIMABHT.js.map +0 -1
- package/dist/chunk-KJHWXOCZ.js +0 -421
- package/dist/chunk-KJHWXOCZ.js.map +0 -1
- package/dist/chunk-KRWGSOJ2.js +0 -91
- package/dist/chunk-KRWGSOJ2.js.map +0 -1
- package/dist/chunk-KXRWNFQJ.js +0 -51
- package/dist/chunk-KXRWNFQJ.js.map +0 -1
- package/dist/chunk-LTFH7SEG.js +0 -373
- package/dist/chunk-LTFH7SEG.js.map +0 -1
- package/dist/chunk-MUNOKLLE.js +0 -165
- package/dist/chunk-MUNOKLLE.js.map +0 -1
- package/dist/chunk-MXGLYWVP.cjs +0 -351
- package/dist/chunk-MXGLYWVP.cjs.map +0 -1
- package/dist/chunk-OP6PHB2U.js +0 -81
- package/dist/chunk-OP6PHB2U.js.map +0 -1
- package/dist/chunk-OYC34VTO.cjs +0 -3957
- package/dist/chunk-OYC34VTO.cjs.map +0 -1
- package/dist/chunk-PQL5I3F6.cjs +0 -421
- package/dist/chunk-PQL5I3F6.cjs.map +0 -1
- package/dist/chunk-REZTQ4PH.cjs +0 -488
- package/dist/chunk-REZTQ4PH.cjs.map +0 -1
- package/dist/chunk-TZRUHQOH.cjs +0 -91
- package/dist/chunk-TZRUHQOH.cjs.map +0 -1
- package/dist/chunk-UTYVIOWZ.js +0 -3957
- package/dist/chunk-UTYVIOWZ.js.map +0 -1
- package/dist/chunk-W2LDIX26.cjs +0 -165
- package/dist/chunk-W2LDIX26.cjs.map +0 -1
- package/dist/chunk-WSG64BVN.cjs +0 -219
- package/dist/chunk-WSG64BVN.cjs.map +0 -1
- package/dist/chunk-WTQYGUNF.js +0 -400
- package/dist/chunk-WTQYGUNF.js.map +0 -1
- package/dist/chunk-WYK7EL5R.cjs +0 -895
- package/dist/chunk-WYK7EL5R.cjs.map +0 -1
- package/dist/chunk-XC4KHJYX.cjs +0 -617
- package/dist/chunk-XC4KHJYX.cjs.map +0 -1
- package/dist/chunk-XVBH5XHE.cjs +0 -400
- package/dist/chunk-XVBH5XHE.cjs.map +0 -1
- package/dist/chunk-XWYZIFZW.js +0 -66
- package/dist/chunk-XWYZIFZW.js.map +0 -1
- package/dist/chunk-Y3NNQMAX.cjs +0 -194
- package/dist/chunk-Y3NNQMAX.cjs.map +0 -1
- package/dist/chunk-YAFQVVDI.js +0 -488
- package/dist/chunk-YAFQVVDI.js.map +0 -1
- package/dist/components/legend/index.cjs +0 -12
- package/dist/components/legend/index.cjs.map +0 -1
- package/dist/components/legend/index.css +0 -91
- package/dist/components/legend/index.css.map +0 -1
- package/dist/components/legend/index.d.cts +0 -37
- package/dist/components/legend/index.d.ts +0 -37
- package/dist/components/legend/index.js +0 -12
- package/dist/components/legend/index.js.map +0 -1
- package/dist/components/tooltip/index.cjs +0 -12
- package/dist/components/tooltip/index.cjs.map +0 -1
- package/dist/components/tooltip/index.css +0 -13
- package/dist/components/tooltip/index.css.map +0 -1
- package/dist/components/tooltip/index.d.cts +0 -71
- package/dist/components/tooltip/index.d.ts +0 -71
- package/dist/components/tooltip/index.js +0 -12
- package/dist/components/tooltip/index.js.map +0 -1
- package/dist/components/trend-indicator/index.cjs +0 -8
- package/dist/components/trend-indicator/index.cjs.map +0 -1
- package/dist/components/trend-indicator/index.css +0 -27
- package/dist/components/trend-indicator/index.css.map +0 -1
- package/dist/components/trend-indicator/index.d.cts +0 -44
- package/dist/components/trend-indicator/index.d.ts +0 -44
- package/dist/components/trend-indicator/index.js +0 -8
- package/dist/components/trend-indicator/index.js.map +0 -1
- package/dist/format-metric-value-MXm5DtQ_.d.cts +0 -24
- package/dist/format-metric-value-MXm5DtQ_.d.ts +0 -24
- package/dist/hooks/index.cjs +0 -29
- package/dist/hooks/index.cjs.map +0 -1
- package/dist/hooks/index.css +0 -9
- package/dist/hooks/index.css.map +0 -1
- package/dist/hooks/index.d.cts +0 -264
- package/dist/hooks/index.d.ts +0 -264
- package/dist/hooks/index.js +0 -29
- package/dist/hooks/index.js.map +0 -1
- package/dist/leaderboard-chart-BSbg0ufV.d.cts +0 -79
- package/dist/leaderboard-chart-odEYxxEC.d.ts +0 -79
- package/dist/legend-DFkosEvC.d.cts +0 -9
- package/dist/legend-DLswHhOk.d.ts +0 -9
- package/dist/providers/index.cjs +0 -21
- package/dist/providers/index.cjs.map +0 -1
- package/dist/providers/index.css +0 -9
- package/dist/providers/index.css.map +0 -1
- package/dist/providers/index.d.cts +0 -28
- package/dist/providers/index.d.ts +0 -28
- package/dist/providers/index.js +0 -21
- package/dist/providers/index.js.map +0 -1
- package/dist/themes-D0qc5JaW.d.cts +0 -67
- package/dist/themes-itO4Ht5g.d.ts +0 -67
- package/dist/types-B5f6XQ7Q.d.cts +0 -19
- package/dist/types-BsHooDbM.d.ts +0 -19
- package/dist/types-BuSrRM4p.d.ts +0 -49
- package/dist/types-ChOUI9-N.d.cts +0 -545
- package/dist/types-ChOUI9-N.d.ts +0 -545
- package/dist/types-Dfw9VOKI.d.cts +0 -49
- package/dist/utils/index.cjs +0 -44
- package/dist/utils/index.cjs.map +0 -1
- package/dist/utils/index.d.cts +0 -226
- package/dist/utils/index.d.ts +0 -226
- package/dist/utils/index.js +0 -44
- package/dist/utils/index.js.map +0 -1
- package/dist/with-responsive-CNfhzAUu.d.cts +0 -18
- package/dist/with-responsive-CNfhzAUu.d.ts +0 -18
|
@@ -7,8 +7,8 @@ describe( 'PieChart', () => {
|
|
|
7
7
|
const defaultProps = {
|
|
8
8
|
size: 500,
|
|
9
9
|
data: [
|
|
10
|
-
{ label: 'A',
|
|
11
|
-
{ label: 'B',
|
|
10
|
+
{ label: 'A', value: 50 },
|
|
11
|
+
{ label: 'B', value: 50 },
|
|
12
12
|
],
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -21,21 +21,21 @@ describe( 'PieChart', () => {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
describe( 'Data Validation', () => {
|
|
24
|
-
test( 'validates total
|
|
24
|
+
test( 'validates total value is greater than 0', () => {
|
|
25
25
|
renderWithTheme( {
|
|
26
26
|
data: [
|
|
27
|
-
{ label: 'A',
|
|
28
|
-
{ label: 'B',
|
|
27
|
+
{ label: 'A', value: 0 },
|
|
28
|
+
{ label: 'B', value: 0 },
|
|
29
29
|
],
|
|
30
30
|
} );
|
|
31
|
-
expect( screen.getByText( /invalid
|
|
31
|
+
expect( screen.getByText( /invalid data/i ) ).toBeInTheDocument();
|
|
32
32
|
} );
|
|
33
33
|
|
|
34
34
|
test( 'handles negative values', () => {
|
|
35
35
|
renderWithTheme( {
|
|
36
36
|
data: [
|
|
37
|
-
{ label: 'A',
|
|
38
|
-
{ label: 'B',
|
|
37
|
+
{ label: 'A', value: -30 },
|
|
38
|
+
{ label: 'B', value: 130 },
|
|
39
39
|
],
|
|
40
40
|
} );
|
|
41
41
|
expect( screen.getByText( /invalid data/i ) ).toBeInTheDocument();
|
|
@@ -48,7 +48,7 @@ describe( 'PieChart', () => {
|
|
|
48
48
|
|
|
49
49
|
test( 'handles single data point', () => {
|
|
50
50
|
renderWithTheme( {
|
|
51
|
-
data: [ { label: 'A',
|
|
51
|
+
data: [ { label: 'A', value: 100 } ],
|
|
52
52
|
} );
|
|
53
53
|
// Use getAllByText since 'A' appears in both chart and legend
|
|
54
54
|
const labels = screen.getAllByText( 'A' );
|
|
@@ -157,10 +157,11 @@ describe( 'PieChart', () => {
|
|
|
157
157
|
} );
|
|
158
158
|
|
|
159
159
|
describe( 'Legend Value Display', () => {
|
|
160
|
+
// Values that give clean percentages: 60/100=60%, 23/100=23%, 17/100=17%
|
|
160
161
|
const testData = [
|
|
161
|
-
{ label: 'Windows', value:
|
|
162
|
-
{ label: 'MacOS', value:
|
|
163
|
-
{ label: 'Linux', value:
|
|
162
|
+
{ label: 'Windows', value: 60, valueDisplay: '60' },
|
|
163
|
+
{ label: 'MacOS', value: 23, valueDisplay: '23' },
|
|
164
|
+
{ label: 'Linux', value: 17, valueDisplay: '17' },
|
|
164
165
|
];
|
|
165
166
|
|
|
166
167
|
test( 'shows percentage values by default when showLegend and showValues are enabled', () => {
|
|
@@ -170,7 +171,7 @@ describe( 'PieChart', () => {
|
|
|
170
171
|
// legendValueDisplay defaults to 'percentage'
|
|
171
172
|
} );
|
|
172
173
|
|
|
173
|
-
// Should display
|
|
174
|
+
// Should display calculated percentages from values
|
|
174
175
|
expect( screen.getByText( '60%' ) ).toBeInTheDocument();
|
|
175
176
|
expect( screen.getByText( '23%' ) ).toBeInTheDocument();
|
|
176
177
|
expect( screen.getByText( '17%' ) ).toBeInTheDocument();
|
|
@@ -184,9 +185,9 @@ describe( 'PieChart', () => {
|
|
|
184
185
|
} );
|
|
185
186
|
|
|
186
187
|
// Should display localized numeric values
|
|
187
|
-
expect( screen.getByText( '
|
|
188
|
-
expect( screen.getByText( '
|
|
189
|
-
expect( screen.getByText( '
|
|
188
|
+
expect( screen.getByText( '60' ) ).toBeInTheDocument();
|
|
189
|
+
expect( screen.getByText( '23' ) ).toBeInTheDocument();
|
|
190
|
+
expect( screen.getByText( '17' ) ).toBeInTheDocument();
|
|
190
191
|
} );
|
|
191
192
|
|
|
192
193
|
test( 'shows formatted values when legendValueDisplay is set to "valueDisplay"', () => {
|
|
@@ -197,9 +198,9 @@ describe( 'PieChart', () => {
|
|
|
197
198
|
} );
|
|
198
199
|
|
|
199
200
|
// Should display formatted values (valueDisplay field)
|
|
200
|
-
expect( screen.getByText( '
|
|
201
|
-
expect( screen.getByText( '
|
|
202
|
-
expect( screen.getByText( '
|
|
201
|
+
expect( screen.getByText( '60' ) ).toBeInTheDocument();
|
|
202
|
+
expect( screen.getByText( '23' ) ).toBeInTheDocument();
|
|
203
|
+
expect( screen.getByText( '17' ) ).toBeInTheDocument();
|
|
203
204
|
} );
|
|
204
205
|
|
|
205
206
|
test( 'shows no values when legendValueDisplay is set to "none"', () => {
|
|
@@ -222,9 +223,12 @@ describe( 'PieChart', () => {
|
|
|
222
223
|
} );
|
|
223
224
|
|
|
224
225
|
describe( 'Tooltip Functionality', () => {
|
|
226
|
+
// Values: 80000 + 30000 = 110000
|
|
227
|
+
// Windows: 80000/110000 = 72.727...%
|
|
228
|
+
// MacOS: 30000/110000 = 27.272...%
|
|
225
229
|
const testData = [
|
|
226
|
-
{ label: 'Windows', value: 80000, valueDisplay: '80K'
|
|
227
|
-
{ label: 'MacOS', value: 30000, valueDisplay: '30K'
|
|
230
|
+
{ label: 'Windows', value: 80000, valueDisplay: '80K' },
|
|
231
|
+
{ label: 'MacOS', value: 30000, valueDisplay: '30K' },
|
|
228
232
|
];
|
|
229
233
|
|
|
230
234
|
test( 'does not show tooltip when withTooltips is false', async () => {
|
|
@@ -314,7 +318,7 @@ describe( 'PieChart', () => {
|
|
|
314
318
|
|
|
315
319
|
test( 'tooltip shows valueDisplay when available, falls back to value', async () => {
|
|
316
320
|
const user = userEvent.setup();
|
|
317
|
-
const dataWithoutValueDisplay = [ { label: 'Test', value: 42
|
|
321
|
+
const dataWithoutValueDisplay = [ { label: 'Test', value: 42 } ];
|
|
318
322
|
|
|
319
323
|
renderWithTheme( {
|
|
320
324
|
data: dataWithoutValueDisplay,
|
|
@@ -356,15 +360,18 @@ describe( 'PieChart', () => {
|
|
|
356
360
|
expect( customTooltipRenderer ).toHaveBeenCalled();
|
|
357
361
|
|
|
358
362
|
// Verify the renderer received correct parameters
|
|
363
|
+
// Percentage is calculated from values: 80000 / (80000 + 30000) = 72.727...%
|
|
359
364
|
expect( customTooltipRenderer ).toHaveBeenCalledWith(
|
|
360
365
|
expect.objectContaining( {
|
|
361
366
|
tooltipData: expect.objectContaining( {
|
|
362
367
|
label: 'Windows',
|
|
363
368
|
value: 80000,
|
|
364
|
-
percentage: 70,
|
|
365
369
|
} ),
|
|
366
370
|
} )
|
|
367
371
|
);
|
|
372
|
+
// Verify percentage is calculated (approximately 72.73%)
|
|
373
|
+
const callArgs = customTooltipRenderer.mock.calls[ 0 ][ 0 ];
|
|
374
|
+
expect( callArgs.tooltipData.percentage ).toBeCloseTo( 72.73, 1 );
|
|
368
375
|
} );
|
|
369
376
|
} );
|
|
370
377
|
|
|
@@ -372,8 +379,8 @@ describe( 'PieChart', () => {
|
|
|
372
379
|
test( 'filters segments when interactive legend is enabled and segment is toggled', async () => {
|
|
373
380
|
const user = userEvent.setup();
|
|
374
381
|
const testData = [
|
|
375
|
-
{ label: 'Segment A', value: 50
|
|
376
|
-
{ label: 'Segment B', value: 50
|
|
382
|
+
{ label: 'Segment A', value: 50 },
|
|
383
|
+
{ label: 'Segment B', value: 50 },
|
|
377
384
|
];
|
|
378
385
|
|
|
379
386
|
renderWithTheme( {
|
|
@@ -404,8 +411,8 @@ describe( 'PieChart', () => {
|
|
|
404
411
|
test( 'shows empty state when all segments are hidden', async () => {
|
|
405
412
|
const user = userEvent.setup();
|
|
406
413
|
const testData = [
|
|
407
|
-
{ label: 'Segment A', value: 50
|
|
408
|
-
{ label: 'Segment B', value: 50
|
|
414
|
+
{ label: 'Segment A', value: 50 },
|
|
415
|
+
{ label: 'Segment B', value: 50 },
|
|
409
416
|
];
|
|
410
417
|
|
|
411
418
|
renderWithTheme( {
|
|
@@ -443,8 +450,8 @@ describe( 'PieChart', () => {
|
|
|
443
450
|
|
|
444
451
|
test( 'does not filter segments when legendInteractive is false', () => {
|
|
445
452
|
const testData = [
|
|
446
|
-
{ label: 'Segment A', value: 50
|
|
447
|
-
{ label: 'Segment B', value: 50
|
|
453
|
+
{ label: 'Segment A', value: 50 },
|
|
454
|
+
{ label: 'Segment B', value: 50 },
|
|
448
455
|
];
|
|
449
456
|
|
|
450
457
|
renderWithTheme( {
|
|
@@ -466,9 +473,9 @@ describe( 'PieChart', () => {
|
|
|
466
473
|
test( 'maintains consistent colors when segments are hidden', async () => {
|
|
467
474
|
const user = userEvent.setup();
|
|
468
475
|
const testData = [
|
|
469
|
-
{ label: 'Segment A', value: 30
|
|
470
|
-
{ label: 'Segment B', value: 40
|
|
471
|
-
{ label: 'Segment C', value: 30
|
|
476
|
+
{ label: 'Segment A', value: 30 },
|
|
477
|
+
{ label: 'Segment B', value: 40 },
|
|
478
|
+
{ label: 'Segment C', value: 30 },
|
|
472
479
|
];
|
|
473
480
|
|
|
474
481
|
renderWithTheme( {
|
|
@@ -498,9 +505,9 @@ describe( 'PieChart', () => {
|
|
|
498
505
|
test( 'recalculates legend percentages when segments are hidden', async () => {
|
|
499
506
|
const user = userEvent.setup();
|
|
500
507
|
const testData = [
|
|
501
|
-
{ label: 'Segment A', value: 25
|
|
502
|
-
{ label: 'Segment B', value: 50
|
|
503
|
-
{ label: 'Segment C', value: 25
|
|
508
|
+
{ label: 'Segment A', value: 25 },
|
|
509
|
+
{ label: 'Segment B', value: 50 },
|
|
510
|
+
{ label: 'Segment C', value: 25 },
|
|
504
511
|
];
|
|
505
512
|
|
|
506
513
|
renderWithTheme( {
|
|
@@ -5,15 +5,9 @@
|
|
|
5
5
|
width: 100%;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
&__svg-wrapper {
|
|
10
|
-
flex: 1;
|
|
11
|
-
min-height: 0; // Required for flex shrinking
|
|
12
|
-
min-width: 0; // Required for flex shrinking
|
|
8
|
+
&__centering {
|
|
13
9
|
width: 100%;
|
|
14
|
-
|
|
15
|
-
align-items: center;
|
|
16
|
-
justify-content: center;
|
|
10
|
+
height: 100%;
|
|
17
11
|
}
|
|
18
12
|
|
|
19
13
|
.label {
|
|
@@ -8,7 +8,11 @@ import clsx from 'clsx';
|
|
|
8
8
|
import { useCallback, useContext, useMemo } from 'react';
|
|
9
9
|
import { Legend, useChartLegendItems } from '../../components/legend';
|
|
10
10
|
import { BaseTooltip } from '../../components/tooltip';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
useDataWithPercentages,
|
|
13
|
+
useInteractiveLegendData,
|
|
14
|
+
usePrefersReducedMotion,
|
|
15
|
+
} from '../../hooks';
|
|
12
16
|
import {
|
|
13
17
|
GlobalChartsProvider,
|
|
14
18
|
useChartId,
|
|
@@ -17,18 +21,20 @@ import {
|
|
|
17
21
|
GlobalChartsContext,
|
|
18
22
|
} from '../../providers';
|
|
19
23
|
import { attachSubComponents } from '../../utils';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
ChartHTML,
|
|
23
|
-
useChartChildren,
|
|
24
|
-
renderLegendSlot,
|
|
25
|
-
} from '../private/chart-composition';
|
|
24
|
+
import { ChartSVG, ChartHTML, useChartChildren } from '../private/chart-composition';
|
|
25
|
+
import { ChartLayout } from '../private/chart-layout';
|
|
26
26
|
import { RadialWipeAnimation } from '../private/radial-wipe-animation';
|
|
27
27
|
import { SingleChartContext } from '../private/single-chart-context';
|
|
28
|
+
import { SvgEmptyState } from '../private/svg-empty-state';
|
|
28
29
|
import { withResponsive } from '../private/with-responsive';
|
|
29
30
|
import styles from './pie-semi-circle-chart.module.scss';
|
|
30
31
|
import type { LegendValueDisplay } from '../../components/legend';
|
|
31
|
-
import type {
|
|
32
|
+
import type {
|
|
33
|
+
BaseChartProps,
|
|
34
|
+
DataPointPercentage,
|
|
35
|
+
DataPointPercentageCalculated,
|
|
36
|
+
Optional,
|
|
37
|
+
} from '../../types';
|
|
32
38
|
import type { ChartComponentWithComposition } from '../private/chart-composition';
|
|
33
39
|
import type { ResponsiveConfig } from '../private/with-responsive';
|
|
34
40
|
import type { PieArcDatum } from '@visx/shape/lib/shapes/Pie';
|
|
@@ -39,9 +45,9 @@ import type { FC, MouseEvent, ReactNode } from 'react';
|
|
|
39
45
|
*/
|
|
40
46
|
export type PieSemiCircleChartRenderTooltipParams = {
|
|
41
47
|
/**
|
|
42
|
-
* The data point being hovered, including label, value, and percentage.
|
|
48
|
+
* The data point being hovered, including label, value, and calculated percentage.
|
|
43
49
|
*/
|
|
44
|
-
tooltipData:
|
|
50
|
+
tooltipData: DataPointPercentageCalculated;
|
|
45
51
|
};
|
|
46
52
|
|
|
47
53
|
/**
|
|
@@ -129,7 +135,7 @@ type PieSemiCircleChartResponsiveComponent = ChartComponentWithComposition<
|
|
|
129
135
|
PieSemiCircleChartBaseProps & ResponsiveConfig
|
|
130
136
|
>;
|
|
131
137
|
|
|
132
|
-
export type ArcData = PieArcDatum<
|
|
138
|
+
export type ArcData = PieArcDatum< DataPointPercentageCalculated >;
|
|
133
139
|
|
|
134
140
|
/**
|
|
135
141
|
* Validates the semi-circle pie chart data
|
|
@@ -142,15 +148,15 @@ const validateData = ( data: DataPointPercentage[] ) => {
|
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
// Check for negative values
|
|
145
|
-
const hasNegativeValues = data.some( item => item.
|
|
151
|
+
const hasNegativeValues = data.some( item => item.value < 0 );
|
|
146
152
|
if ( hasNegativeValues ) {
|
|
147
153
|
return { isValid: false, message: 'Invalid data: Negative values are not allowed' };
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
// Validate total
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
return { isValid: false, message: 'Invalid
|
|
156
|
+
// Validate total value is greater than 0
|
|
157
|
+
const totalValue = data.reduce( ( sum, item ) => sum + item.value, 0 );
|
|
158
|
+
if ( totalValue <= 0 ) {
|
|
159
|
+
return { isValid: false, message: 'Invalid data: Total value must be greater than 0' };
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
return { isValid: true, message: '' };
|
|
@@ -181,10 +187,8 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
181
187
|
const legendPosition = legend.position ?? 'bottom';
|
|
182
188
|
|
|
183
189
|
const chartId = useChartId( providedChartId );
|
|
184
|
-
// Measure the SVG wrapper to calculate constrained dimensions
|
|
185
|
-
const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
|
|
186
190
|
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
|
|
187
|
-
useTooltip<
|
|
191
|
+
useTooltip< DataPointPercentageCalculated >();
|
|
188
192
|
|
|
189
193
|
// Set up portal tooltip for better z-index handling
|
|
190
194
|
// We get containerBounds to cancel out stale offsets in the position calculation
|
|
@@ -239,9 +243,12 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
239
243
|
|
|
240
244
|
const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
|
|
241
245
|
|
|
246
|
+
// Calculate percentages from values (single source of truth)
|
|
247
|
+
const dataWithPercentages = useDataWithPercentages( data );
|
|
248
|
+
|
|
242
249
|
// Filter and recalculate data for interactive legends
|
|
243
250
|
const { visibleData, allSegmentsHidden, legendData } = useInteractiveLegendData( {
|
|
244
|
-
data,
|
|
251
|
+
data: dataWithPercentages,
|
|
245
252
|
chartId,
|
|
246
253
|
legendInteractive,
|
|
247
254
|
isSeriesVisible,
|
|
@@ -250,12 +257,12 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
250
257
|
// Define accessors with useMemo to avoid changing dependencies
|
|
251
258
|
const accessors = useMemo(
|
|
252
259
|
() => ( {
|
|
253
|
-
value: ( d:
|
|
260
|
+
value: ( d: DataPointPercentageCalculated ) => d.value,
|
|
254
261
|
sort: (
|
|
255
|
-
a:
|
|
256
|
-
b:
|
|
262
|
+
a: DataPointPercentageCalculated & { index: number },
|
|
263
|
+
b: DataPointPercentageCalculated & { index: number }
|
|
257
264
|
) => b.value - a.value,
|
|
258
|
-
fill: ( d:
|
|
265
|
+
fill: ( d: DataPointPercentageCalculated & { index: number } ) =>
|
|
259
266
|
getElementStyles( { data: d, index: d.index } ).color,
|
|
260
267
|
} ),
|
|
261
268
|
[ getElementStyles ]
|
|
@@ -315,18 +322,6 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
315
322
|
);
|
|
316
323
|
}
|
|
317
324
|
|
|
318
|
-
// Calculate chart dimensions maintaining the 2:1 width-to-height ratio.
|
|
319
|
-
// Use measured SVG wrapper dimensions to respect height constraints, falling back
|
|
320
|
-
// to explicit props during initial render before measurement is available.
|
|
321
|
-
const availableWidth = svgWrapperWidth > 0 ? svgWrapperWidth : effectiveWidth;
|
|
322
|
-
const availableHeight =
|
|
323
|
-
svgWrapperHeight > 0 ? svgWrapperHeight : propHeight || effectiveWidth / 2;
|
|
324
|
-
// Constrain width so that height (= width / 2) never exceeds the available height
|
|
325
|
-
const width = Math.min( availableWidth, availableHeight * 2 );
|
|
326
|
-
const height = width / 2;
|
|
327
|
-
const radius = height; // For a semi-circle, radius equals the SVG height
|
|
328
|
-
const innerRadius = radius * ( 1 - thickness );
|
|
329
|
-
|
|
330
325
|
// Map data with index for color assignment
|
|
331
326
|
// When interactive, we need to find the original index to maintain consistent colors
|
|
332
327
|
const dataWithIndex = visibleData.map( d => {
|
|
@@ -357,16 +352,11 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
357
352
|
);
|
|
358
353
|
|
|
359
354
|
return (
|
|
360
|
-
<SingleChartContext.Provider
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
} }
|
|
366
|
-
>
|
|
367
|
-
<Stack
|
|
368
|
-
ref={ containerRef }
|
|
369
|
-
direction="column"
|
|
355
|
+
<SingleChartContext.Provider value={ { chartId } }>
|
|
356
|
+
<ChartLayout
|
|
357
|
+
legendPosition={ legendPosition }
|
|
358
|
+
legendElement={ legendElement }
|
|
359
|
+
legendChildren={ legendChildren }
|
|
370
360
|
gap={ gap }
|
|
371
361
|
className={ clsx(
|
|
372
362
|
'pie-semi-circle-chart',
|
|
@@ -381,120 +371,130 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
381
371
|
height: propHeight || undefined,
|
|
382
372
|
} }
|
|
383
373
|
data-testid="pie-chart-container"
|
|
374
|
+
trailingContent={
|
|
375
|
+
<>
|
|
376
|
+
{ withTooltips && tooltipOpen && tooltipData && (
|
|
377
|
+
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
378
|
+
<div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
|
|
379
|
+
</TooltipInPortal>
|
|
380
|
+
) }
|
|
381
|
+
{ htmlChildren }
|
|
382
|
+
{ otherChildren }
|
|
383
|
+
</>
|
|
384
|
+
}
|
|
384
385
|
>
|
|
385
|
-
{
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
{ /* Main chart group centered horizontally and positioned at bottom */ }
|
|
406
|
-
<Group
|
|
407
|
-
top={ height }
|
|
408
|
-
left={ width / 2 }
|
|
409
|
-
mask={ animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null }
|
|
386
|
+
{ ( { contentWidth, contentHeight } ) => {
|
|
387
|
+
// Calculate chart dimensions maintaining the 2:1 width-to-height ratio.
|
|
388
|
+
// Use measured dimensions to respect height constraints, falling back
|
|
389
|
+
// to explicit props during initial render before measurement is available.
|
|
390
|
+
const availableWidth = contentWidth > 0 ? contentWidth : effectiveWidth;
|
|
391
|
+
const availableHeight =
|
|
392
|
+
contentHeight > 0 ? contentHeight : propHeight || effectiveWidth / 2;
|
|
393
|
+
// Constrain width so that height (= width / 2) never exceeds the available height
|
|
394
|
+
const width = Math.min( availableWidth, availableHeight * 2 );
|
|
395
|
+
const height = width / 2;
|
|
396
|
+
const radius = height; // For a semi-circle, radius equals the SVG height
|
|
397
|
+
const innerRadius = radius * ( 1 - thickness );
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<Stack
|
|
401
|
+
ref={ containerRef }
|
|
402
|
+
align="center"
|
|
403
|
+
justify="center"
|
|
404
|
+
className={ styles[ 'pie-semi-circle-chart__centering' ] }
|
|
410
405
|
>
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
'jetpack-charts'
|
|
422
|
-
) }
|
|
423
|
-
</text>
|
|
424
|
-
) : (
|
|
425
|
-
<>
|
|
426
|
-
{ /* Pie chart */ }
|
|
427
|
-
<Pie< DataPointPercentage & { index: number } >
|
|
428
|
-
data={ dataWithIndex }
|
|
429
|
-
pieValue={ accessors.value }
|
|
430
|
-
outerRadius={ radius }
|
|
406
|
+
<svg
|
|
407
|
+
width={ width }
|
|
408
|
+
height={ height }
|
|
409
|
+
viewBox={ `0 0 ${ width } ${ height }` }
|
|
410
|
+
data-testid="pie-chart-svg"
|
|
411
|
+
>
|
|
412
|
+
<defs>
|
|
413
|
+
<RadialWipeAnimation
|
|
414
|
+
id={ `radial-wipe-${ chartId }` }
|
|
415
|
+
radius={ radius }
|
|
431
416
|
innerRadius={ innerRadius }
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
417
|
+
startAngle="-180deg"
|
|
418
|
+
wipePercentage={ 50 }
|
|
419
|
+
/>
|
|
420
|
+
</defs>
|
|
421
|
+
|
|
422
|
+
{ /* Main chart group centered horizontally and positioned at bottom */ }
|
|
423
|
+
<Group
|
|
424
|
+
top={ height }
|
|
425
|
+
left={ width / 2 }
|
|
426
|
+
mask={
|
|
427
|
+
animation && ! prefersReducedMotion ? `url(#radial-wipe-${ chartId })` : null
|
|
428
|
+
}
|
|
429
|
+
>
|
|
430
|
+
{ allSegmentsHidden ? (
|
|
431
|
+
<SvgEmptyState x={ 0 } y={ -radius / 2 } width={ width } height={ height }>
|
|
432
|
+
{ __(
|
|
433
|
+
'All segments are hidden. Click legend items to show data.',
|
|
434
|
+
'jetpack-charts'
|
|
435
|
+
) }
|
|
436
|
+
</SvgEmptyState>
|
|
437
|
+
) : (
|
|
438
|
+
<>
|
|
439
|
+
{ /* Pie chart */ }
|
|
440
|
+
<Pie< DataPointPercentageCalculated & { index: number } >
|
|
441
|
+
data={ dataWithIndex }
|
|
442
|
+
pieValue={ accessors.value }
|
|
443
|
+
outerRadius={ radius }
|
|
444
|
+
innerRadius={ innerRadius }
|
|
445
|
+
cornerRadius={ 3 }
|
|
446
|
+
padAngle={ PAD_ANGLE }
|
|
447
|
+
startAngle={ startAngle }
|
|
448
|
+
endAngle={ endAngle }
|
|
449
|
+
pieSort={ accessors.sort }
|
|
450
|
+
>
|
|
451
|
+
{ pie => {
|
|
452
|
+
return pie.arcs.map( arc => (
|
|
453
|
+
<g
|
|
454
|
+
key={ arc.data.label }
|
|
455
|
+
onMouseMove={ withTooltips ? handleArcMouseMove( arc ) : undefined }
|
|
456
|
+
onMouseLeave={ withTooltips ? handleMouseLeave : undefined }
|
|
457
|
+
>
|
|
458
|
+
<path
|
|
459
|
+
d={ pie.path( arc ) || '' }
|
|
460
|
+
fill={ accessors.fill( arc.data ) }
|
|
461
|
+
data-testid="pie-segment"
|
|
462
|
+
/>
|
|
463
|
+
</g>
|
|
464
|
+
) );
|
|
465
|
+
} }
|
|
466
|
+
</Pie>
|
|
467
|
+
|
|
468
|
+
{ /* Label and note text */ }
|
|
469
|
+
<Group>
|
|
470
|
+
<Text
|
|
471
|
+
textAnchor="middle"
|
|
472
|
+
verticalAnchor="start"
|
|
473
|
+
y={ -40 } // Position above the chart with space for note
|
|
474
|
+
className={ styles.label }
|
|
444
475
|
>
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
<Group>
|
|
457
|
-
<Text
|
|
458
|
-
textAnchor="middle"
|
|
459
|
-
verticalAnchor="start"
|
|
460
|
-
y={ -40 } // Position above the chart with space for note
|
|
461
|
-
className={ styles.label }
|
|
462
|
-
>
|
|
463
|
-
{ label }
|
|
464
|
-
</Text>
|
|
465
|
-
<Text
|
|
466
|
-
textAnchor="middle"
|
|
467
|
-
verticalAnchor="start"
|
|
468
|
-
y={ -20 } // Position between label and chart
|
|
469
|
-
className={ styles.note }
|
|
470
|
-
>
|
|
471
|
-
{ note }
|
|
472
|
-
</Text>
|
|
473
|
-
</Group>
|
|
474
|
-
|
|
475
|
-
{ /* Render SVG children from composition API */ }
|
|
476
|
-
{ ! allSegmentsHidden && svgChildren }
|
|
477
|
-
</>
|
|
478
|
-
) }
|
|
479
|
-
</Group>
|
|
480
|
-
</svg>
|
|
481
|
-
</div>
|
|
482
|
-
|
|
483
|
-
{ legendPosition === 'bottom' && legendElement }
|
|
484
|
-
{ renderLegendSlot( legendChildren, 'bottom' ) }
|
|
485
|
-
|
|
486
|
-
{ withTooltips && tooltipOpen && tooltipData && (
|
|
487
|
-
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
488
|
-
<div role="tooltip">{ renderTooltip( { tooltipData } ) }</div>
|
|
489
|
-
</TooltipInPortal>
|
|
490
|
-
) }
|
|
491
|
-
|
|
492
|
-
{ /* Render HTML children from composition API */ }
|
|
493
|
-
{ htmlChildren }
|
|
476
|
+
{ label }
|
|
477
|
+
</Text>
|
|
478
|
+
<Text
|
|
479
|
+
textAnchor="middle"
|
|
480
|
+
verticalAnchor="start"
|
|
481
|
+
y={ -20 } // Position between label and chart
|
|
482
|
+
className={ styles.note }
|
|
483
|
+
>
|
|
484
|
+
{ note }
|
|
485
|
+
</Text>
|
|
486
|
+
</Group>
|
|
494
487
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
488
|
+
{ /* Render SVG children from composition API */ }
|
|
489
|
+
{ ! allSegmentsHidden && svgChildren }
|
|
490
|
+
</>
|
|
491
|
+
) }
|
|
492
|
+
</Group>
|
|
493
|
+
</svg>
|
|
494
|
+
</Stack>
|
|
495
|
+
);
|
|
496
|
+
} }
|
|
497
|
+
</ChartLayout>
|
|
498
498
|
</SingleChartContext.Provider>
|
|
499
499
|
);
|
|
500
500
|
};
|