@@ -295,24 +295,22 @@ import {
295295import { 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