11import { ComponentRef , Injector } from '@angular/core' ;
2- import { DynamicComponentService } from '@hypertrace/common' ;
2+ import { Color , DynamicComponentService } from '@hypertrace/common' ;
33import { ContainerElement , EnterElement , select , Selection } from 'd3-selection' ;
4+ import { Observable , Subject } from 'rxjs' ;
5+ import { startWith } from 'rxjs/operators' ;
46import { LegendPosition } from '../../../legend/legend.component' ;
57import { Series , Summary } from '../../chart' ;
68import {
@@ -10,20 +12,35 @@ import {
1012} from './cartesian-interval-control.component' ;
1113import { CartesianSummaryComponent , SUMMARIES_DATA } from './cartesian-summary.component' ;
1214
13- export class CartesianLegend {
15+ export class CartesianLegend < TData > {
1416 private static readonly CSS_CLASS : string = 'legend' ;
17+ private static readonly RESET_CSS_CLASS : string = 'reset' ;
18+ private static readonly SELECTABLE_CSS_CLASS : string = 'selectable' ;
19+ private static readonly DEFAULT_CSS_CLASS : string = 'default' ;
20+ private static readonly ACTIVE_CSS_CLASS : string = 'active' ;
21+ private static readonly INACTIVE_CSS_CLASS : string = 'inactive' ;
1522 public static readonly CSS_SELECTOR : string = `.${ CartesianLegend . CSS_CLASS } ` ;
1623
24+ public readonly activeSeries$ : Observable < Series < TData > [ ] > ;
25+ private readonly activeSeriesSubject : Subject < Series < TData > [ ] > = new Subject ( ) ;
26+ private readonly initialSeries : Series < TData > [ ] ;
27+
28+ private isSelectionModeOn : boolean = false ;
1729 private legendElement ?: HTMLDivElement ;
30+ private activeSeries : Series < TData > [ ] ;
1831 private intervalControl ?: ComponentRef < unknown > ;
1932 private summaryControl ?: ComponentRef < unknown > ;
2033
2134 public constructor (
22- private readonly series : Series < { } > [ ] ,
35+ private readonly series : Series < TData > [ ] ,
2336 private readonly injector : Injector ,
2437 private readonly intervalData ?: CartesianIntervalData ,
2538 private readonly summaries : Summary [ ] = [ ]
26- ) { }
39+ ) {
40+ this . activeSeries = [ ...this . series ] ;
41+ this . initialSeries = [ ...this . series ] ;
42+ this . activeSeries$ = this . activeSeriesSubject . asObservable ( ) . pipe ( startWith ( this . series ) ) ;
43+ }
2744
2845 public draw ( hostElement : Element , position : LegendPosition ) : this {
2946 this . legendElement = this . drawLegendContainer ( hostElement , position , this . intervalData !== undefined ) . node ( ) ! ;
@@ -33,6 +50,7 @@ export class CartesianLegend {
3350 }
3451
3552 this . drawLegendEntries ( this . legendElement ) ;
53+ this . drawReset ( this . legendElement ) ;
3654
3755 if ( this . intervalData ) {
3856 this . intervalControl = this . drawIntervalControl ( this . legendElement , this . intervalData ) ;
@@ -50,6 +68,20 @@ export class CartesianLegend {
5068 this . summaryControl && this . summaryControl . destroy ( ) ;
5169 }
5270
71+ private drawReset ( container : ContainerElement ) : void {
72+ select ( container )
73+ . append ( 'span' )
74+ . classed ( CartesianLegend . RESET_CSS_CLASS , true )
75+ . text ( 'Reset' )
76+ . on ( 'click' , ( ) => this . disableSelectionMode ( ) ) ;
77+
78+ this . updateResetElementVisibility ( ! this . isSelectionModeOn ) ;
79+ }
80+
81+ private updateResetElementVisibility ( isHidden : boolean ) : void {
82+ select ( this . legendElement ! ) . select ( `span.${ CartesianLegend . RESET_CSS_CLASS } ` ) . classed ( 'hidden' , isHidden ) ;
83+ }
84+
5385 private drawLegendEntries ( container : ContainerElement ) : void {
5486 select ( container )
5587 . append ( 'div' )
@@ -78,20 +110,47 @@ export class CartesianLegend {
78110 . classed ( `position-${ legendPosition } ` , true ) ;
79111 }
80112
81- private drawLegendEntry ( element : EnterElement ) : Selection < HTMLDivElement , Series < { } > , null , undefined > {
82- const legendEntry = select < EnterElement , Series < { } > > ( element ) . append ( 'div' ) . classed ( 'legend-entry' , true ) ;
113+ private drawLegendEntry ( element : EnterElement ) : Selection < HTMLDivElement , Series < TData > , null , undefined > {
114+ const legendEntry = select < EnterElement , Series < TData > > ( element ) . append ( 'div' ) . classed ( 'legend-entry' , true ) ;
83115
84116 this . appendLegendSymbol ( legendEntry ) ;
85-
86117 legendEntry
87118 . append ( 'span' )
88119 . classed ( 'legend-text' , true )
89- . text ( series => series . name ) ;
120+ . classed ( CartesianLegend . SELECTABLE_CSS_CLASS , this . series . length > 1 )
121+ . text ( series => series . name )
122+ . on ( 'click' , series => ( this . series . length > 1 ? this . updateActiveSeries ( series ) : undefined ) ) ;
123+
124+ this . updateLegendClassesAndStyle ( ) ;
90125
91126 return legendEntry ;
92127 }
93128
94- private appendLegendSymbol ( selection : Selection < HTMLDivElement , Series < { } > , null , undefined > ) : void {
129+ private updateLegendClassesAndStyle ( ) : void {
130+ const legendElementSelection = select ( this . legendElement ! ) ;
131+
132+ // Legend entry symbol
133+ legendElementSelection
134+ . selectAll ( '.legend-symbol circle' )
135+ . style ( 'fill' , series =>
136+ ! this . isThisLegendEntryActive ( series as Series < TData > ) ? Color . Gray3 : ( series as Series < TData > ) . color
137+ ) ;
138+
139+ // Legend entry value text
140+ legendElementSelection
141+ . selectAll ( 'span.legend-text' )
142+ . classed ( CartesianLegend . DEFAULT_CSS_CLASS , ! this . isSelectionModeOn )
143+ . classed (
144+ CartesianLegend . ACTIVE_CSS_CLASS ,
145+ series => this . isSelectionModeOn && this . isThisLegendEntryActive ( series as Series < TData > )
146+ )
147+ . classed (
148+ CartesianLegend . INACTIVE_CSS_CLASS ,
149+ series => this . isSelectionModeOn && ! this . isThisLegendEntryActive ( series as Series < TData > )
150+ ) ;
151+ }
152+
153+ private appendLegendSymbol ( selection : Selection < HTMLDivElement , Series < TData > , null , undefined > ) : void {
95154 selection
96155 . append ( 'svg' )
97156 . classed ( 'legend-symbol' , true )
@@ -133,4 +192,30 @@ export class CartesianLegend {
133192 } )
134193 ) ;
135194 }
195+
196+ private disableSelectionMode ( ) : void {
197+ this . activeSeries = [ ...this . initialSeries ] ;
198+ this . isSelectionModeOn = false ;
199+ this . updateLegendClassesAndStyle ( ) ;
200+ this . updateResetElementVisibility ( ! this . isSelectionModeOn ) ;
201+ this . activeSeriesSubject . next ( this . activeSeries ) ;
202+ }
203+
204+ private updateActiveSeries ( seriesEntry : Series < TData > ) : void {
205+ if ( ! this . isSelectionModeOn ) {
206+ this . activeSeries = [ seriesEntry ] ;
207+ this . isSelectionModeOn = true ;
208+ } else if ( this . isThisLegendEntryActive ( seriesEntry ) ) {
209+ this . activeSeries = this . activeSeries . filter ( series => series !== seriesEntry ) ;
210+ } else {
211+ this . activeSeries . push ( seriesEntry ) ;
212+ }
213+ this . updateLegendClassesAndStyle ( ) ;
214+ this . updateResetElementVisibility ( ! this . isSelectionModeOn ) ;
215+ this . activeSeriesSubject . next ( this . activeSeries ) ;
216+ }
217+
218+ private isThisLegendEntryActive ( seriesEntry : Series < TData > ) : boolean {
219+ return this . activeSeries . includes ( seriesEntry ) ;
220+ }
136221}
0 commit comments