Add combinePaths function & Performance improvement - #245
Conversation
…nto add-web-support
…rsection.dart Co-authored-by: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com>
…tter-chart into html-renderer-fix
…nto html-renderer-fix
…nto html-renderer-fix
…nto html-renderer-fix
|
|
||
| final CachedIndicator<Tick> bbmSMA = | ||
| MASeries.getMAIndicator(_fieldIndicator, bbOptions); | ||
| MASeries.getMAIndicator(_fieldIndicator, bbOptions)..calculateValues(); |
There was a problem hiding this comment.
To fix stack overflow error when too much tick data is loaded.
There was a problem hiding this comment.
@balakrishna-deriv I think we should not call calculateValues in createPainter. There are some in other Indicator series even before you guys work on it. I think those also should be removed as well
It will cause the series indicator to calculate all of its values for the entire ticks at every tick update.
The idea was to let AbstractSingleIndicatorSeries to decide when to call calculateValues and when to reuse the old result to avoid unnecessary computations.
What is the scenario which causes the stack overflow issue? for how many tick data?
There was a problem hiding this comment.
@ramin-deriv Yes, calculateValues code was already there at a few places before we started working on this.
The issue happens because the calculate function is called recursively. It is reproducible on a big screen where there are more than 5,000 ticks. It happens on the dev branch too, you can try loading the app on a big landscape tablet and add indicators like ADX, SMI.
There was a problem hiding this comment.
Yes, I saw there are some in other places too. they would be unnecessary calculations and I think they also should be removed.
The thing is that AbstractSingleIndicatorSeries in the beginning calls calculateValues on the indicator. and it avoids hitting the overflow issues.
I guess some nested indicators in BollingerBands can't reuse the old indicator data and go to calculate for all and hit this issue.
But regardless I think we should find the reason ASAP since it will cause to many unnecessary calculation and will slowdown the chart when the tick history is big
I'm checking right now to see how nested indicator in BollingersBands do reuse the old result.
In addition, there can be a back-up solution by keeping track of the indices that don't have calculated result and when happens we go in a loop and calculate values in the loop and not wait for it to be calculated recursively which would have the chance of facing overflow
There was a problem hiding this comment.
@ramin-deriv I get your point but not every indicator series is extending AbstractSingleIndicatorSeries. 😅
For example: ADX Series.

calculateValues in the initialize is not called for indicators like bollinger, adx, smi, etc.. because these indicators are not extending AbstractSingleIndicatorSeries.
calculateValues is called only for indicators such as cci which extends AbstractSingleIndicatorSeries.
I think it's better to do the refactor in a separate PR since we already have this pattern in the dev branch. 😄
There was a problem hiding this comment.
Yes, those that have single line of data extend AbstractSingleIndicatorSeries,
but other indicators that directly extend series are in-fact composition of multiple indicators. and on the life-cycle methods they delegate to their internal series and call the internal series methods accordingly.
and the internal series are SingleIndicatorSeries that extends the abstract one.

For sure, We can do it in another. no problem. can we create a card for it to do it in the earliest time?
| if (_minElapsedTimeToFollow != null && | ||
| elapsedMs < _minElapsedTimeToFollow!) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
onNewFrame is called on every frame. On a 60fps screen, this function is getting called 60 times and it changes the rightBoundEpoch to move the xAxis.
The problem happens when we add multiple indicators to the charts. We added a prop minElapsedTimeToFollow to control the no of frames drawn per second. When there are more indicators, we draw fewer frames to make sure the chart renders smoothly.
| .map((Tick entry) => | ||
| Tick(epoch: entry.epoch, quote: _topHorizontalLine!)) | ||
| .toList(); | ||
| topIntersections = combinePaths( |
There was a problem hiding this comment.
Can we call this one time for both here and in line 127. and use for both places?
There was a problem hiding this comment.
@ramin-deriv Unfortunately, we can't do that because horizontalLineEntries are different in both the places.
At line 87, horizontalLineEntries is created with _topHorizontalLine and at line 127 horizontalLineEntries is created with _bottomHorizontalLine.
| _currentTickAnimationController = AnimationController( | ||
| vsync: this, | ||
| duration: const Duration(milliseconds: 300), | ||
| duration: _currentTickAnimationDuration, |
There was a problem hiding this comment.
And in the state class we just directly use widget.currentTickAnimationDuration.
There was a problem hiding this comment.
this is done :)
| if (widget.currentTickAnimationDuration != null && | ||
| widget.currentTickAnimationDuration!.inMilliseconds != | ||
| _currentTickAnimationDuration.inMilliseconds) { | ||
| _setupCurrentTickAnimation(); | ||
| } | ||
|
|
||
| if (widget.quoteBoundsAnimationDuration != null && | ||
| widget.quoteBoundsAnimationDuration!.inMilliseconds != | ||
| _quoteBoundsAnimationDuration.inMilliseconds) { | ||
| _setupBoundsAnimation(); | ||
| } |
There was a problem hiding this comment.
I think this part would change to this. and Duration implements comparable so I think we can just compare them without using inMillise...
if (widget.currentTickAnimationDuration !=
oldChart.currentTickAnimationDuration) {
_setupCurrentTickAnimation();
}
if (widget.quoteBoundsAnimationDuration !=
oldChart.quoteBoundsAnimationDuration) {
_setupBoundsAnimation();
}
There was a problem hiding this comment.
updated this
| this.currentTickAnimationDuration, | ||
| this.quoteBoundsAnimationDuration, |
There was a problem hiding this comment.
I think these can set the default duration themselves. after becoming non-nullable.
| this.currentTickAnimationDuration, | |
| this.quoteBoundsAnimationDuration, | |
| this.currentTickAnimationDuration = const Duration(mili, | |
| this.quoteBoundsAnimationDuration = const Duration(mi, |
There was a problem hiding this comment.
updated this
| Duration? currentTickAnimationDuration, | ||
| Duration? quoteBoundsAnimationDuration, |
There was a problem hiding this comment.
| Duration? currentTickAnimationDuration, | |
| Duration? quoteBoundsAnimationDuration, | |
| Duration? currentTickAnimationDuration, | |
| Duration? quoteBoundsAnimationDuration, |
| Duration? currentTickAnimationDuration, | |
| Duration? quoteBoundsAnimationDuration, | |
| super.currentTickAnimationDuration, | |
| super.quoteBoundsAnimationDuration, |
| // TODO(NA): Consider refactoring the switch with OOP pattern. https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html | ||
| switch (_currentViewingMode) { | ||
| case ViewingMode.followCurrentTick: | ||
| if (_minElapsedTimeToFollow != null && |
There was a problem hiding this comment.
Can we make minElapsedTimeToFollow non-nullable and with default value. so we don't have to check here.
There was a problem hiding this comment.
this is done as well

Issue:
Path.combineis not implemented in HTML mode of the flutter charts engine - flutter/flutter#44572.In this PR, we have added a function
combinePathswhich is a replacement forPath.combineused inChannelFillPainterandOscillatorLinePainterin HTML mode.combinePathsfunction combines and intersects two paths to generate upper and lower paths.