@@ -291,28 +291,25 @@ import {
291291 createContainer ,
292292 getInteractiveLegendEvents ,
293293 getInteractiveLegendItemStyles ,
294- } from ' @patternfly/react-charts/victory ' ;
294+ } from ' @patternfly/react-charts' ;
295295import { 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