@@ -861,17 +861,33 @@ class LineChart {
861
861
private resmoothDataset ( dataset : Plottable . Dataset ) {
862
862
let data = dataset . data ( ) ;
863
863
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 ;
865
868
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 ;
868
872
} 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 ;
873
890
}
874
- last = d . smoothed ;
875
891
} ) ;
876
892
}
877
893
0 commit comments