@@ -83,12 +83,34 @@ export default function ReturnedLateChart() {
8383 const [ selectedToolDetail , setSelectedToolDetail ] = useState ( null ) ;
8484 const [ detailOpen , setDetailOpen ] = useState ( false ) ;
8585 const [ detailLoading , setDetailLoading ] = useState ( false ) ;
86+ const [ hiddenProjects , setHiddenProjects ] = useState ( [ ] ) ;
8687 const darkMode = useSelector ( state => state . theme . darkMode ) ;
8788 const [ sortOption , setSortOption ] = useState ( 'DESC' ) ;
8889 const isMultiProjectView = selectedProject === 'All' ;
90+ const visibleDatasets = useMemo (
91+ ( ) =>
92+ chartData . datasets . map ( dataset => ( {
93+ ...dataset ,
94+ hidden : Boolean ( dataset . projectId ) && hiddenProjects . includes ( dataset . projectId ) ,
95+ } ) ) ,
96+ [ chartData . datasets , hiddenProjects ] ,
97+ ) ;
8998 const maxChartValue = Math . max (
9099 0 ,
91- ...chartData . datasets . flatMap ( dataset => dataset . data . filter ( value => value != null ) ) ,
100+ ...visibleDatasets
101+ . filter ( dataset => ! dataset . hidden )
102+ . flatMap ( dataset => dataset . data . filter ( value => value != null ) ) ,
103+ ) ;
104+ const legendItems = useMemo (
105+ ( ) =>
106+ chartData . datasets . map ( dataset => ( {
107+ projectId : dataset . projectId || dataset . label ,
108+ label : dataset . label ,
109+ backgroundColor : dataset . backgroundColor ,
110+ borderColor : dataset . borderColor ,
111+ hidden : Boolean ( dataset . projectId ) && hiddenProjects . includes ( dataset . projectId ) ,
112+ } ) ) ,
113+ [ chartData . datasets , hiddenProjects ] ,
92114 ) ;
93115
94116 const sortToolsData = data => {
@@ -281,6 +303,18 @@ export default function ReturnedLateChart() {
281303 fetchData ( ) ;
282304 } , [ availableProjects , darkMode , selectedProject , dateRange , selectedTools , sortOption ] ) ;
283305
306+ useEffect ( ( ) => {
307+ if ( ! isMultiProjectView ) {
308+ setHiddenProjects ( [ ] ) ;
309+ return ;
310+ }
311+
312+ const validProjectIds = new Set (
313+ chartData . datasets . map ( dataset => dataset . projectId ) . filter ( Boolean ) ,
314+ ) ;
315+ setHiddenProjects ( prev => prev . filter ( projectId => validProjectIds . has ( projectId ) ) ) ;
316+ } , [ chartData . datasets , isMultiProjectView ] ) ;
317+
284318 const handleBarClick = useCallback (
285319 ( event , elements ) => {
286320 if ( ! elements || ! elements . length ) return ;
@@ -312,17 +346,7 @@ export default function ReturnedLateChart() {
312346 } ,
313347 plugins : {
314348 legend : {
315- display : isMultiProjectView && chartData . datasets . length > 1 ,
316- position : 'top' ,
317- align : 'end' ,
318- labels : {
319- color : textColor ,
320- usePointStyle : true ,
321- pointStyle : 'rectRounded' ,
322- boxWidth : 14 ,
323- boxHeight : 14 ,
324- padding : 16 ,
325- } ,
349+ display : false ,
326350 } ,
327351 title : {
328352 display : false ,
@@ -387,7 +411,17 @@ export default function ReturnedLateChart() {
387411 } ,
388412 } ,
389413 } ;
390- } , [ chartData , darkMode , handleBarClick , isMultiProjectView , maxChartValue , rawToolsData ] ) ;
414+ } , [ darkMode , handleBarClick , maxChartValue , rawToolsData ] ) ;
415+
416+ const toggleProjectVisibility = projectId => {
417+ if ( ! projectId || ! isMultiProjectView ) return ;
418+
419+ setHiddenProjects ( prev =>
420+ prev . includes ( projectId ) ? prev . filter ( id => id !== projectId ) : [ ...prev , projectId ] ,
421+ ) ;
422+ } ;
423+
424+ const multiProjectLegendVisible = isMultiProjectView && legendItems . length > 1 ;
391425
392426 const handleProjectChange = e => setSelectedProject ( e . target . value ) ;
393427 const handleStartDateChange = date =>
@@ -403,10 +437,40 @@ export default function ReturnedLateChart() {
403437 < div className = { `${ styles [ 'returned-late-chart' ] } ${ isOxfordBlue } ` } >
404438 < div className = { styles [ 'returned-late-header-row' ] } >
405439 < h1 className = { darkMode ? 'text-white' : '' } > Percent of Tools Returned Late</ h1 >
406- { isMultiProjectView && chartData . datasets . length > 1 && (
407- < p className = { `${ styles [ 'returned-late-legend-hint' ] } ${ darkMode ? 'text-white' : '' } ` } >
408- Click legend items to show or hide project bars.
409- </ p >
440+ { multiProjectLegendVisible && (
441+ < div className = { styles [ 'returned-late-legend-block' ] } >
442+ < p className = { `${ styles [ 'returned-late-legend-hint' ] } ${ darkMode ? 'text-white' : '' } ` } >
443+ Click legend items to show or hide project bars.
444+ </ p >
445+ < div
446+ className = { styles [ 'returned-late-legend' ] }
447+ role = "group"
448+ aria-label = "Project color legend"
449+ >
450+ { legendItems . map ( item => (
451+ < button
452+ key = { item . projectId }
453+ type = "button"
454+ className = { `${ styles [ 'returned-late-legend-item' ] } ${
455+ item . hidden ? styles [ 'returned-late-legend-item-hidden' ] : ''
456+ } `}
457+ onClick = { ( ) => toggleProjectVisibility ( item . projectId ) }
458+ aria-pressed = { ! item . hidden }
459+ title = { `${ item . hidden ? 'Show' : 'Hide' } ${ item . label } ` }
460+ >
461+ < span
462+ className = { styles [ 'returned-late-legend-swatch' ] }
463+ style = { {
464+ backgroundColor : item . backgroundColor ,
465+ borderColor : item . borderColor ,
466+ } }
467+ aria-hidden = "true"
468+ />
469+ < span className = { styles [ 'returned-late-legend-label' ] } > { item . label } </ span >
470+ </ button >
471+ ) ) }
472+ </ div >
473+ </ div >
410474 ) }
411475 </ div >
412476 < div className = { styles [ 'returned-late-filters' ] } >
@@ -515,7 +579,12 @@ export default function ReturnedLateChart() {
515579 </ div >
516580 ) }
517581 { ! loading && ! error && chartData . labels . length > 0 && (
518- < Bar ref = { chartRef } data = { chartData } options = { options } plugins = { [ ChartDataLabels ] } />
582+ < Bar
583+ ref = { chartRef }
584+ data = { { ...chartData , datasets : visibleDatasets } }
585+ options = { options }
586+ plugins = { [ ChartDataLabels ] }
587+ />
519588 ) }
520589 </ div >
521590 { detailOpen && (
0 commit comments