@@ -38,7 +38,7 @@ import {
38
38
39
39
import 'd3-transition' ;
40
40
41
- import { brushX as d3BrushX } from 'd3-brush' ;
41
+ import { BrushBehavior , brushX as d3BrushX } from 'd3-brush' ;
42
42
import { EventResultDataDTO } from 'src/app/modules/time-series/models/event-result-data.model' ;
43
43
import { EventResultSeriesDTO } from 'src/app/modules/time-series/models/event-result-series.model' ;
44
44
import { EventResultPointDTO } from 'src/app/modules/time-series/models/event-result-point.model' ;
@@ -63,13 +63,17 @@ export class LineChartService {
63
63
// D3 margin conventions
64
64
// > With this convention, all subsequent code can ignore margins.
65
65
// see: https://bl.ocks.org/mbostock/3019563
66
- private _margin = { top : 40 , right : 70 , bottom : 40 , left : 60 } ;
67
- private _width = 600 - this . _margin . left - this . _margin . right ;
68
- private _height = 550 - this . _margin . top - this . _margin . bottom ;
69
- private _labelGroupHeight ;
70
- private _legendGroupTop = this . _margin . top + this . _height + 50 ;
71
- private legendDataMap = { } ;
72
- private brush ;
66
+ private _margin : any = { top : 40 , right : 70 , bottom : 40 , left : 60 } ;
67
+ private _width : number = 600 - this . _margin . left - this . _margin . right ;
68
+ private _height : number = 550 - this . _margin . top - this . _margin . bottom ;
69
+ private _labelGroupHeight : number ;
70
+ private _legendGroupTop : number = this . _margin . top + this . _height + 50 ;
71
+ private _legendGroupHeight : number ;
72
+ private _legendGroupColumnWidth : number ;
73
+ private _legendGroupColumns : number ;
74
+ private legendDataMap : Object = { } ;
75
+ private brush : BrushBehavior < { } > ;
76
+ private focusedLegendEntry : string ;
73
77
74
78
// Map that holds all points clustered by their x-axis values
75
79
private _xAxisCluster : any = { } ;
@@ -78,7 +82,6 @@ export class LineChartService {
78
82
private _mouseEventCatcher : D3Selection < D3BaseType , { } , D3ContainerElement , { } > ;
79
83
private _markerTooltip : D3Selection < HTMLDivElement , { } , D3ContainerElement , { } > ;
80
84
81
-
82
85
constructor ( private translationService : TranslateService ) {
83
86
}
84
87
@@ -98,19 +101,17 @@ export class LineChartService {
98
101
* Draws a line chart for the given data into the given svg
99
102
*/
100
103
public drawLineChart ( incomingData : EventResultDataDTO ) : void {
101
-
102
- let data : TimeSeries [ ] = this . prepareData ( incomingData ) ;
103
-
104
- if ( data . length == 0 ) {
104
+ if ( incomingData . series . length == 0 ) {
105
105
return ;
106
106
}
107
107
108
+ let data : TimeSeries [ ] = this . prepareData ( incomingData ) ;
108
109
let chart : D3Selection < D3BaseType , { } , D3ContainerElement , { } > = d3Select ( 'g#time-series-chart-drawing-area' ) ;
109
110
let xScale : D3ScaleTime < number , number > = this . getXScale ( data ) ;
110
111
let yScale : D3ScaleLinear < number , number > = this . getYScale ( data ) ;
111
- this . _labelGroupHeight = data . length * ChartCommons . LABEL_HEIGHT ;
112
+ this . calculateLegendDimensions ( ) ;
112
113
d3Select ( 'osm-time-series-line-chart' ) . transition ( ) . duration ( 500 ) . style ( 'visibility' , 'visible' ) ;
113
- d3Select ( 'svg#time-series-chart' ) . transition ( ) . duration ( 500 ) . attr ( 'height' , this . _height + this . _labelGroupHeight + this . _margin . top + this . _margin . bottom ) ;
114
+ d3Select ( 'svg#time-series-chart' ) . transition ( ) . duration ( 500 ) . attr ( 'height' , this . _height + this . _legendGroupHeight + this . _margin . top + this . _margin . bottom ) ;
114
115
d3Select ( '.x-axis' ) . transition ( ) . call ( this . updateXAxis , xScale ) ;
115
116
d3Select ( '.y-axis' ) . transition ( ) . call ( this . updateYAxis , yScale , this . _width , this . _margin ) ;
116
117
this . brush = d3BrushX ( ) . extent ( [ [ 0 , 0 ] , [ this . _width , this . _height ] ] ) ;
@@ -135,6 +136,10 @@ export class LineChartService {
135
136
* Set the data for the legend after the incoming data is received
136
137
*/
137
138
public setLegendData ( incomingData : EventResultDataDTO ) {
139
+ if ( incomingData . series . length == 0 ) {
140
+ return ;
141
+ }
142
+
138
143
let labelDataMap = { } ;
139
144
incomingData . series . forEach ( ( data : EventResultSeriesDTO ) => {
140
145
if ( incomingData . summaryLabels . length > 0 && incomingData . summaryLabels [ 0 ] . key != "measurand" ) {
@@ -209,6 +214,7 @@ export class LineChartService {
209
214
. attr ( 'transform' , `translate(${ this . _margin . left } , ${ this . _margin . top } )` ) ; // translates the origin to the top left corner (default behavior of D3)
210
215
211
216
svg . append ( 'g' )
217
+ . attr ( 'id' , 'time-series-chart-legend' )
212
218
. attr ( 'class' , 'legend-group' )
213
219
. attr ( 'transform' , `translate(${ this . _margin . left } , ${ this . _legendGroupTop } )` ) ;
214
220
@@ -314,6 +320,34 @@ export class LineChartService {
314
320
. nice ( ) ;
315
321
}
316
322
323
+ private calculateLegendDimensions ( ) : void {
324
+ let maximumLabelWidth : number = 1 ;
325
+ let labels = Object . keys ( this . legendDataMap ) ;
326
+
327
+ d3Select ( 'g#time-series-chart-legend' )
328
+ . append ( 'g' )
329
+ . attr ( 'id' , 'renderToCalculateMaxWidth' )
330
+ . selectAll ( '.renderToCalculateMaxWidth' )
331
+ . data ( labels )
332
+ . enter ( )
333
+ . append ( 'text' )
334
+ . attr ( 'class' , 'legend-text' )
335
+ . text ( datum => this . legendDataMap [ datum ] . text )
336
+ . each ( ( datum , index , groups ) => {
337
+ Array . from ( groups ) . forEach ( ( text ) => {
338
+ if ( text ) {
339
+ maximumLabelWidth = Math . max ( maximumLabelWidth , text . getBoundingClientRect ( ) . width )
340
+ }
341
+ } ) ;
342
+ } ) ;
343
+
344
+ d3Select ( 'g#renderToCalculateMaxWidth' ) . remove ( ) ;
345
+
346
+ this . _legendGroupColumnWidth = maximumLabelWidth + ChartCommons . COLOR_PREVIEW_SIZE + 30 ;
347
+ this . _legendGroupColumns = Math . floor ( this . _width / this . _legendGroupColumnWidth ) ;
348
+ this . _legendGroupHeight = Math . ceil ( labels . length / this . _legendGroupColumns ) * ChartCommons . LABEL_HEIGHT + 30 ;
349
+ }
350
+
317
351
private getMaxValue ( data : TimeSeries [ ] ) : number {
318
352
return d3Max ( data , ( dataItem : TimeSeries ) => {
319
353
return d3Max ( dataItem . values , ( point : TimeSeriesPoint ) => {
@@ -825,29 +859,36 @@ export class LineChartService {
825
859
. style ( 'opacity' , 0 )
826
860
. remove ( )
827
861
)
828
- . attr ( "transform" , ( datum , index ) => this . position ( index ) )
862
+ . attr ( "transform" , ( datum , index ) => this . getPosition ( index ) )
829
863
. on ( 'click' , ( datum ) => this . onMouseClick ( datum , incomingData ) ) ;
830
864
}
831
865
832
- private position ( index : number ) : string {
833
- const columns = 3 ;
834
- const columnWidth = 550 ;
835
- const xMargin = 10 ;
836
- const yMargin = 10 ;
837
-
838
- const x = index % columns * columnWidth + xMargin ;
839
- const y = Math . floor ( index / columns ) * ChartCommons . LABEL_HEIGHT + yMargin ;
866
+ private getPosition ( index : number ) : string {
867
+ const x = index % this . _legendGroupColumns * this . _legendGroupColumnWidth ;
868
+ const y = Math . floor ( index / this . _legendGroupColumns ) * ChartCommons . LABEL_HEIGHT + 12 ;
840
869
841
870
return "translate(" + x + "," + y + ")" ;
842
871
}
843
872
844
873
private onMouseClick ( labelKey : string , incomingData : EventResultDataDTO ) : void {
845
- if ( d3Event . metaKey ) {
846
- this . legendDataMap [ labelKey ] . show ? this . legendDataMap [ labelKey ] . show = false : this . legendDataMap [ labelKey ] . show = true ;
874
+ if ( d3Event . metaKey || d3Event . ctrlKey ) {
875
+ this . legendDataMap [ labelKey ] . show = ! this . legendDataMap [ labelKey ] . show ;
847
876
} else {
848
- Object . keys ( this . legendDataMap ) . forEach ( ( legend ) => {
849
- this . legendDataMap [ legend ] . show = legend === labelKey ;
850
- } ) ;
877
+ if ( labelKey == this . focusedLegendEntry ) {
878
+ Object . keys ( this . legendDataMap ) . forEach ( ( legend ) => {
879
+ this . legendDataMap [ legend ] . show = true ;
880
+ } ) ;
881
+ this . focusedLegendEntry = "" ;
882
+ } else {
883
+ Object . keys ( this . legendDataMap ) . forEach ( ( legend ) => {
884
+ if ( legend === labelKey ) {
885
+ this . legendDataMap [ legend ] . show = true ;
886
+ this . focusedLegendEntry = legend ;
887
+ } else {
888
+ this . legendDataMap [ legend ] . show = false ;
889
+ }
890
+ } ) ;
891
+ }
851
892
}
852
893
this . drawLineChart ( incomingData ) ;
853
894
}
0 commit comments