1
1
import { ComponentRef , Injector } from '@angular/core' ;
2
- import { DynamicComponentService } from '@hypertrace/common' ;
2
+ import { Color , DynamicComponentService } from '@hypertrace/common' ;
3
3
import { ContainerElement , EnterElement , select , Selection } from 'd3-selection' ;
4
+ import { Observable , Subject } from 'rxjs' ;
5
+ import { startWith } from 'rxjs/operators' ;
4
6
import { LegendPosition } from '../../../legend/legend.component' ;
5
7
import { Series , Summary } from '../../chart' ;
6
8
import {
@@ -10,20 +12,35 @@ import {
10
12
} from './cartesian-interval-control.component' ;
11
13
import { CartesianSummaryComponent , SUMMARIES_DATA } from './cartesian-summary.component' ;
12
14
13
- export class CartesianLegend {
15
+ export class CartesianLegend < TData > {
14
16
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' ;
15
22
public static readonly CSS_SELECTOR : string = `.${ CartesianLegend . CSS_CLASS } ` ;
16
23
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 ;
17
29
private legendElement ?: HTMLDivElement ;
30
+ private activeSeries : Series < TData > [ ] ;
18
31
private intervalControl ?: ComponentRef < unknown > ;
19
32
private summaryControl ?: ComponentRef < unknown > ;
20
33
21
34
public constructor (
22
- private readonly series : Series < { } > [ ] ,
35
+ private readonly series : Series < TData > [ ] ,
23
36
private readonly injector : Injector ,
24
37
private readonly intervalData ?: CartesianIntervalData ,
25
38
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
+ }
27
44
28
45
public draw ( hostElement : Element , position : LegendPosition ) : this {
29
46
this . legendElement = this . drawLegendContainer ( hostElement , position , this . intervalData !== undefined ) . node ( ) ! ;
@@ -33,6 +50,7 @@ export class CartesianLegend {
33
50
}
34
51
35
52
this . drawLegendEntries ( this . legendElement ) ;
53
+ this . drawReset ( this . legendElement ) ;
36
54
37
55
if ( this . intervalData ) {
38
56
this . intervalControl = this . drawIntervalControl ( this . legendElement , this . intervalData ) ;
@@ -50,6 +68,20 @@ export class CartesianLegend {
50
68
this . summaryControl && this . summaryControl . destroy ( ) ;
51
69
}
52
70
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
+
53
85
private drawLegendEntries ( container : ContainerElement ) : void {
54
86
select ( container )
55
87
. append ( 'div' )
@@ -78,20 +110,47 @@ export class CartesianLegend {
78
110
. classed ( `position-${ legendPosition } ` , true ) ;
79
111
}
80
112
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 ) ;
83
115
84
116
this . appendLegendSymbol ( legendEntry ) ;
85
-
86
117
legendEntry
87
118
. append ( 'span' )
88
119
. 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 ( ) ;
90
125
91
126
return legendEntry ;
92
127
}
93
128
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 {
95
154
selection
96
155
. append ( 'svg' )
97
156
. classed ( 'legend-symbol' , true )
@@ -133,4 +192,30 @@ export class CartesianLegend {
133
192
} )
134
193
) ;
135
194
}
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
+ }
136
221
}
0 commit comments