@@ -39,6 +39,12 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
39
39
public config : TimelineConfigInterface < Datum > = this . _defaultConfig
40
40
41
41
events = {
42
+ [ Timeline . selectors . background ] : {
43
+ wheel : this . _onMouseWheel . bind ( this ) ,
44
+ } ,
45
+ [ Timeline . selectors . label ] : {
46
+ wheel : this . _onMouseWheel . bind ( this ) ,
47
+ } ,
42
48
[ Timeline . selectors . rows ] : {
43
49
wheel : this . _onMouseWheel . bind ( this ) ,
44
50
} ,
@@ -134,7 +140,7 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
134
140
. call ( trimSVGText , config . rowMaxLabelWidth ?? config . maxLabelWidth )
135
141
136
142
const labelWidth = label . node ( ) . getBBox ( ) . width
137
- this . _labelsGroup . empty ( )
143
+ label . remove ( )
138
144
139
145
const tolerance = 1.15 // Some characters are wider than others so we add a little of extra space to take that into account
140
146
this . _labelWidth = labelWidth ? tolerance * labelWidth + this . _labelMargin : 0
@@ -156,7 +162,7 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
156
162
157
163
// Small segments bleed
158
164
const lineBleed = [ 1 , 1 ] as [ number , number ]
159
- if ( config . showEmptySegments && config . lineCap ) {
165
+ if ( config . showEmptySegments && config . lineCap && firstItem && lastItem ) {
160
166
const firstItemStart = getNumber ( firstItem , config . x , firstItemIdx )
161
167
const firstItemEnd = getNumber ( firstItem , config . x , firstItemIdx ) + this . _getLineDuration ( firstItem , firstItemIdx )
162
168
const lastItemStart = getNumber ( lastItem , config . x , lastItemIdx )
@@ -173,11 +179,11 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
173
179
// Icon bleed
174
180
const iconBleed = [ 0 , 0 ] as [ number , number ]
175
181
if ( config . lineStartIcon ) {
176
- iconBleed [ 0 ] = getIconBleed ( firstItem , firstItemIdx , config . lineStartIcon , config . lineStartIconSize , config . lineStartIconArrangement , rowHeight )
182
+ iconBleed [ 0 ] = max ( data , ( d , i ) => getIconBleed ( d , i , config . lineStartIcon , config . lineStartIconSize , config . lineStartIconArrangement , rowHeight ) )
177
183
}
178
184
179
185
if ( config . lineEndIcon ) {
180
- iconBleed [ 1 ] = getIconBleed ( lastItem , lastItemIdx , config . lineEndIcon , config . lineEndIconSize , config . lineEndIconArrangement , rowHeight )
186
+ iconBleed [ 1 ] = max ( data , ( d , i ) => getIconBleed ( d , i , config . lineEndIcon , config . lineEndIconSize , config . lineEndIconArrangement , rowHeight ) )
181
187
}
182
188
183
189
this . _rowIconBleed = iconBleed
@@ -435,7 +441,8 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
435
441
this . _updateScrollPosition ( 0 )
436
442
437
443
// Clip path
438
- this . _clipPath . select ( 'rect' )
444
+ const clipPathRect = this . _clipPath . select ( 'rect' )
445
+ smartTransition ( clipPathRect , clipPathRect . attr ( 'width' ) ? duration : 0 )
439
446
. attr ( 'x' , xStart )
440
447
. attr ( 'width' , timelineWidth )
441
448
. attr ( 'height' , this . _height )
@@ -470,20 +477,20 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
470
477
this . _getRecordKey ( d , i ) , getNumber ( d , config . x , i ) ,
471
478
] . join ( '-' )
472
479
473
- const lineHeight = this . _getLineWidth ( d , i , rowHeight )
480
+ const lineWidth = this . _getLineWidth ( d , i , rowHeight )
474
481
const lineLength = this . _getLineLength ( d , i )
475
482
476
483
if ( lineLength < 0 ) {
477
484
console . warn ( 'Unovis | Timeline: Line segments should not have negative lengths. Setting to 0.' )
478
485
}
479
486
480
- const isLineTooShort = config . showEmptySegments && config . lineCap && ( lineLength < lineHeight )
487
+ const isLineTooShort = config . showEmptySegments && config . lineCap && ( lineLength < lineWidth )
481
488
const lineLengthCorrected = config . showEmptySegments
482
- ? Math . max ( config . lineCap ? lineHeight : 1 , lineLength )
489
+ ? Math . max ( config . lineCap ? lineWidth : 1 , lineLength )
483
490
: Math . max ( 0 , lineLength )
484
491
485
492
const x = xScale ( getNumber ( d , config . x , i ) )
486
- const y = yStart + rowOrdinalScale ( this . _getRecordKey ( d , i ) ) * rowHeight + ( rowHeight - lineHeight ) / 2
493
+ const y = yStart + rowOrdinalScale ( this . _getRecordKey ( d , i ) ) * rowHeight + ( rowHeight - lineWidth ) / 2
487
494
const xOffset = isLineTooShort ? - ( lineLengthCorrected - lineLength ) / 2 : 0
488
495
489
496
return {
@@ -493,10 +500,10 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
493
500
_yPx : y ,
494
501
_xOffsetPx : xOffset ,
495
502
_length : lineLength ,
496
- _height : lineHeight ,
503
+ _height : lineWidth ,
497
504
_lengthCorrected : lineLengthCorrected ,
498
- _startIconSize : getNumber ( d , config . lineStartIconSize , i ) ?? lineHeight ,
499
- _endIconSize : getNumber ( d , config . lineEndIconSize , i ) ?? lineHeight ,
505
+ _startIconSize : getNumber ( d , config . lineStartIconSize , i ) ?? lineWidth ,
506
+ _endIconSize : getNumber ( d , config . lineEndIconSize , i ) ?? lineWidth ,
500
507
_startIconColor : getString ( d , config . lineStartIconColor , i ) ,
501
508
_endIconColor : getString ( d , config . lineEndIconColor , i ) ,
502
509
_startIconArrangement : getValue ( d , config . lineStartIconArrangement , i ) ?? Arrangement . Outside ,
@@ -528,28 +535,38 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf
528
535
? this . xScale ( a . xSource )
529
536
: this . xScale ( getNumber ( sourceLine , config . x , sourceLineIndex ) ) + this . _getLineLength ( sourceLine , sourceLineIndex )
530
537
) + ( a . xSourceOffsetPx ?? 0 )
531
- const targetLineStart = this . xScale ( getNumber ( targetLine , config . x , targetLineIndex ) )
538
+ const targetLineLength = this . _getLineLength ( targetLine , targetLineIndex )
539
+ const isTargetLineTooShort = config . showEmptySegments && config . lineCap && ( targetLineLength < targetLineWidth )
540
+ const targetLineStart = this . xScale ( getNumber ( targetLine , config . x , targetLineIndex ) ) + ( isTargetLineTooShort ? - targetLineWidth / 2 : 0 )
532
541
const x2 = ( a . xTarget ? this . xScale ( a . xTarget ) : targetLineStart ) + ( a . xTargetOffsetPx ?? 0 )
533
542
const isX2OutsideTargetLineStart = ( x2 < targetLineStart ) || ( x2 > targetLineStart )
534
543
535
544
// Points array
536
545
const sourceMargin = a . lineSourceMarginPx ?? TIMELINE_DEFAULT_ARROW_MARGIN
537
546
const targetMargin = a . lineTargetMarginPx ?? TIMELINE_DEFAULT_ARROW_MARGIN
538
- const y1 = sourceLineY < targetLineY ? sourceLineY + sourceLineWidth / 2 : sourceLineY - sourceLineWidth / 2
539
- const y2 = sourceLineY < targetLineY ? targetLineY - targetLineWidth / 2 : targetLineY + targetLineWidth / 2
540
- const points = [ [ x1 , y1 + sourceMargin ] ] as [ number , number ] [ ]
541
- const threshold = 5
547
+ const y1 = sourceLineY < targetLineY ? sourceLineY + sourceLineWidth / 2 + sourceMargin : sourceLineY - sourceLineWidth / 2 - sourceMargin
548
+ const y2 = sourceLineY < targetLineY ? targetLineY - targetLineWidth / 2 - targetMargin : targetLineY + targetLineWidth / 2 + targetMargin
549
+ const arrowHeadLength = a . arrowHeadLength ?? TIMELINE_DEFAULT_ARROW_HEAD_LENGTH
550
+ const isForwardArrow = x1 < x2 && ! isX2OutsideTargetLineStart
551
+ const threshold = arrowHeadLength + ( isForwardArrow ? targetMargin : 0 )
552
+
553
+ const points = [ [ x1 , y1 ] ] as [ number , number ] [ ]
542
554
if ( Math . abs ( x2 - x1 ) > threshold ) {
543
- if ( ( x1 < x2 ) && ! isX2OutsideTargetLineStart ) {
555
+ if ( isForwardArrow ) {
556
+ points . push ( [ x1 , ( y1 + targetLineY ) / 2 ] ) // A dummy point to enable smooth transitions when arrows change
544
557
points . push ( [ x1 , targetLineY ] )
545
558
points . push ( [ x2 - targetMargin , targetLineY ] )
546
559
} else {
547
- points . push ( [ x1 , y1 + Math . sign ( targetLineY - sourceLineY ) * ( rowHeight / 2 - sourceMargin ) ] )
548
- points . push ( [ x2 , y1 + Math . sign ( targetLineY - sourceLineY ) * ( rowHeight / 2 - sourceMargin ) ] )
549
- points . push ( [ x2 , y2 - targetMargin ] )
560
+ const verticalOffset = Math . sign ( targetLineY - sourceLineY ) * ( rowHeight / 4 )
561
+ points . push ( [ x1 , y2 - verticalOffset ] )
562
+ points . push ( [ x2 , y2 - verticalOffset ] )
563
+ points . push ( [ x2 , y2 ] )
550
564
}
551
565
} else {
552
- points . push ( [ x1 , y2 - targetMargin ] )
566
+ const quarterOffset = ( y2 - y1 ) / 4
567
+ points . push ( [ x1 , y1 + quarterOffset ] ) // A dummy point to enable smooth transitions
568
+ points . push ( [ x1 , y1 + 3 * quarterOffset ] ) // A dummy point to enable smooth transitions
569
+ points . push ( [ x1 , y2 ] )
553
570
}
554
571
555
572
return {
0 commit comments