77 select as d3Select ,
88 Selection as D3Selection ,
99 BaseType as D3BaseType ,
10- ContainerElement as D3ContainerElement
10+ ContainerElement as D3ContainerElement ,
11+ event as d3Event
1112} from 'd3-selection' ;
1213
1314import { max as d3Max , min as d3Min } from 'd3-array' ;
@@ -29,14 +30,16 @@ import {
2930 axisRight as d3AxisRight
3031} from 'd3-axis' ;
3132
32- import {
33+ import {
3334 line as d3Line ,
3435 curveMonotoneX as d3CurveMonotoneX ,
3536 Line as D3Line
3637} from 'd3-shape' ;
3738
3839import 'd3-transition' ;
3940
41+ import { brushX as d3BrushX } from 'd3-brush' ;
42+
4043import { EventResultDataDTO } from 'src/app/modules/time-series/models/event-result-data.model' ;
4144import { EventResultSeriesDTO } from 'src/app/modules/time-series/models/event-result-series.model' ;
4245import { EventResultPointDTO } from 'src/app/modules/time-series/models/event-result-point.model' ;
@@ -59,6 +62,8 @@ export class LineChartService {
5962 private _margin = { top : 40 , right : 70 , bottom : 40 , left : 60 } ;
6063 private _width = 600 - this . _margin . left - this . _margin . right ;
6164 private _height = 500 - this . _margin . top - this . _margin . bottom ;
65+ private idleTimeout ;
66+ private brush ;
6267
6368
6469 constructor ( private translationService : TranslateService ) { }
@@ -97,11 +102,12 @@ export class LineChartService {
97102
98103 d3Select ( '.x-axis' ) . transition ( ) . call ( this . updateXAxis , xScale ) ;
99104 d3Select ( '.y-axis' ) . transition ( ) . call ( this . updateYAxis , yScale , this . _width , this . _margin ) ;
100-
105+ this . brush = d3BrushX ( ) . extent ( [ [ 0 , 0 ] , [ this . _width , this . _height ] ] ) ;
106+ this . addBrush ( chart , xScale , yScale , data ) ;
101107 this . addDataLinesToChart ( chart , xScale , yScale , data ) ;
102-
103108 }
104109
110+
105111 /**
106112 * Prepares the incoming data for drawing with D3.js
107113 */
@@ -125,8 +131,8 @@ export class LineChartService {
125131
126132 private generateKey ( data : EventResultSeriesDTO ) : string {
127133 return data . jobGroup
128- + data . measuredEvent
129- + data . data . length ;
134+ + data . measuredEvent
135+ + data . data . length ;
130136 }
131137
132138 /**
@@ -137,13 +143,13 @@ export class LineChartService {
137143 this . _width = svgElement . nativeElement . parentElement . offsetWidth - this . _margin . left - this . _margin . right ;
138144 //this._height = svgElement.nativeElement.parentElement.offsetHeight - this._margin.top - this._margin.bottom;
139145 const svg = d3Select ( svgElement . nativeElement )
140- . attr ( 'id' , 'time-series-chart' )
141- . attr ( 'width' , this . _width + this . _margin . left + this . _margin . right )
142- . attr ( 'height' , 0 ) ;
146+ . attr ( 'id' , 'time-series-chart' )
147+ . attr ( 'width' , this . _width + this . _margin . left + this . _margin . right )
148+ . attr ( 'height' , 0 ) ;
143149
144150 return svg . append ( 'g' ) // g = grouping element; group all other stuff into the chart
145- . attr ( 'id' , 'time-series-chart-drawing-area' )
146- . attr ( 'transform' , 'translate(' + this . _margin . left + ', ' + this . _margin . top + ')' ) ; // translates the origin to the top left corner (default behavior of D3)
151+ . attr ( 'id' , 'time-series-chart-drawing-area' )
152+ . attr ( 'transform' , 'translate(' + this . _margin . left + ', ' + this . _margin . top + ')' ) ; // translates the origin to the top left corner (default behavior of D3)
147153 }
148154
149155 public startResize ( svgElement : ElementRef ) : void {
@@ -166,8 +172,8 @@ export class LineChartService {
166172 */
167173 private getXScale ( data : TimeSeries [ ] ) : D3ScaleTime < number , number > {
168174 return d3ScaleTime ( ) // Define a scale for the X-Axis
169- . range ( [ 0 , this . _width ] ) // Display the X-Axis over the complete width
170- . domain ( [ this . getMinDate ( data ) , this . getMaxDate ( data ) ] ) ;
175+ . range ( [ 0 , this . _width ] ) // Display the X-Axis over the complete width
176+ . domain ( [ this . getMinDate ( data ) , this . getMaxDate ( data ) ] ) ;
171177 }
172178
173179 private getMinDate ( data : TimeSeries [ ] ) : Date {
@@ -191,9 +197,9 @@ export class LineChartService {
191197 */
192198 private getYScale ( data : TimeSeries [ ] ) : D3ScaleLinear < number , number > {
193199 return d3ScaleLinear ( ) // Linear scale for the numbers on the Y-Axis
194- . range ( [ this . _height , 0 ] ) // Display the Y-Axis over the complete height - origin is top left corner, so height comes first
195- . domain ( [ 0 , this . getMaxValue ( data ) ] )
196- . nice ( ) ;
200+ . range ( [ this . _height , 0 ] ) // Display the Y-Axis over the complete height - origin is top left corner, so height comes first
201+ . domain ( [ 0 , this . getMaxValue ( data ) ] )
202+ . nice ( ) ;
197203 }
198204
199205 private getMaxValue ( data : TimeSeries [ ] ) : number {
@@ -214,9 +220,9 @@ export class LineChartService {
214220
215221 // Add the X-Axis to the chart
216222 chart . append ( 'g' ) // new group for the X-Axis (see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g)
217- . attr ( 'class' , 'axis x-axis' ) // a css class to style it later
218- . attr ( 'transform' , 'translate(0, ' + this . _height + ')' ) // even if the D3 method called `axisBottom` we have to move it to the bottom by ourselfs
219- . call ( xAxis ) ;
223+ . attr ( 'class' , 'axis x-axis' ) // a css class to style it later
224+ . attr ( 'transform' , 'translate(0, ' + this . _height + ')' ) // even if the D3 method called `axisBottom` we have to move it to the bottom by ourselfs
225+ . call ( xAxis ) ;
220226 }
221227
222228 /**
@@ -229,15 +235,15 @@ export class LineChartService {
229235
230236 // Add the Y-Axis to the chart
231237 chart . append ( 'g' ) // new group for the y-axis
232- . attr ( 'class' , 'axis y-axis' ) // a css class to style it later
233- . call ( yAxis ) ;
238+ . attr ( 'class' , 'axis y-axis' ) // a css class to style it later
239+ . call ( yAxis ) ;
234240
235241 // Add the axis description
236242 this . translationService . get ( "frontend.de.iteratec.osm.timeSeries.loadTimes" ) . pipe ( take ( 1 ) ) . subscribe ( title => {
237243 d3Select ( '.y-axis' ) . append ( 'text' )
238- . attr ( 'class' , 'description' )
239- . attr ( 'transform' , 'translate(-' + ( this . _margin . left - 20 ) + ', ' + ( this . _height / 2 - this . _margin . bottom ) + ') rotate(-90)' )
240- . text ( title + ' [ms]' ) ;
244+ . attr ( 'class' , 'description' )
245+ . attr ( 'transform' , 'translate(-' + ( this . _margin . left - 20 ) + ', ' + ( this . _height / 2 - this . _margin . bottom ) + ') rotate(-90)' )
246+ . text ( title + ' [ms]' ) ;
241247 } ) ;
242248 }
243249
@@ -254,7 +260,7 @@ export class LineChartService {
254260 lines . forEach ( ( line , index ) => {
255261 let tspan = element . append ( 'tspan' ) . text ( line ) ;
256262 if ( index > 0 )
257- tspan . attr ( 'x' , 0 ) . attr ( 'dy' , '15' ) ;
263+ tspan . attr ( 'x' , 0 ) . attr ( 'dy' , '15' ) ;
258264 } ) ;
259265 } ) ;
260266 }
@@ -296,9 +302,9 @@ export class LineChartService {
296302 transition . on ( 'end.showTicks' , function show ( ) {
297303 d3Select ( this ) . selectAll ( 'g.tick text' )
298304 . transition ( )
299- . delay ( 100 )
300- . duration ( 500 )
301- . attr ( 'opacity' , '1.0' )
305+ . delay ( 100 )
306+ . duration ( 500 )
307+ . attr ( 'opacity' , '1.0' )
302308 } ) ;
303309 }
304310
@@ -307,28 +313,28 @@ export class LineChartService {
307313 d3AxisRight ( yScale ) // axis right, because we draw the background line with this
308314 . tickSize ( width ) // background line over complete chart width
309315 )
310- . attr ( 'transform' , 'translate(0, 0)' ) // move the axis to the left
311- . call ( g => g . selectAll ( ".tick:not(:first-of-type) line" ) // make all line dotted, except the one on the bottom as this will indicate the x-axis
312- . attr ( "stroke-opacity" , 0.5 )
313- . attr ( "stroke-dasharray" , "1,1" ) )
314- . call ( g => g . selectAll ( ".tick text" ) // move the text a little so it does not overlap with the lines
315- . attr ( "x" , - 5 ) ) ;
316+ . attr ( 'transform' , 'translate(0, 0)' ) // move the axis to the left
317+ . call ( g => g . selectAll ( ".tick:not(:first-of-type) line" ) // make all line dotted, except the one on the bottom as this will indicate the x-axis
318+ . attr ( "stroke-opacity" , 0.5 )
319+ . attr ( "stroke-dasharray" , "1,1" ) )
320+ . call ( g => g . selectAll ( ".tick text" ) // move the text a little so it does not overlap with the lines
321+ . attr ( "x" , - 5 ) ) ;
316322 }
317323
318324 /**
319325 * Configuration of the line generator which does print the lines
320326 */
321327 private getLineGenerator ( xScale : D3ScaleTime < number , number > ,
322328 yScale : D3ScaleLinear < number , number > ) : D3Line < TimeSeriesPoint > {
323-
329+ console . log ( xScale . ticks ( ) ) ;
324330 return d3Line < TimeSeriesPoint > ( ) // Setup a line generator
325331 . x ( ( point : TimeSeriesPoint ) => {
326332 return xScale ( point . date ) ;
327333 } ) // ... specify the data for the X-Coordinate
328334 . y ( ( point : TimeSeriesPoint ) => {
329335 return yScale ( point . value ) ;
330336 } ) // ... and for the Y-Coordinate
331- . curve ( d3CurveMonotoneX ) ; // smooth the line
337+ . curve ( d3CurveMonotoneX ) ; // smooth the line
332338
333339 }
334340
@@ -341,37 +347,90 @@ export class LineChartService {
341347 data : TimeSeries [ ] ) : void {
342348 // Create one group per line / data entry
343349 chart . selectAll ( '.line' ) // Get all lines already drawn
344- . data ( data , ( datum : TimeSeries ) => datum . key ) // ... for this data
345- . join (
346- enter => this . drawLine ( enter , xScale , yScale )
347- )
348- . attr ( 'class' , ( dataItem : TimeSeries ) => {
349- return 'line line-' + dataItem . key ;
350- } )
351-
352- //this.addDataPointsToChart(chartLineGroups, xScale, yScale, data);
350+ . data ( data , ( datum : TimeSeries ) => datum . key ) // ... for this data
351+ . join (
352+ enter => this . drawLine ( enter , xScale , yScale ) )
353+ . attr ( 'class' , ( dataItem : TimeSeries ) => {
354+ return 'line line' + dataItem . key ;
355+ } ) ;
356+
357+ //this.addDataPointsToChart(chartLineGroups, xScale, data);
358+ }
359+
360+ private addBrush ( chart : D3Selection < D3BaseType , { } , D3ContainerElement , { } > ,
361+ xScale : D3ScaleTime < number , number > ,
362+ yScale : D3ScaleLinear < number , number > ,
363+ data : TimeSeries [ ] ) {
364+ chart . selectAll ( '.brush' )
365+ . remove ( ) ;
366+ this . brush . on ( 'end' , ( ) => this . updateChart ( chart , xScale , yScale ) ) ;
367+ chart . append ( 'g' )
368+ . attr ( 'class' , 'brush' )
369+ . data ( [ 1 ] )
370+ . call ( this . brush )
371+ . on ( 'dblclick' , ( ) => {
372+ xScale . domain ( [ this . getMinDate ( data ) , this . getMaxDate ( data ) ] ) ;
373+ this . resetChart ( xScale , yScale ) ;
374+ } ) ;
375+ }
376+
377+ private resetChart ( xScale : D3ScaleTime < number , number > , yScale : D3ScaleLinear < number , number > ) {
378+ d3Select ( '.x-axis' ) . transition ( ) . call ( this . updateXAxis , xScale ) ;
379+ d3Select ( 'g#time-series-chart-drawing-area' ) . selectAll ( '.line' ) . each ( ( data , index , nodes ) => {
380+ d3Select ( nodes [ index ] ) . transition ( )
381+ . attr ( 'd' , ( dataItem : TimeSeries ) => this . getLineGenerator ( xScale , yScale ) ( dataItem . values ) )
382+ } )
383+ }
384+
385+ private updateChart ( selection : any , xScale : D3ScaleTime < number , number > , yScale : D3ScaleLinear < number , number > ) {
386+ //selected boundaries
387+ let extent = d3Event . selection ;
388+ // If no selection, back to initial coordinate. Otherwise, update X axis domain
389+ if ( ! extent ) {
390+ if ( ! this . idleTimeout ) return this . idleTimeout = setTimeout ( this . idleTimeout = null , 350 ) ; // This allows to wait a little bit
391+ //xScale = this.getXScale(data);
392+
393+ } else {
394+ xScale . domain ( [ xScale . invert ( extent [ 0 ] ) , xScale . invert ( extent [ 1 ] ) ] ) ;
395+ selection . select ( ".brush" ) . call ( this . brush . move , null ) ;
396+ d3Select ( '.x-axis' ) . transition ( ) . call ( this . updateXAxis , xScale ) ; // This remove the grey brush area as soon as the selection has been don
397+ selection . selectAll ( '.line' ) . each ( ( data , index , nodes ) => {
398+ d3Select ( nodes [ index ] )
399+ . transition ( )
400+ . attr ( 'd' , ( dataItem : TimeSeries ) => {
401+ /*let newDataItem = dataItem.values.map((point: TimeSeriesPoint) => {
402+ point.date
403+ })*/
404+ console . log ( xScale . ticks ( ) ) ;
405+ return this . getLineGenerator ( xScale , yScale ) ( dataItem . values ) ;
406+ } )
407+ }
408+ )
409+ }
410+
353411 }
354-
412+
355413 private drawLine ( selection : any ,
356414 xScale : D3ScaleTime < number , number > ,
357415 yScale : D3ScaleLinear < number , number >
358416 ) {
359417 return selection
360- . append ( 'g' ) // Group each line so we can add dots to this group latter
361- . append ( 'path' ) // Draw one path for every item in the data set
362- . attr ( 'fill' , 'none' )
363- . attr ( 'stroke' , ( d , index : number ) => { return getColorScheme ( ) [ index ] ; } )
364- . attr ( 'stroke-width' , 1.5 )
365- . attr ( 'd' , ( dataItem : TimeSeries ) => {
366- return this . getLineGenerator ( xScale , yScale ) ( dataItem . values ) ;
367- } )
368- . on ( 'mouseover' , ( ) => {
369- console . log ( "Mouse over line" ) ;
370- //this.highlightLine(this);
371- } )
372- . on ( 'mouseout' , ( ) => {
373- //normalizeColors();
374- } ) ;
418+ . append ( 'g' ) // Group each line so we can add dots to this group latter
419+ . append ( 'path' ) // Draw one path for every item in the data set
420+ . attr ( 'fill' , 'none' )
421+ . attr ( 'stroke' , ( d , index : number ) => { return getColorScheme ( ) [ index ] ; } )
422+ . attr ( 'stroke-width' , 1.5 )
423+ . attr ( 'd' , ( dataItem : TimeSeries ) => {
424+ return this . getLineGenerator ( xScale , yScale ) ( dataItem . values ) ;
425+ } )
426+ . on ( 'mouseover' , ( ) => {
427+ console . log ( "Mouse over line" ) ;
428+ //this.highlightLine(this);
429+ } )
430+ . on ( 'mouseout' , ( ) => {
431+ //normalizeColors();
432+ } ) ;
433+
375434 }
376435
377436 //private addDataPointsToChart(chartLineGroups: D3Selection<any, LineChartDataDTO, D3ContainerElement, {}>,
0 commit comments