Skip to content

Commit 969256b

Browse files
authored
chore(chart legend): convert to typescript (#11823)
* chore(chart legend): convert to typescript * converted interactive examples * updated chartlegend.md * converted remaining examples * updated usage of hooks and fixed the tooltip example
1 parent 23d5f17 commit 969256b

10 files changed

+784
-682
lines changed

packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md

Lines changed: 17 additions & 682 deletions
Large diffs are not rendered by default.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ChartDonut } from '@patternfly/react-charts/victory';
2+
3+
interface PetData {
4+
x?: string;
5+
y?: number;
6+
name?: string;
7+
}
8+
9+
export const ChartLegendBasicRightLegend: React.FunctionComponent = () => {
10+
const data: PetData[] = [
11+
{ x: 'Cats', y: 35 },
12+
{ x: 'Dogs', y: 55 },
13+
{ x: 'Birds', y: 10 }
14+
];
15+
const legendData: PetData[] = [{ name: 'Cats: 35' }, { name: 'Dogs: 55' }, { name: 'Birds: 10' }];
16+
17+
return (
18+
<div style={{ height: '230px', width: '350px' }}>
19+
<ChartDonut
20+
ariaDesc="Average number of pets"
21+
ariaTitle="Donut chart example"
22+
constrainToVisibleArea
23+
data={data}
24+
labels={({ datum }) => `${datum.x}: ${datum.y}%`}
25+
legendData={legendData}
26+
legendOrientation="vertical"
27+
legendPosition="right"
28+
name="chart1"
29+
padding={{
30+
bottom: 20,
31+
left: 20,
32+
right: 140, // Adjusted to accommodate legend
33+
top: 20
34+
}}
35+
subTitle="Pets"
36+
title="100"
37+
width={350}
38+
/>
39+
</div>
40+
);
41+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
Chart,
3+
ChartAxis,
4+
ChartBar,
5+
ChartGroup,
6+
ChartThemeColor,
7+
ChartVoronoiContainer
8+
} from '@patternfly/react-charts/victory';
9+
10+
interface PetData {
11+
x?: string;
12+
y?: number;
13+
name?: string;
14+
}
15+
16+
export const ChartLegendBottomAlignedLegend: React.FunctionComponent = () => {
17+
const legendData: PetData[] = [{ name: 'Cats' }, { name: 'Dogs' }, { name: 'Birds' }, { name: 'Mice' }];
18+
const data1: PetData[] = [
19+
{ name: 'Cats', x: '2015', y: 1 },
20+
{ name: 'Cats', x: '2016', y: 2 },
21+
{ name: 'Cats', x: '2017', y: 5 },
22+
{ name: 'Cats', x: '2018', y: 3 }
23+
];
24+
const data2: PetData[] = [
25+
{ name: 'Dogs', x: '2015', y: 2 },
26+
{ name: 'Dogs', x: '2016', y: 1 },
27+
{ name: 'Dogs', x: '2017', y: 7 },
28+
{ name: 'Dogs', x: '2018', y: 4 }
29+
];
30+
const data3: PetData[] = [
31+
{ name: 'Birds', x: '2015', y: 4 },
32+
{ name: 'Birds', x: '2016', y: 4 },
33+
{ name: 'Birds', x: '2017', y: 9 },
34+
{ name: 'Birds', x: '2018', y: 7 }
35+
];
36+
const data4: PetData[] = [
37+
{ name: 'Mice', x: '2015', y: 3 },
38+
{ name: 'Mice', x: '2016', y: 3 },
39+
{ name: 'Mice', x: '2017', y: 8 },
40+
{ name: 'Mice', x: '2018', y: 5 }
41+
];
42+
43+
return (
44+
<div style={{ height: '275px', width: '450px' }}>
45+
<Chart
46+
ariaDesc="Average number of pets"
47+
ariaTitle="Bar chart example"
48+
containerComponent={
49+
<ChartVoronoiContainer labels={({ datum }) => `${datum.name}: ${datum.y}`} constrainToVisibleArea />
50+
}
51+
domainPadding={{ x: [30, 25] }}
52+
legendData={legendData}
53+
legendPosition="bottom"
54+
height={275}
55+
name="chart2"
56+
padding={{
57+
bottom: 75, // Adjusted to accommodate legend
58+
left: 50,
59+
right: 50,
60+
top: 50
61+
}}
62+
themeColor={ChartThemeColor.purple}
63+
width={450}
64+
>
65+
<ChartAxis />
66+
<ChartAxis dependentAxis showGrid />
67+
<ChartGroup offset={11}>
68+
<ChartBar data={data1} />
69+
<ChartBar data={data2} />
70+
<ChartBar data={data3} />
71+
<ChartBar data={data4} />
72+
</ChartGroup>
73+
</Chart>
74+
</div>
75+
);
76+
};
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import {
2+
Chart,
3+
ChartArea,
4+
ChartAxis,
5+
ChartGroup,
6+
ChartLegend,
7+
ChartLegendTooltip,
8+
ChartScatter,
9+
ChartThemeColor,
10+
createContainer,
11+
getInteractiveLegendEvents,
12+
getInteractiveLegendItemStyles
13+
} from '@patternfly/react-charts/victory';
14+
import { getResizeObserver } from '@patternfly/react-core';
15+
import { useRef, useState, useEffect } from 'react';
16+
17+
export const ChartLegendInteractive: React.FunctionComponent = () => {
18+
const containerRef = useRef(null);
19+
const [hiddenSeries, setHiddenSeries] = useState(new Set());
20+
const [width, setWidth] = useState(0);
21+
22+
const series = [
23+
{
24+
datapoints: [
25+
{ x: '2015', y: 3 },
26+
{ x: '2016', y: 4 },
27+
{ x: '2017', y: 8 },
28+
{ x: '2018', y: 6 }
29+
],
30+
legendItem: { name: 'Cats' }
31+
},
32+
{
33+
datapoints: [
34+
{ x: '2015', y: 2 },
35+
{ x: '2016', y: 3 },
36+
{ x: '2017', y: 4 },
37+
{ x: '2018', y: 5 },
38+
{ x: '2019', y: 6 }
39+
],
40+
legendItem: { name: 'Dogs' }
41+
},
42+
{
43+
datapoints: [
44+
{ x: '2015', y: 1 },
45+
{ x: '2016', y: 2 },
46+
{ x: '2017', y: 3 },
47+
{ x: '2018', y: 2 },
48+
{ x: '2019', y: 4 }
49+
],
50+
legendItem: { name: 'Birds' }
51+
}
52+
];
53+
54+
// Returns groups of chart names associated with each data series
55+
const getChartNames = () => series.map((_, index) => [`area-${index}`, `scatter-${index}`]);
56+
57+
// Handles legend click to toggle visibility of data series
58+
const handleLegendClick = (props) => {
59+
setHiddenSeries((prev) => {
60+
const newHidden = new Set(prev);
61+
if (!newHidden.delete(props.index)) {
62+
newHidden.add(props.index);
63+
}
64+
return newHidden;
65+
});
66+
};
67+
68+
// Returns legend data styled per hiddenSeries
69+
const getLegendData = () =>
70+
series.map((s, index) => ({
71+
childName: `area-${index}`,
72+
...s.legendItem,
73+
...getInteractiveLegendItemStyles(hiddenSeries.has(index))
74+
}));
75+
76+
// Returns true if data series is hidden
77+
const isHidden = (index) => hiddenSeries.has(index);
78+
79+
// Checks if any data series is visible
80+
const isDataAvailable = () => hiddenSeries.size !== series.length;
81+
82+
// Set chart width per current window size
83+
useEffect(() => {
84+
const observer = getResizeObserver(containerRef.current, () => {
85+
if (containerRef.current?.clientWidth) {
86+
setWidth(containerRef.current.clientWidth);
87+
}
88+
});
89+
return () => observer();
90+
}, []);
91+
92+
// Note: Container order is important
93+
const CursorVoronoiContainer = createContainer('voronoi', 'cursor');
94+
const container = (
95+
<CursorVoronoiContainer
96+
cursorDimension="x"
97+
labels={({ datum }) => (datum.childName.includes('area-') && datum.y !== null ? `${datum.y}` : null)}
98+
labelComponent={<ChartLegendTooltip legendData={getLegendData()} title={(datum) => datum.x} />}
99+
mouseFollowTooltips
100+
voronoiDimension="x"
101+
voronoiPadding={50}
102+
disable={!isDataAvailable()}
103+
/>
104+
);
105+
106+
return (
107+
<div ref={containerRef}>
108+
<div className="area-chart-legend-bottom-responsive">
109+
<Chart
110+
ariaDesc="Average number of pets"
111+
ariaTitle="Area chart example"
112+
containerComponent={container}
113+
events={getInteractiveLegendEvents({
114+
chartNames: getChartNames(),
115+
isHidden,
116+
legendName: 'chart5-ChartLegend',
117+
onLegendClick: handleLegendClick
118+
})}
119+
height={225}
120+
legendComponent={<ChartLegend name={'chart5-ChartLegend'} data={getLegendData()} />}
121+
legendPosition="bottom-left"
122+
name="chart5"
123+
padding={{ bottom: 75, left: 50, right: 50, top: 50 }}
124+
maxDomain={{ y: 9 }}
125+
themeColor={ChartThemeColor.multiUnordered}
126+
width={width}
127+
>
128+
<ChartAxis tickValues={['2015', '2016', '2017', '2018']} />
129+
<ChartAxis dependentAxis showGrid />
130+
<ChartGroup>
131+
{series.map((s, index) => (
132+
<ChartScatter
133+
key={`scatter-${index}`}
134+
name={`scatter-${index}`}
135+
data={!isHidden(index) ? s.datapoints : [{ y: null }]}
136+
size={({ active }) => (active ? 5 : 3)}
137+
/>
138+
))}
139+
</ChartGroup>
140+
<ChartGroup>
141+
{series.map((s, index) => (
142+
<ChartArea
143+
key={`area-${index}`}
144+
name={`area-${index}`}
145+
data={!isHidden(index) ? s.datapoints : [{ y: null }]}
146+
interpolation="monotoneX"
147+
/>
148+
))}
149+
</ChartGroup>
150+
</Chart>
151+
</div>
152+
</div>
153+
);
154+
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {
2+
Chart,
3+
ChartLegend,
4+
ChartThemeColor,
5+
ChartPie,
6+
getInteractiveLegendEvents,
7+
getInteractiveLegendItemStyles
8+
} from '@patternfly/react-charts/victory';
9+
import { useState } from 'react';
10+
11+
export const ChartLegendInteractivePieChart: React.FunctionComponent = () => {
12+
const [hiddenSeries, setHiddenSeries] = useState<Set<number>>(new Set());
13+
14+
const series = [
15+
{
16+
datapoints: { x: 'Cats', y: 35 },
17+
legendItem: { name: 'Cats: 35' }
18+
},
19+
{
20+
datapoints: { x: 'Dogs', y: 55 },
21+
legendItem: { name: 'Dogs: 55' }
22+
},
23+
{
24+
datapoints: { x: 'Birds', y: 10 },
25+
legendItem: { name: 'Birds: 10' }
26+
}
27+
];
28+
29+
// Returns groups of chart names associated with each data series
30+
const getChartNames = () => {
31+
const result = [];
32+
series.map((_, _index) => {
33+
// Provide names for each series hidden / shown -- use the same name for a pie chart
34+
result.push(['pie']);
35+
});
36+
return result;
37+
};
38+
39+
// Returns legend data styled per hiddenSeries
40+
const getLegendData = () =>
41+
series.map((s, index) => ({
42+
...s.legendItem, // name property
43+
...getInteractiveLegendItemStyles(hiddenSeries.has(index)) // hidden styles
44+
}));
45+
46+
// Hide each data series individually
47+
const handleLegendClick = (props: { index: number }) => {
48+
const newHiddenSeries = new Set(hiddenSeries);
49+
if (newHiddenSeries.delete(props.index)) {
50+
newHiddenSeries.add(props.index);
51+
}
52+
setHiddenSeries(newHiddenSeries);
53+
};
54+
55+
// Returns true if data series is hidden
56+
const isHidden = (index: number) => hiddenSeries.has(index);
57+
// Returns onMouseOver, onMouseOut, and onClick events for the interactive legend
58+
const getEvents = () =>
59+
getInteractiveLegendEvents({
60+
chartNames: getChartNames(),
61+
isHidden,
62+
legendName: 'chart6-ChartLegend',
63+
onLegendClick: handleLegendClick
64+
});
65+
66+
const data = [];
67+
series.map((s, index) => {
68+
data.push(!hiddenSeries.has(index) ? s.datapoints : { y: null });
69+
});
70+
71+
return (
72+
<div style={{ height: '275px', width: '300px' }}>
73+
<Chart
74+
ariaDesc="Average number of pets"
75+
ariaTitle="Pie chart example"
76+
events={getEvents()}
77+
height={275}
78+
legendComponent={<ChartLegend name={'chart6-ChartLegend'} data={getLegendData()} />}
79+
legendPosition="bottom"
80+
name="chart6"
81+
padding={{
82+
bottom: 65,
83+
left: 20,
84+
right: 20,
85+
top: 20
86+
}}
87+
showAxis={false}
88+
themeColor={ChartThemeColor.multiUnordered}
89+
width={300}
90+
>
91+
<ChartPie constrainToVisibleArea data={data} labels={({ datum }) => `${datum.x}: ${datum.y}`} name="pie" />
92+
</Chart>
93+
</div>
94+
);
95+
};

0 commit comments

Comments
 (0)