From f6a18a590d97b62addc96545acc94500e76604c8 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Sun, 11 May 2025 16:40:16 +0530 Subject: [PATCH 1/5] chore(chart legend): convert to typescript --- .../ChartLegend/examples/ChartLegend.md | 206 +----------------- .../examples/ChartLegendBasicRightLegend.tsx | 41 ++++ .../ChartLegendBottomAlignedLegend.tsx | 76 +++++++ .../ChartLegendResponsiveBottomLeft.tsx | 82 +++++++ .../examples/ChartLegendStandalone.tsx | 90 ++++++++ 5 files changed, 296 insertions(+), 199 deletions(-) create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBasicRightLegend.tsx create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBottomAlignedLegend.tsx create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendStandalone.tsx diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md index 1386771e2ce..d461b9e8196 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md @@ -53,221 +53,29 @@ The examples below are based on the [Victory](https://formidable.com/open-source ## Examples ### Basic with right aligned legend -```js -import { ChartDonut } from '@patternfly/react-charts/victory'; - -
- `${datum.x}: ${datum.y}%`} - legendData={[{ name: 'Cats: 35' }, { name: 'Dogs: 55' }, { name: 'Birds: 10' }]} - legendOrientation="vertical" - legendPosition="right" - name="chart1" - padding={{ - bottom: 20, - left: 20, - right: 140, // Adjusted to accommodate legend - top: 20 - }} - subTitle="Pets" - title="100" - width={350} - /> -
+```ts file = "ChartLegendBasicRightLegend.tsx" + ``` ### Bottom aligned legend -```js -import { Chart, ChartAxis, ChartBar, ChartGroup, ChartThemeColor, ChartVoronoiContainer } from '@patternfly/react-charts/victory'; - -
- `${datum.name}: ${datum.y}`} constrainToVisibleArea />} - domainPadding={{ x: [30, 25] }} - legendData={[{ name: 'Cats' }, { name: 'Dogs' }, { name: 'Birds' }, { name: 'Mice' }]} - legendPosition="bottom" - height={275} - name="chart2" - padding={{ - bottom: 75, // Adjusted to accommodate legend - left: 50, - right: 50, - top: 50 - }} - themeColor={ChartThemeColor.purple} - width={450} - > - - - - - - - - - -
+```ts file = "ChartLegendBottomAlignedLegend.tsx" + ``` ### Responsive bottom-left aligned legend This demonstrates a responsive legend which wraps when items are wider than its container. -```js -import { ChartBullet } from '@patternfly/react-charts/victory'; -import { getResizeObserver } from '@patternfly/react-core'; - -class BulletChart extends React.Component { - constructor(props) { - super(props); - this.containerRef = createRef(); - this.observer = () => {}; - this.state = { - extraHeight: 0, - width: 0 - }; - this.handleResize = () => { - if (this.containerRef.current && this.containerRef.current.clientWidth) { - this.setState({ width: this.containerRef.current.clientWidth }); - } - }; - this.handleLegendAllowWrap = (extraHeight) => { - if (extraHeight !== this.state.extraHeight) { - this.setState({ extraHeight }); - } - } - this.getHeight = (baseHeight) => { - const { extraHeight } = this.state; - return baseHeight + extraHeight; - }; - } - - componentDidMount() { - this.observer = getResizeObserver(this.containerRef.current, this.handleResize); - this.handleResize(); - } - - componentWillUnmount() { - this.observer(); - } +```ts file = "ChartLegendResponsiveBottomLeft.tsx" - render() { - const { width } = this.state; - const height = this.getHeight(200); - return ( -
- `${datum.name}: ${datum.y}`} - legendAllowWrap={this.handleLegendAllowWrap} - legendPosition="bottom-left" - maxDomain={{y: 100}} - name="chart3" - padding={{ - bottom: 50, - left: 50, - right: 50, - top: 100 // Adjusted to accommodate labels - }} - primarySegmentedMeasureData={[{ name: 'Measure', y: 25 }, { name: 'Measure', y: 60 }]} - primarySegmentedMeasureLegendData={[{ name: 'Measure 1' }, { name: 'Measure 2' }]} - qualitativeRangeData={[{ name: 'Range', y: 50 }, { name: 'Range', y: 75 }]} - qualitativeRangeLegendData={[{ name: 'Range 1' }, { name: 'Range 2' }]} - subTitle="Measure details" - title="Text label" - titlePosition="top-left" - width={width} - /> -
- ); - } -} ``` ### Standalone legend This demonstrates a standalone legend vs. using the `legendData` property. -```js -import { Chart, ChartAxis, ChartGroup, ChartLegend, ChartLine, ChartThemeColor, ChartVoronoiContainer } from '@patternfly/react-charts/victory'; - -
- `${datum.name}: ${datum.y}`} constrainToVisibleArea />} - height={275} - maxDomain={{y: 10}} - minDomain={{y: 0}} - name="chart4" - padding={{ - bottom: 75, // Adjusted to accommodate legend - left: 50, - right: 50, - top: 50 - }} - themeColor={ChartThemeColor.green} - width={450} - > - - - - - - - - - - -
+```ts file = "ChartLegendStandalone.tsx" + ``` ### Interactive legend diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBasicRightLegend.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBasicRightLegend.tsx new file mode 100644 index 00000000000..1ebb5a2c0a0 --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBasicRightLegend.tsx @@ -0,0 +1,41 @@ +import { ChartDonut } from '@patternfly/react-charts/victory'; + +interface PetData { + x?: string; + y?: number; + name?: string; +} + +export const ChartLegendBasicRightLegend: React.FunctionComponent = () => { + const data: PetData[] = [ + { x: 'Cats', y: 35 }, + { x: 'Dogs', y: 55 }, + { x: 'Birds', y: 10 } + ]; + const legendData: PetData[] = [{ name: 'Cats: 35' }, { name: 'Dogs: 55' }, { name: 'Birds: 10' }]; + + return ( +
+ `${datum.x}: ${datum.y}%`} + legendData={legendData} + legendOrientation="vertical" + legendPosition="right" + name="chart1" + padding={{ + bottom: 20, + left: 20, + right: 140, // Adjusted to accommodate legend + top: 20 + }} + subTitle="Pets" + title="100" + width={350} + /> +
+ ); +}; diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBottomAlignedLegend.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBottomAlignedLegend.tsx new file mode 100644 index 00000000000..5b9f412340a --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendBottomAlignedLegend.tsx @@ -0,0 +1,76 @@ +import { + Chart, + ChartAxis, + ChartBar, + ChartGroup, + ChartThemeColor, + ChartVoronoiContainer +} from '@patternfly/react-charts/victory'; + +interface PetData { + x?: string; + y?: number; + name?: string; +} + +export const ChartLegendBottomAlignedLegend: React.FunctionComponent = () => { + const legendData: PetData[] = [{ name: 'Cats' }, { name: 'Dogs' }, { name: 'Birds' }, { name: 'Mice' }]; + const data1: PetData[] = [ + { name: 'Cats', x: '2015', y: 1 }, + { name: 'Cats', x: '2016', y: 2 }, + { name: 'Cats', x: '2017', y: 5 }, + { name: 'Cats', x: '2018', y: 3 } + ]; + const data2: PetData[] = [ + { name: 'Dogs', x: '2015', y: 2 }, + { name: 'Dogs', x: '2016', y: 1 }, + { name: 'Dogs', x: '2017', y: 7 }, + { name: 'Dogs', x: '2018', y: 4 } + ]; + const data3: PetData[] = [ + { name: 'Birds', x: '2015', y: 4 }, + { name: 'Birds', x: '2016', y: 4 }, + { name: 'Birds', x: '2017', y: 9 }, + { name: 'Birds', x: '2018', y: 7 } + ]; + const data4: PetData[] = [ + { name: 'Mice', x: '2015', y: 3 }, + { name: 'Mice', x: '2016', y: 3 }, + { name: 'Mice', x: '2017', y: 8 }, + { name: 'Mice', x: '2018', y: 5 } + ]; + + return ( +
+ `${datum.name}: ${datum.y}`} constrainToVisibleArea /> + } + domainPadding={{ x: [30, 25] }} + legendData={legendData} + legendPosition="bottom" + height={275} + name="chart2" + padding={{ + bottom: 75, // Adjusted to accommodate legend + left: 50, + right: 50, + top: 50 + }} + themeColor={ChartThemeColor.purple} + width={450} + > + + + + + + + + + +
+ ); +}; diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx new file mode 100644 index 00000000000..cd17446e921 --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx @@ -0,0 +1,82 @@ +import { ChartBullet } from '@patternfly/react-charts/victory'; +import { getResizeObserver } from '@patternfly/react-core'; + +interface Data { + name?: string; + y?: number; +} + +export const ChartLegendResponsiveBottomLeft: React.FunctionComponent = () => { + const containerRef = React.useRef(null); + const [extraHeight, setExtraHeight] = React.useState(0); + const [width, setWidth] = React.useState(0); + + const handleResize = () => { + if (containerRef.current && containerRef.current.clientWidth) { + setWidth(containerRef.current.clientWidth); + } + }; + + const handleLegendAllowWrap = (newExtraHeight: number) => { + if (newExtraHeight !== extraHeight) { + setExtraHeight(newExtraHeight); + } + }; + + const getHeight = (baseHeight: number) => baseHeight + extraHeight; + + React.useEffect(() => { + const observer = getResizeObserver(containerRef.current, handleResize); + handleResize(); + + return () => { + observer(); + }; + }, []); + + const comparativeWarningMeasureData: Data[] = [{ name: 'Warning', y: 88 }]; + const comparativeWarningMeasureLegendData: Data[] = [{ name: 'Warning' }]; + const primarySegmentedMeasureData: Data[] = [ + { name: 'Measure', y: 25 }, + { name: 'Measure', y: 60 } + ]; + const primarySegmentedMeasureLegendData: Data[] = [{ name: 'Measure 1' }, { name: 'Measure 2' }]; + const qualitativeRangeData: Data[] = [ + { name: 'Range', y: 50 }, + { name: 'Range', y: 75 } + ]; + const qualitativeRangeLegendData: Data[] = [{ name: 'Range 1' }, { name: 'Range 2' }]; + const height = getHeight(200); + + return ( +
+ `${datum.name}: ${datum.y}`} + legendAllowWrap={handleLegendAllowWrap} + legendPosition="bottom-left" + maxDomain={{ y: 100 }} + name="chart3" + padding={{ + bottom: 50, + left: 50, + right: 50, + top: 100 // Adjusted to accommodate labels + }} + primarySegmentedMeasureData={primarySegmentedMeasureData} + primarySegmentedMeasureLegendData={primarySegmentedMeasureLegendData} + qualitativeRangeData={qualitativeRangeData} + qualitativeRangeLegendData={qualitativeRangeLegendData} + subTitle="Measure details" + title="Text label" + titlePosition="top-left" + width={width} + /> +
+ ); +}; diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendStandalone.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendStandalone.tsx new file mode 100644 index 00000000000..374fa17c231 --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendStandalone.tsx @@ -0,0 +1,90 @@ +import { + Chart, + ChartAxis, + ChartGroup, + ChartLegend, + ChartLine, + ChartThemeColor, + ChartVoronoiContainer +} from '@patternfly/react-charts/victory'; + +interface PetData { + name?: string; + x?: string; + y?: number; + symbol?: { type: string }; +} + +export const ChartLegendStandalone: React.FunctionComponent = () => { + const data1: PetData[] = [ + { name: 'Cats', x: '2015', y: 1 }, + { name: 'Cats', x: '2016', y: 2 }, + { name: 'Cats', x: '2017', y: 5 }, + { name: 'Cats', x: '2018', y: 3 } + ]; + const data2: PetData[] = [ + { name: 'Dogs', x: '2015', y: 2 }, + { name: 'Dogs', x: '2016', y: 1 }, + { name: 'Dogs', x: '2017', y: 7 }, + { name: 'Dogs', x: '2018', y: 4 } + ]; + const data3: PetData[] = [ + { name: 'Birds', x: '2015', y: 3 }, + { name: 'Birds', x: '2016', y: 4 }, + { name: 'Birds', x: '2017', y: 9 }, + { name: 'Birds', x: '2018', y: 5 } + ]; + const data4: PetData[] = [ + { name: 'Mice', x: '2015', y: 3 }, + { name: 'Mice', x: '2016', y: 3 }, + { name: 'Mice', x: '2017', y: 8 }, + { name: 'Mice', x: '2018', y: 7 } + ]; + const data5: PetData[] = [ + { name: 'Cats' }, + { name: 'Dogs', symbol: { type: 'dash' } }, + { name: 'Birds' }, + { name: 'Mice' } + ]; + + return ( +
+ `${datum.name}: ${datum.y}`} constrainToVisibleArea /> + } + height={275} + maxDomain={{ y: 10 }} + minDomain={{ y: 0 }} + name="chart4" + padding={{ + bottom: 75, // Adjusted to accommodate legend + left: 50, + right: 50, + top: 50 + }} + themeColor={ChartThemeColor.green} + width={450} + > + + + + + + + + + + +
+ ); +}; From 7c4c3abd38167f015318e3676a8ff022657991e8 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Sun, 11 May 2025 17:10:21 +0530 Subject: [PATCH 2/5] converted interactive examples --- .../examples/ChartLegendInteractive.tsx | 153 ++++++++++++++++++ .../ChartLegendInteractivePieChart.tsx | 94 +++++++++++ 2 files changed, 247 insertions(+) create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx new file mode 100644 index 00000000000..43ad2e884b0 --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx @@ -0,0 +1,153 @@ +import { + Chart, + ChartArea, + ChartAxis, + ChartGroup, + ChartLegend, + ChartLegendTooltip, + ChartScatter, + ChartThemeColor, + createContainer, + getInteractiveLegendEvents, + getInteractiveLegendItemStyles +} from '@patternfly/react-charts/victory'; +import { getResizeObserver } from '@patternfly/react-core'; + +export const ChartLegendInteractive: React.FunctionComponent = () => { + const containerRef = React.useRef(null); + const [hiddenSeries, setHiddenSeries] = React.useState(new Set()); + const [width, setWidth] = React.useState(0); + + const series = [ + { + datapoints: [ + { x: '2015', y: 3 }, + { x: '2016', y: 4 }, + { x: '2017', y: 8 }, + { x: '2018', y: 6 } + ], + legendItem: { name: 'Cats' } + }, + { + datapoints: [ + { x: '2015', y: 2 }, + { x: '2016', y: 3 }, + { x: '2017', y: 4 }, + { x: '2018', y: 5 }, + { x: '2019', y: 6 } + ], + legendItem: { name: 'Dogs' } + }, + { + datapoints: [ + { x: '2015', y: 1 }, + { x: '2016', y: 2 }, + { x: '2017', y: 3 }, + { x: '2018', y: 2 }, + { x: '2019', y: 4 } + ], + legendItem: { name: 'Birds' } + } + ]; + + // Returns groups of chart names associated with each data series + const getChartNames = () => series.map((_, index) => [`area-${index}`, `scatter-${index}`]); + + // Handles legend click to toggle visibility of data series + const handleLegendClick = (props) => { + setHiddenSeries((prev) => { + const newHidden = new Set(prev); + if (!newHidden.delete(props.index)) { + newHidden.add(props.index); + } + return newHidden; + }); + }; + + // Returns legend data styled per hiddenSeries + const getLegendData = () => + series.map((s, index) => ({ + childName: `area-${index}`, + ...s.legendItem, + ...getInteractiveLegendItemStyles(hiddenSeries.has(index)) + })); + + // Returns true if data series is hidden + const isHidden = (index) => hiddenSeries.has(index); + + // Checks if any data series is visible + const isDataAvailable = () => hiddenSeries.size !== series.length; + + // Set chart width per current window size + useEffect(() => { + const observer = getResizeObserver(containerRef.current, () => { + if (containerRef.current?.clientWidth) { + setWidth(containerRef.current.clientWidth); + } + }); + return () => observer(); + }, []); + + // Note: Container order is important + const CursorVoronoiContainer = createContainer('voronoi', 'cursor'); + const container = ( + (datum.childName.includes('area-') && datum.y !== null ? `${datum.y}` : null)} + labelComponent={ datum.x} />} + mouseFollowTooltips + voronoiDimension="x" + voronoiPadding={50} + disable={!isDataAvailable()} + /> + ); + + return ( +
+
+ } + legendPosition="bottom-left" + name="chart5" + padding={{ bottom: 75, left: 50, right: 50, top: 50 }} + maxDomain={{ y: 9 }} + themeColor={ChartThemeColor.multiUnordered} + width={width} + > + + + + {series.map((s, index) => ( + (active ? 5 : 3)} + /> + ))} + + + {series.map((s, index) => ( + + ))} + + +
+
+ ); +}; diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx new file mode 100644 index 00000000000..be6f7daaf6f --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx @@ -0,0 +1,94 @@ +import { + Chart, + ChartLegend, + ChartThemeColor, + ChartPie, + getInteractiveLegendEvents, + getInteractiveLegendItemStyles +} from '@patternfly/react-charts/victory'; + +export const ChartLegendInteractivePieChart: React.FunctionComponent = () => { + const [hiddenSeries, setHiddenSeries] = React.useState>(new Set()); + + const series = [ + { + datapoints: { x: 'Cats', y: 35 }, + legendItem: { name: 'Cats: 35' } + }, + { + datapoints: { x: 'Dogs', y: 55 }, + legendItem: { name: 'Dogs: 55' } + }, + { + datapoints: { x: 'Birds', y: 10 }, + legendItem: { name: 'Birds: 10' } + } + ]; + + // Returns groups of chart names associated with each data series + const getChartNames = () => { + const result = []; + series.map((_, _index) => { + // Provide names for each series hidden / shown -- use the same name for a pie chart + result.push(['pie']); + }); + return result; + }; + + // Returns legend data styled per hiddenSeries + const getLegendData = () => + series.map((s, index) => ({ + ...s.legendItem, // name property + ...getInteractiveLegendItemStyles(hiddenSeries.has(index)) // hidden styles + })); + + // Hide each data series individually + const handleLegendClick = (props: { index: number }) => { + const newHiddenSeries = new Set(hiddenSeries); + if (newHiddenSeries.delete(props.index)) { + newHiddenSeries.add(props.index); + } + setHiddenSeries(newHiddenSeries); + }; + + // Returns true if data series is hidden + const isHidden = (index: number) => hiddenSeries.has(index); + // Returns onMouseOver, onMouseOut, and onClick events for the interactive legend + const getEvents = () => + getInteractiveLegendEvents({ + chartNames: getChartNames(), + isHidden, + legendName: 'chart6-ChartLegend', + onLegendClick: handleLegendClick + }); + + const data = []; + series.map((s, index) => { + data.push(!hiddenSeries.has(index) ? s.datapoints : { y: null }); + }); + + return ( +
+ } + legendPosition="bottom" + name="chart6" + padding={{ + bottom: 65, + left: 20, + right: 20, + top: 20 + }} + showAxis={false} + themeColor={ChartThemeColor.multiUnordered} + width={300} + > + `${datum.x}: ${datum.y}`} name="pie" /> + +
+ ); +}; From e9ced96a232ad4497e37a8b95ebdc19820f0ebcb Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Sun, 11 May 2025 17:17:50 +0530 Subject: [PATCH 3/5] updated chartlegend.md --- .../ChartLegend/examples/ChartLegend.md | 268 +----------------- 1 file changed, 3 insertions(+), 265 deletions(-) diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md index d461b9e8196..dcc49fda35a 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md @@ -82,278 +82,16 @@ This demonstrates a standalone legend vs. using the `legendData` property. This demonstrates how to add an interactive legend using events such as `onMouseOver`, `onMouseOut`, and `onClick`. -```js -import { - Chart, - ChartArea, - ChartAxis, - ChartGroup, - ChartLegend, - ChartLegendTooltip, - ChartScatter, - ChartThemeColor, - createContainer, - getInteractiveLegendEvents, - getInteractiveLegendItemStyles, -} from '@patternfly/react-charts/victory'; -import { getResizeObserver } from '@patternfly/react-core'; -// import '@patternfly/patternfly/patternfly-charts.css'; // For mixed blend mode - -const InteractiveLegendChart = () => { - const containerRef = useRef(null); - const [hiddenSeries, setHiddenSeries] = useState(new Set()); - const [width, setWidth] = useState(0); - - const series = [ - { - datapoints: [ - { x: '2015', y: 3 }, - { x: '2016', y: 4 }, - { x: '2017', y: 8 }, - { x: '2018', y: 6 } - ], - legendItem: { name: 'Cats' } - }, - { - datapoints: [ - { x: '2015', y: 2 }, - { x: '2016', y: 3 }, - { x: '2017', y: 4 }, - { x: '2018', y: 5 }, - { x: '2019', y: 6 } - ], - legendItem: { name: 'Dogs' } - }, - { - datapoints: [ - { x: '2015', y: 1 }, - { x: '2016', y: 2 }, - { x: '2017', y: 3 }, - { x: '2018', y: 2 }, - { x: '2019', y: 4 } - ], - legendItem: { name: 'Birds' } - } - ]; - - // Returns groups of chart names associated with each data series - const getChartNames = () => series.map((_, index) => [`area-${index}`, `scatter-${index}`]); - - // Handles legend click to toggle visibility of data series - const handleLegendClick = (props) => { - setHiddenSeries((prev) => { - const newHidden = new Set(prev); - if (!newHidden.delete(props.index)) { - newHidden.add(props.index); - } - return newHidden; - }); - }; - - // Returns legend data styled per hiddenSeries - const getLegendData = () => - series.map((s, index) => ({ - childName: `area-${index}`, - ...s.legendItem, - ...getInteractiveLegendItemStyles(hiddenSeries.has(index)) - })); - - // Returns true if data series is hidden - const isHidden = (index) => hiddenSeries.has(index); - - // Checks if any data series is visible - const isDataAvailable = () => hiddenSeries.size !== series.length; - - // Set chart width per current window size - useEffect(() => { - const observer = getResizeObserver(containerRef.current, () => { - if (containerRef.current?.clientWidth) { - setWidth(containerRef.current.clientWidth); - } - }); - return () => observer(); - }, []); - - // Note: Container order is important - const CursorVoronoiContainer = createContainer("voronoi", "cursor"); - const container = ( - datum.childName.includes('area-') && datum.y !== null ? `${datum.y}` : null} - labelComponent={ datum.x} />} - mouseFollowTooltips - voronoiDimension="x" - voronoiPadding={50} - disable={!isDataAvailable()} - /> - ); - - return ( -
-
- } - legendPosition="bottom-left" - name="chart5" - padding={{ bottom: 75, left: 50, right: 50, top: 50 }} - maxDomain={{ y: 9 }} - themeColor={ChartThemeColor.multiUnordered} - width={width} - > - - - - {series.map((s, index) => ( - (active ? 5 : 3)} - /> - ))} - - - {series.map((s, index) => ( - - ))} - - -
-
- ); -}; +```ts file = "ChartLegendInteractive.tsx" + ``` ### Interactive legend with pie chart This demonstrates how to add an interactive legend to a pie chart using events such as `onMouseOver`, `onMouseOut`, and `onClick`. -```js -import { - Chart, - ChartLegend, - ChartThemeColor, - ChartPie, - getInteractiveLegendEvents, - getInteractiveLegendItemStyles -} from '@patternfly/react-charts/victory'; +```ts file = "ChartLegendInteractivePieChart.tsx" -class InteractivePieLegendChart extends React.Component { - constructor(props) { - super(props); - this.state = { - hiddenSeries: new Set(), - width: 0 - }; - this.series = [{ - datapoints: { x: 'Cats', y: 35 }, - legendItem: { name: 'Cats: 35' } - }, { - datapoints: { x: 'Dogs', y: 55 }, - legendItem: { name: 'Dogs: 55' } - }, { - datapoints: { x: 'Birds', y: 10 }, - legendItem: { name: 'Birds: 10' } - }]; - - // Returns groups of chart names associated with each data series - this.getChartNames = () => { - const result = []; - this.series.map((_, index) => { - // Provide names for each series hidden / shown -- use the same name for a pie chart - result.push(['pie']); - }); - return result; - }; - - // Returns onMouseOver, onMouseOut, and onClick events for the interactive legend - this.getEvents = () => getInteractiveLegendEvents({ - chartNames: this.getChartNames(), - isHidden: this.isHidden, - legendName: 'chart6-ChartLegend', - onLegendClick: this.handleLegendClick - }); - - // Returns legend data styled per hiddenSeries - this.getLegendData = () => { - const { hiddenSeries } = this.state; - return this.series.map((s, index) => { - return { - ...s.legendItem, // name property - ...getInteractiveLegendItemStyles(hiddenSeries.has(index)) // hidden styles - }; - }); - }; - - // Hide each data series individually - this.handleLegendClick = (props) => { - if (!this.state.hiddenSeries.delete(props.index)) { - this.state.hiddenSeries.add(props.index); - } - this.setState({ hiddenSeries: new Set(this.state.hiddenSeries) }); - }; - - // Returns true if data series is hidden - this.isHidden = (index) => { - const { hiddenSeries } = this.state; // Skip if already hidden - return hiddenSeries.has(index); - }; - }; - - render() { - const { hiddenSeries, width } = this.state; - - const data = []; - this.series.map((s, index) => { - data.push(!hiddenSeries.has(index) ? s.datapoints : { y: null }); - }); - - return ( -
- } - legendPosition="bottom" - name="chart6" - padding={{ - bottom: 65, - left: 20, - right: 20, - top: 20 - }} - showAxis={false} - themeColor={ChartThemeColor.multiUnordered} - width={300} - > - `${datum.x}: ${datum.y}`} - name="pie" - /> - -
- ); - } -} ``` ### Legend tooltips From 47521eddf00ffe6a9c86739ee166573affce8df6 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Sun, 11 May 2025 18:59:20 +0530 Subject: [PATCH 4/5] converted remaining examples --- .../ChartLegend/examples/ChartLegend.md | 223 +----------------- .../examples/ChartLegendLayout.tsx | 57 +++++ .../ChartLegend/examples/ChartLegendLinks.tsx | 116 +++++++++ .../examples/ChartLegendTooltips.tsx | 51 ++++ 4 files changed, 230 insertions(+), 217 deletions(-) create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLayout.tsx create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLinks.tsx create mode 100644 packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md index dcc49fda35a..ed2ffbf35bd 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md @@ -98,235 +98,24 @@ This demonstrates how to add an interactive legend to a pie chart using events s This demonstrates an approach for applying tooltips to a legend using a custom label component. These tooltips are keyboard navigable. -```js -import { ChartLabel, ChartLegend, ChartPie, ChartThemeColor } from '@patternfly/react-charts/victory'; -import { Tooltip } from '@patternfly/react-core'; - -class TooltipPieChart extends React.Component { - constructor(props) { - super(props); - - // Custom legend label component - // Note: Tooltip wraps children with a div tag, so we use a reference to ChartLabel instead - this.LegendLabel = ({datum, ...rest}) => { - const ref = createRef(); - return ( - - - - - ); - } - - // Custom legend component - this.getLegend = (legendData) => ( - } - /> - ); - } - - render() { - return ( -
- `${datum.x}: ${datum.y}`} - legendComponent={this.getLegend([ - { name: 'Cats: 35' }, - { name: 'Dogs: 55' }, - { name: 'Birds: 10' } - ])} - legendPosition="bottom" - name="chart7" - padding={{ - bottom: 65, - left: 20, - right: 20, - top: 20 - }} - themeColor={ChartThemeColor.multiOrdered} - width={300} - /> -
- ); - } -} +```ts file = "ChartLegendTooltips.tsx" + ``` ### Legend links This demonstrates an approach for applying links to a legend using a custom label component. These links are keyboard navigable. -```js -import { Chart, ChartAxis, ChartGroup, ChartLabel, ChartLegend, ChartLine, ChartVoronoiContainer } from '@patternfly/react-charts/victory'; - -class LegendLinkPieChart extends React.Component { - constructor(props) { - super(props); - - // Custom legend label component - this.LegendLabel = ({datum, ...rest}) => ( - - - - ); - - // Custom legend component - this.getLegend = (legendData) => ( - } - /> - ); - } - - render() { - return ( -
- `${datum.name}: ${datum.y}`} constrainToVisibleArea />} - legendComponent={this.getLegend([ - { name: 'Cats' }, - { name: 'Dogs' }, - { name: 'Birds' }, - { name: 'Mice'} - ])} - legendData={[{ name: 'Cats' }, { name: 'Dogs', symbol: { type: 'dash' } }, { name: 'Birds' }, { name: 'Mice' }]} - legendPosition="bottom" - height={275} - maxDomain={{y: 10}} - minDomain={{y: 0}} - name="chart8" - padding={{ - bottom: 75, // Adjusted to accommodate legend - left: 50, - right: 50, - top: 50 - }} - width={450} - > - } /> - } /> - - - - - - - -
- ); - } -} +```ts file = "ChartLegendLinks.tsx" + ``` ### Legend layout This demonstrates an approach for applying a different legend layout and styles using a custom label component. -```js -import { ChartLabel, ChartLegend, ChartDonut, ChartThemeColor } from '@patternfly/react-charts/victory'; -import { Tooltip } from '@patternfly/react-core'; - -class LegendLayoutPieChart extends React.Component { - constructor(props) { - super(props); - - // Custom legend label component - this.LegendLabel = ({values, ...rest}) => ( - - ); - - // Custom legend component - this.getLegend = (legendData, values) => ( - } - rowGutter={20} - /> - ); - } - - render() { - return ( -
- `${datum.x}: ${datum.y}`} - legendComponent={this.getLegend([ - { name: 'Cats' }, - { name: 'Dogs' }, - { name: 'Birds' } - ], [ 35, 55, 10 ])} - legendOrientation="vertical" - legendPosition="right" - name="chart9" - padding={{ - bottom: 20, - left: 20, - right: 140, // Adjusted to accommodate legend - top: 20 - }} - subTitle="Pets" - title="100" - themeColor={ChartThemeColor.multiOrdered} - width={375} - /> -
- ); - } -} +```ts file = "ChartLegendLayout.tsx" + ``` ## Documentation diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLayout.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLayout.tsx new file mode 100644 index 00000000000..f28ab03baac --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLayout.tsx @@ -0,0 +1,57 @@ +import { ChartLabel, ChartLegend, ChartDonut, ChartThemeColor } from '@patternfly/react-charts/victory'; + +interface PetData { + x: string; + y: number; +} + +export const ChartLegendLayout: React.FunctionComponent = () => { + // Custom legend label component + const LegendLabel = ({ values, ...rest }) => ( + + ); + + // Custom legend component + const getLegend = (legendData, values) => ( + } + rowGutter={20} + /> + ); + + const data: PetData[] = [ + { x: 'Cats', y: 35 }, + { x: 'Dogs', y: 55 }, + { x: 'Birds', y: 10 } + ]; + + return ( +
+ `${datum.x}: ${datum.y}`} + legendComponent={getLegend([{ name: 'Cats' }, { name: 'Dogs' }, { name: 'Birds' }], [35, 55, 10])} + legendOrientation="vertical" + legendPosition="right" + name="chart9" + padding={{ + bottom: 20, + left: 20, + right: 140, // Adjusted to accommodate legend + top: 20 + }} + subTitle="Pets" + title="100" + themeColor={ChartThemeColor.multiOrdered} + width={375} + /> +
+ ); +}; diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLinks.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLinks.tsx new file mode 100644 index 00000000000..0a544be49b5 --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendLinks.tsx @@ -0,0 +1,116 @@ +import { + Chart, + ChartAxis, + ChartGroup, + ChartLabel, + ChartLegend, + ChartLine, + ChartVoronoiContainer +} from '@patternfly/react-charts/victory'; + +interface PetData { + name?: string; + x?: string; + y?: number; + symbol?: { type: string }; +} + +export const ChartLegendLinks: React.FunctionComponent = () => { + // Custom legend label component + const LegendLabel = ({ _datum, ...rest }) => ( + + + + ); + + // Custom legend component + const getLegend = (legendData) => } />; + + const legendData: PetData[] = [ + { name: 'Cats' }, + { name: 'Dogs', symbol: { type: 'dash' } }, + { name: 'Birds' }, + { name: 'Mice' } + ]; + const data1: PetData[] = [ + { name: 'Cats', x: '2015', y: 1 }, + { name: 'Cats', x: '2016', y: 2 }, + { name: 'Cats', x: '2017', y: 5 }, + { name: 'Cats', x: '2018', y: 3 } + ]; + const data2: PetData[] = [ + { name: 'Dogs', x: '2015', y: 2 }, + { name: 'Dogs', x: '2016', y: 1 }, + { name: 'Dogs', x: '2017', y: 7 }, + { name: 'Dogs', x: '2018', y: 4 } + ]; + const data3: PetData[] = [ + { name: 'Birds', x: '2015', y: 3 }, + { name: 'Birds', x: '2016', y: 4 }, + { name: 'Birds', x: '2017', y: 9 }, + { name: 'Birds', x: '2018', y: 5 } + ]; + const data4: PetData[] = [ + { name: 'Mice', x: '2015', y: 3 }, + { name: 'Mice', x: '2016', y: 3 }, + { name: 'Mice', x: '2017', y: 8 }, + { name: 'Mice', x: '2018', y: 7 } + ]; + + return ( +
+ `${datum.name}: ${datum.y}`} + constrainToVisibleArea + /> + } + legendComponent={getLegend([{ name: 'Cats' }, { name: 'Dogs' }, { name: 'Birds' }, { name: 'Mice' }])} + legendData={legendData} + legendPosition="bottom" + height={275} + maxDomain={{ y: 10 }} + minDomain={{ y: 0 }} + name="chart8" + padding={{ + bottom: 75, // Adjusted to accommodate legend + left: 50, + right: 50, + top: 50 + }} + width={450} + > + } /> + } + /> + + + + + + + +
+ ); +}; diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx new file mode 100644 index 00000000000..32d743891a7 --- /dev/null +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx @@ -0,0 +1,51 @@ +import { ChartLabel, ChartLegend, ChartPie, ChartThemeColor } from '@patternfly/react-charts/victory'; +import { Tooltip } from '@patternfly/react-core'; + +interface PetData { + x: string; + y: number; +} + +export const ChartLegendTooltips: React.FunctionComponent = () => { + const data: PetData[] = [ + { x: 'Cats', y: 35 }, + { x: 'Dogs', y: 55 }, + { x: 'Birds', y: 10 } + ]; + const ref = React.useRef(null); + // Custom legend label component + // Note: Tooltip wraps children with a div tag, so we use a reference to ChartLabel instead + const LegendLabel = ({ datum, ...rest }) => ( + + + + + ); + + // Custom legend component + const getLegend = (legendData) => } />; + + return ( +
+ `${datum.x}: ${datum.y}`} + legendComponent={getLegend([{ name: 'Cats: 35' }, { name: 'Dogs: 55' }, { name: 'Birds: 10' }])} + legendPosition="bottom" + name="chart7" + padding={{ + bottom: 65, + left: 20, + right: 20, + top: 20 + }} + themeColor={ChartThemeColor.multiOrdered} + width={300} + /> +
+ ); +}; From 9029f8f60d4707fa929c1530d80cadbc979b60dd Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Thu, 15 May 2025 00:19:05 +0530 Subject: [PATCH 5/5] updated usage of hooks and fixed the tooltip example --- .../ChartLegend/examples/ChartLegend.md | 2 +- .../examples/ChartLegendInteractive.tsx | 7 ++++--- .../ChartLegendInteractivePieChart.tsx | 3 ++- .../ChartLegendResponsiveBottomLeft.tsx | 9 +++++---- .../examples/ChartLegendTooltips.tsx | 18 +++++++++++------- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md index ed2ffbf35bd..3a0df4da60e 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegend.md @@ -20,7 +20,7 @@ propComponents: [ hideDarkMode: true --- -import { cloneElement, createRef, useEffect, useRef, useState } from 'react'; +import { cloneElement, useEffect, useRef, useState } from 'react'; import { Chart, ChartArea, diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx index 43ad2e884b0..1cca049d777 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractive.tsx @@ -12,11 +12,12 @@ import { getInteractiveLegendItemStyles } from '@patternfly/react-charts/victory'; import { getResizeObserver } from '@patternfly/react-core'; +import { useRef, useState, useEffect } from 'react'; export const ChartLegendInteractive: React.FunctionComponent = () => { - const containerRef = React.useRef(null); - const [hiddenSeries, setHiddenSeries] = React.useState(new Set()); - const [width, setWidth] = React.useState(0); + const containerRef = useRef(null); + const [hiddenSeries, setHiddenSeries] = useState(new Set()); + const [width, setWidth] = useState(0); const series = [ { diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx index be6f7daaf6f..8729019333e 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendInteractivePieChart.tsx @@ -6,9 +6,10 @@ import { getInteractiveLegendEvents, getInteractiveLegendItemStyles } from '@patternfly/react-charts/victory'; +import { useState } from 'react'; export const ChartLegendInteractivePieChart: React.FunctionComponent = () => { - const [hiddenSeries, setHiddenSeries] = React.useState>(new Set()); + const [hiddenSeries, setHiddenSeries] = useState>(new Set()); const series = [ { diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx index cd17446e921..5b2e8151d44 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendResponsiveBottomLeft.tsx @@ -1,5 +1,6 @@ import { ChartBullet } from '@patternfly/react-charts/victory'; import { getResizeObserver } from '@patternfly/react-core'; +import { useRef, useState, useEffect } from 'react'; interface Data { name?: string; @@ -7,9 +8,9 @@ interface Data { } export const ChartLegendResponsiveBottomLeft: React.FunctionComponent = () => { - const containerRef = React.useRef(null); - const [extraHeight, setExtraHeight] = React.useState(0); - const [width, setWidth] = React.useState(0); + const containerRef = useRef(null); + const [extraHeight, setExtraHeight] = useState(0); + const [width, setWidth] = useState(0); const handleResize = () => { if (containerRef.current && containerRef.current.clientWidth) { @@ -25,7 +26,7 @@ export const ChartLegendResponsiveBottomLeft: React.FunctionComponent = () => { const getHeight = (baseHeight: number) => baseHeight + extraHeight; - React.useEffect(() => { + useEffect(() => { const observer = getResizeObserver(containerRef.current, handleResize); handleResize(); diff --git a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx index 32d743891a7..1069dabe637 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx +++ b/packages/react-charts/src/victory/components/ChartLegend/examples/ChartLegendTooltips.tsx @@ -1,5 +1,6 @@ import { ChartLabel, ChartLegend, ChartPie, ChartThemeColor } from '@patternfly/react-charts/victory'; import { Tooltip } from '@patternfly/react-core'; +import { useRef } from 'react'; interface PetData { x: string; @@ -12,15 +13,18 @@ export const ChartLegendTooltips: React.FunctionComponent = () => { { x: 'Dogs', y: 55 }, { x: 'Birds', y: 10 } ]; - const ref = React.useRef(null); + // Custom legend label component // Note: Tooltip wraps children with a div tag, so we use a reference to ChartLabel instead - const LegendLabel = ({ datum, ...rest }) => ( - - - - - ); + const LegendLabel = ({ datum, ...rest }) => { + const ref = useRef(null); + return ( + + + + + ); + }; // Custom legend component const getLegend = (legendData) => } />;