Skip to content

Commit 8c84ff4

Browse files
authored
Chore(ChartLegend): updated interactive legend example (#11623)
* chore(ChartLegend): updated interactive legend example * revert import path modification * added a comment
1 parent 7f8e103 commit 8c84ff4

File tree

1 file changed

+107
-156
lines changed
  • packages/react-charts/src/victory/components/ChartLegend/examples

1 file changed

+107
-156
lines changed

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

Lines changed: 107 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -295,24 +295,22 @@ import {
295295
import { getResizeObserver } from '@patternfly/react-core';
296296
// import '@patternfly/patternfly/patternfly-charts.css'; // For mixed blend mode
297297

298-
class InteractiveLegendChart extends React.Component {
299-
constructor(props) {
300-
super(props);
301-
this.containerRef = React.createRef();
302-
this.observer = () => {};
303-
this.state = {
304-
hiddenSeries: new Set(),
305-
width: 0
306-
};
307-
this.series = [{
298+
const InteractiveLegendChart = () => {
299+
const containerRef = React.useRef(null);
300+
const [hiddenSeries, setHiddenSeries] = React.useState(new Set());
301+
const [width, setWidth] = React.useState(0);
302+
303+
const series = [
304+
{
308305
datapoints: [
309306
{ x: '2015', y: 3 },
310307
{ x: '2016', y: 4 },
311308
{ x: '2017', y: 8 },
312309
{ x: '2018', y: 6 }
313310
],
314311
legendItem: { name: 'Cats' }
315-
}, {
312+
},
313+
{
316314
datapoints: [
317315
{ x: '2015', y: 2 },
318316
{ x: '2016', y: 3 },
@@ -321,7 +319,8 @@ class InteractiveLegendChart extends React.Component {
321319
{ x: '2019', y: 6 }
322320
],
323321
legendItem: { name: 'Dogs' }
324-
}, {
322+
},
323+
{
325324
datapoints: [
326325
{ x: '2015', y: 1 },
327326
{ x: '2016', y: 2 },
@@ -330,158 +329,110 @@ class InteractiveLegendChart extends React.Component {
330329
{ x: '2019', y: 4 }
331330
],
332331
legendItem: { name: 'Birds' }
333-
}];
334-
335-
// Returns groups of chart names associated with each data series
336-
this.getChartNames = () => {
337-
const result = [];
338-
this.series.map((_, index) => {
339-
// Each group of chart names are hidden / shown together
340-
result.push([`area-${index}`, `scatter-${index}`]);
341-
});
342-
return result;
343-
};
344-
345-
// Returns onMouseOver, onMouseOut, and onClick events for the interactive legend
346-
this.getEvents = () => getInteractiveLegendEvents({
347-
chartNames: this.getChartNames(),
348-
isHidden: this.isHidden,
349-
legendName: 'chart5-ChartLegend',
350-
onLegendClick: this.handleLegendClick
351-
});
352-
353-
// Returns legend data styled per hiddenSeries
354-
this.getLegendData = () => {
355-
const { hiddenSeries } = this.state;
356-
return this.series.map((s, index) => {
357-
return {
358-
childName: `area-${index}`, // Sync tooltip legend with the series associated with given chart name
359-
...s.legendItem, // name property
360-
...getInteractiveLegendItemStyles(hiddenSeries.has(index)) // hidden styles
361-
};
362-
});
363-
};
332+
}
333+
];
364334

365-
// Hide each data series individually
366-
this.handleLegendClick = (props) => {
367-
if (!this.state.hiddenSeries.delete(props.index)) {
368-
this.state.hiddenSeries.add(props.index);
369-
}
370-
this.setState({ hiddenSeries: new Set(this.state.hiddenSeries) });
371-
};
335+
// Returns groups of chart names associated with each data series
336+
const getChartNames = () => series.map((_, index) => [`area-${index}`, `scatter-${index}`]);
372337

373-
// Set chart width per current window size
374-
this.handleResize = () => {
375-
if (this.containerRef.current && this.containerRef.current.clientWidth) {
376-
this.setState({ width: this.containerRef.current.clientWidth });
338+
// Handles legend click to toggle visibility of data series
339+
const handleLegendClick = (props) => {
340+
setHiddenSeries((prev) => {
341+
const newHidden = new Set(prev);
342+
if (!newHidden.delete(props.index)) {
343+
newHidden.add(props.index);
377344
}
378-
};
379-
380-
// Returns true if data series is hidden
381-
this.isHidden = (index) => {
382-
const { hiddenSeries } = this.state; // Skip if already hidden
383-
return hiddenSeries.has(index);
384-
};
385-
386-
this.isDataAvailable = () => {
387-
const { hiddenSeries } = this.state;
388-
return hiddenSeries.size !== this.series.length;
389-
};
390-
391-
// Note: Container order is important
392-
const CursorVoronoiContainer = createContainer("voronoi", "cursor");
393-
394-
this.cursorVoronoiContainer = (
395-
<CursorVoronoiContainer
396-
cursorDimension="x"
397-
labels={({ datum }) => datum.childName.includes('area-') && datum.y !== null ? `${datum.y}` : null}
398-
labelComponent={<ChartLegendTooltip legendData={this.getLegendData()} title={(datum) => datum.x}/>}
399-
mouseFollowTooltips
400-
voronoiDimension="x"
401-
voronoiPadding={50}
402-
/>
403-
);
345+
return newHidden;
346+
});
404347
};
405348

406-
componentDidMount() {
407-
this.observer = getResizeObserver(this.containerRef.current, this.handleResize);
408-
this.handleResize();
409-
}
410-
411-
componentWillUnmount() {
412-
this.observer();
413-
}
414-
415-
// Tips:
416-
// 1. Omitting hidden components will reassign color scale, use null data instead or custom colors
417-
// 2. Set domain or tick axis labels to account for when all data series are hidden
418-
// 3. Omit tooltip for ChartScatter component by checking childName prop
419-
// 4. Omit tooltip when all data series are hidden
420-
// 5. Clone original container to ensure tooltip events are not lost when data series are hidden / shown
421-
render() {
422-
const { hiddenSeries, width } = this.state;
423-
424-
const container = React.cloneElement(
425-
this.cursorVoronoiContainer,
426-
{
427-
disable: !this.isDataAvailable()
349+
// Returns legend data styled per hiddenSeries
350+
const getLegendData = () =>
351+
series.map((s, index) => ({
352+
childName: `area-${index}`,
353+
...s.legendItem,
354+
...getInteractiveLegendItemStyles(hiddenSeries.has(index))
355+
}));
356+
357+
// Returns true if data series is hidden
358+
const isHidden = (index) => hiddenSeries.has(index);
359+
360+
// Checks if any data series is visible
361+
const isDataAvailable = () => hiddenSeries.size !== series.length;
362+
363+
// Set chart width per current window size
364+
React.useEffect(() => {
365+
const observer = getResizeObserver(containerRef.current, () => {
366+
if (containerRef.current?.clientWidth) {
367+
setWidth(containerRef.current.clientWidth);
428368
}
429-
);
369+
});
370+
return () => observer();
371+
}, []);
372+
373+
// Note: Container order is important
374+
const CursorVoronoiContainer = createContainer("voronoi", "cursor");
375+
const container = (
376+
<CursorVoronoiContainer
377+
cursorDimension="x"
378+
labels={({ datum }) => datum.childName.includes('area-') && datum.y !== null ? `${datum.y}` : null}
379+
labelComponent={<ChartLegendTooltip legendData={getLegendData()} title={(datum) => datum.x} />}
380+
mouseFollowTooltips
381+
voronoiDimension="x"
382+
voronoiPadding={50}
383+
disable={!isDataAvailable()}
384+
/>
385+
);
430386

431-
return (
432-
<div ref={this.containerRef}>
433-
<div className="area-chart-legend-bottom-responsive">
434-
<Chart
435-
ariaDesc="Average number of pets"
436-
ariaTitle="Area chart example"
437-
containerComponent={container}
438-
events={this.getEvents()}
439-
height={225}
440-
legendComponent={<ChartLegend name={'chart5-ChartLegend'} data={this.getLegendData()} />}
441-
legendPosition="bottom-left"
442-
name="chart5"
443-
padding={{
444-
bottom: 75, // Adjusted to accommodate legend
445-
left: 50,
446-
right: 50,
447-
top: 50,
448-
}}
449-
maxDomain={{y: 9}}
450-
themeColor={ChartThemeColor.multiUnordered}
451-
width={width}
452-
>
453-
<ChartAxis tickValues={['2015', '2016', '2017', '2018']} />
454-
<ChartAxis dependentAxis showGrid />
455-
<ChartGroup>
456-
{this.series.map((s, index) => {
457-
return (
458-
<ChartScatter
459-
data={!hiddenSeries.has(index) ? s.datapoints : [{ y: null}]}
460-
key={'scatter-' + index}
461-
name={'scatter-' + index}
462-
size={({ active }) => (active ? 5 : 3)}
463-
/>
464-
);
465-
})}
466-
</ChartGroup>
467-
<ChartGroup>
468-
{this.series.map((s, index) => {
469-
return (
470-
<ChartArea
471-
data={!hiddenSeries.has(index) ? s.datapoints : [{ y: null}]}
472-
interpolation="monotoneX"
473-
key={'area-' + index}
474-
name={'area-' + index}
475-
/>
476-
);
477-
})}
478-
</ChartGroup>
479-
</Chart>
480-
</div>
387+
return (
388+
<div ref={containerRef}>
389+
<div className="area-chart-legend-bottom-responsive">
390+
<Chart
391+
ariaDesc="Average number of pets"
392+
ariaTitle="Area chart example"
393+
containerComponent={container}
394+
events={getInteractiveLegendEvents({
395+
chartNames: getChartNames(),
396+
isHidden,
397+
legendName: 'chart5-ChartLegend',
398+
onLegendClick: handleLegendClick
399+
})}
400+
height={225}
401+
legendComponent={<ChartLegend name={'chart5-ChartLegend'} data={getLegendData()} />}
402+
legendPosition="bottom-left"
403+
name="chart5"
404+
padding={{ bottom: 75, left: 50, right: 50, top: 50 }}
405+
maxDomain={{ y: 9 }}
406+
themeColor={ChartThemeColor.multiUnordered}
407+
width={width}
408+
>
409+
<ChartAxis tickValues={['2015', '2016', '2017', '2018']} />
410+
<ChartAxis dependentAxis showGrid />
411+
<ChartGroup>
412+
{series.map((s, index) => (
413+
<ChartScatter
414+
key={`scatter-${index}`}
415+
name={`scatter-${index}`}
416+
data={!isHidden(index) ? s.datapoints : [{ y: null }]}
417+
size={({ active }) => (active ? 5 : 3)}
418+
/>
419+
))}
420+
</ChartGroup>
421+
<ChartGroup>
422+
{series.map((s, index) => (
423+
<ChartArea
424+
key={`area-${index}`}
425+
name={`area-${index}`}
426+
data={!isHidden(index) ? s.datapoints : [{ y: null }]}
427+
interpolation="monotoneX"
428+
/>
429+
))}
430+
</ChartGroup>
431+
</Chart>
481432
</div>
482-
);
483-
}
484-
}
433+
</div>
434+
);
435+
};
485436
```
486437
487438
### Interactive legend with pie chart

0 commit comments

Comments
 (0)