@@ -861,17 +861,33 @@ class LineChart {
861861 private resmoothDataset ( dataset : Plottable . Dataset ) {
862862 let data = dataset . data ( ) ;
863863 const smoothingWeight = this . smoothingWeight ;
864- let last = data . length > 0 ? this . yValueAccessor ( data [ 0 ] , 0 , dataset ) : NaN ;
864+ // 1st-order IIR low-pass filter to attenuate the higher-
865+ // frequency components of the time-series.
866+ let last = data . length > 0 ? 0 : NaN ;
867+ let numAccum = 0 ;
865868 data . forEach ( ( d , i ) => {
866- if ( ! _ . isFinite ( last ) ) {
867- d . smoothed = this . yValueAccessor ( d , i , dataset ) ;
869+ let nextVal = this . yValueAccessor ( d , i , dataset ) ;
870+ if ( ! _ . isFinite ( nextVal ) ) {
871+ d . smoothed = nextVal ;
868872 } else {
869- // 1st-order IIR low-pass filter to attenuate the higher-
870- // frequency components of the time-series.
871- d . smoothed = last * smoothingWeight + (
872- 1 - smoothingWeight ) * this . yValueAccessor ( d , i , dataset ) ;
873+ last = last * smoothingWeight + ( 1 - smoothingWeight ) * nextVal ;
874+ numAccum ++ ;
875+ // The uncorrected moving average is biased towards the initial value.
876+ // For example, if initialized with `0`, with smoothingWeight `s`, where
877+ // every data point is `c`, after `t` steps the moving average is
878+ // ```
879+ // EMA = 0*s^(t) + c*(1 - s)*s^(t-1) + c*(1 - s)*s^(t-2) + ...
880+ // = c*(1 - s^t)
881+ // ```
882+ // If initialized with `0`, dividing by (1 - s^t) is enough to debias
883+ // the moving average. We count the number of finite data points and
884+ // divide appropriately before storing the data.
885+ let debiasWeight = 1 ;
886+ if ( smoothingWeight !== 1.0 ) {
887+ debiasWeight = 1.0 - Math . pow ( smoothingWeight , numAccum ) ;
888+ }
889+ d . smoothed = last / debiasWeight ;
873890 }
874- last = d . smoothed ;
875891 } ) ;
876892 }
877893
0 commit comments