Skip to content

Commit 6a6a3ce

Browse files
committed
chore(ChartLegend): updated interactive legend example
1 parent b4c0781 commit 6a6a3ce

File tree

1 file changed

+108
-158
lines changed
  • packages/react-charts/src/victory/components/ChartLegend/examples

1 file changed

+108
-158
lines changed

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

Lines changed: 108 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -291,28 +291,25 @@ import {
291291
createContainer,
292292
getInteractiveLegendEvents,
293293
getInteractiveLegendItemStyles,
294-
} from '@patternfly/react-charts/victory';
294+
} from '@patternfly/react-charts';
295295
import { getResizeObserver } from '@patternfly/react-core';
296-
// import '@patternfly/patternfly/patternfly-charts.css'; // For mixed blend mode
297296

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 = [{
297+
const InteractiveLegendChart = () => {
298+
const containerRef = React.useRef(null);
299+
const [hiddenSeries, setHiddenSeries] = React.useState(new Set());
300+
const [width, setWidth] = React.useState(0);
301+
302+
const series = [
303+
{
308304
datapoints: [
309305
{ x: '2015', y: 3 },
310306
{ x: '2016', y: 4 },
311307
{ x: '2017', y: 8 },
312308
{ x: '2018', y: 6 }
313309
],
314310
legendItem: { name: 'Cats' }
315-
}, {
311+
},
312+
{
316313
datapoints: [
317314
{ x: '2015', y: 2 },
318315
{ x: '2016', y: 3 },
@@ -321,7 +318,8 @@ class InteractiveLegendChart extends React.Component {
321318
{ x: '2019', y: 6 }
322319
],
323320
legendItem: { name: 'Dogs' }
324-
}, {
321+
},
322+
{
325323
datapoints: [
326324
{ x: '2015', y: 1 },
327325
{ x: '2016', y: 2 },
@@ -330,158 +328,110 @@ class InteractiveLegendChart extends React.Component {
330328
{ x: '2019', y: 4 }
331329
],
332330
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-
});
331+
}
332+
];
352333

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-
};
334+
// Returns groups of chart names associated with each data series
335+
const getChartNames = () => series.map((_, index) => [`area-${index}`, `scatter-${index}`]);
364336

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);
337+
// Handles legend click to toggle visibility of data series
338+
const handleLegendClick = (props) => {
339+
setHiddenSeries((prev) => {
340+
const newHidden = new Set(prev);
341+
if (!newHidden.delete(props.index)) {
342+
newHidden.add(props.index);
369343
}
370-
this.setState({ hiddenSeries: new Set(this.state.hiddenSeries) });
371-
};
372-
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 });
377-
}
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-
);
344+
return newHidden;
345+
});
404346
};
405347

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()
348+
// Returns legend data styled per hiddenSeries
349+
const getLegendData = () =>
350+
series.map((s, index) => ({
351+
childName: `area-${index}`,
352+
...s.legendItem,
353+
...getInteractiveLegendItemStyles(hiddenSeries.has(index))
354+
}));
355+
356+
// Returns true if data series is hidden
357+
const isHidden = (index) => hiddenSeries.has(index);
358+
359+
// Checks if any data series is visible
360+
const isDataAvailable = () => hiddenSeries.size !== series.length;
361+
362+
// Set chart width per current window size
363+
React.useEffect(() => {
364+
const observer = getResizeObserver(containerRef.current, () => {
365+
if (containerRef.current?.clientWidth) {
366+
setWidth(containerRef.current.clientWidth);
428367
}
429-
);
368+
});
369+
return () => observer();
370+
}, []);
371+
372+
// Note: Container order is important
373+
const CursorVoronoiContainer = createContainer("voronoi", "cursor");
374+
const container = (
375+
<CursorVoronoiContainer
376+
cursorDimension="x"
377+
labels={({ datum }) => datum.childName.includes('area-') && datum.y !== null ? `${datum.y}` : null}
378+
labelComponent={<ChartLegendTooltip legendData={getLegendData()} title={(datum) => datum.x} />}
379+
mouseFollowTooltips
380+
voronoiDimension="x"
381+
voronoiPadding={50}
382+
disable={!isDataAvailable()}
383+
/>
384+
);
430385

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>
386+
return (
387+
<div ref={containerRef}>
388+
<div className="area-chart-legend-bottom-responsive">
389+
<Chart
390+
ariaDesc="Average number of pets"
391+
ariaTitle="Area chart example"
392+
containerComponent={container}
393+
events={getInteractiveLegendEvents({
394+
chartNames: getChartNames(),
395+
isHidden,
396+
legendName: 'chart5-ChartLegend',
397+
onLegendClick: handleLegendClick
398+
})}
399+
height={225}
400+
legendComponent={<ChartLegend name={'chart5-ChartLegend'} data={getLegendData()} />}
401+
legendPosition="bottom-left"
402+
name="chart5"
403+
padding={{ bottom: 75, left: 50, right: 50, top: 50 }}
404+
maxDomain={{ y: 9 }}
405+
themeColor={ChartThemeColor.multiUnordered}
406+
width={width}
407+
>
408+
<ChartAxis tickValues={['2015', '2016', '2017', '2018']} />
409+
<ChartAxis dependentAxis showGrid />
410+
<ChartGroup>
411+
{series.map((s, index) => (
412+
<ChartScatter
413+
key={`scatter-${index}`}
414+
name={`scatter-${index}`}
415+
data={!isHidden(index) ? s.datapoints : [{ y: null }]}
416+
size={({ active }) => (active ? 5 : 3)}
417+
/>
418+
))}
419+
</ChartGroup>
420+
<ChartGroup>
421+
{series.map((s, index) => (
422+
<ChartArea
423+
key={`area-${index}`}
424+
name={`area-${index}`}
425+
data={!isHidden(index) ? s.datapoints : [{ y: null }]}
426+
interpolation="monotoneX"
427+
/>
428+
))}
429+
</ChartGroup>
430+
</Chart>
481431
</div>
482-
);
483-
}
484-
}
432+
</div>
433+
);
434+
};
485435
```
486436
487437
### Interactive legend with pie chart

0 commit comments

Comments
 (0)