Skip to content

Add combinePaths function & Performance improvement - #245

Merged
raminvakili8 merged 116 commits into
deriv-com:fe-changesfrom
balakrishna-deriv:html-renderer-fix
Oct 17, 2023
Merged

Add combinePaths function & Performance improvement#245
raminvakili8 merged 116 commits into
deriv-com:fe-changesfrom
balakrishna-deriv:html-renderer-fix

Conversation

@balakrishna-deriv

@balakrishna-deriv balakrishna-deriv commented Aug 15, 2023

Copy link
Copy Markdown
Contributor

Issue: Path.combine is not implemented in HTML mode of the flutter charts engine - flutter/flutter#44572.

In this PR, we have added a function combinePaths which is a replacement for Path.combine used in ChannelFillPainter and OscillatorLinePainter in HTML mode. combinePaths function combines and intersects two paths to generate upper and lower paths.

  • Added two props to control the animation duration
  • Added a prop to control the frames drawn per second

balakrishna-deriv and others added 30 commits November 1, 2022 11:13
…rsection.dart

Co-authored-by: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com>
@balakrishna-deriv balakrishna-deriv changed the title Add combinePaths function Add combinePaths function & Performance improvement Oct 6, 2023
Comment thread lib/src/deriv_chart/chart/basic_chart.dart

final CachedIndicator<Tick> bbmSMA =
MASeries.getMAIndicator(_fieldIndicator, bbOptions);
MASeries.getMAIndicator(_fieldIndicator, bbOptions)..calculateValues();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To fix stack overflow error when too much tick data is loaded.

@raminvakili8 raminvakili8 Oct 11, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

image

Bug video:
https://github.com/regentmarkets/flutter-chart/assets/56330681/918ce872-e07d-49d0-a20c-3fbc0e5d78de

@raminvakili8 raminvakili8 Oct 11, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@balakrishna-deriv balakrishna-deriv Oct 11, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ramin-deriv I get your point but not every indicator series is extending AbstractSingleIndicatorSeries. 😅
For example: ADX Series.
image

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. 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image I think If we add the initialize function to the series and call it on update it would work.

I can do it in a separate PR later as I have to test all the indicators again 😅

@raminvakili8 raminvakili8 Oct 11, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
image

For sure, We can do it in another. no problem. can we create a card for it to do it in the earliest time?

Comment on lines +223 to +226
if (_minElapsedTimeToFollow != null &&
elapsedMs < _minElapsedTimeToFollow!) {
return;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call this one time for both here and in line 127. and use for both places?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment thread lib/src/deriv_chart/chart/basic_chart.dart Outdated
_currentTickAnimationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
duration: _currentTickAnimationDuration,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And in the state class we just directly use widget.currentTickAnimationDuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is done :)

Comment on lines +170 to +180
if (widget.currentTickAnimationDuration != null &&
widget.currentTickAnimationDuration!.inMilliseconds !=
_currentTickAnimationDuration.inMilliseconds) {
_setupCurrentTickAnimation();
}

if (widget.quoteBoundsAnimationDuration != null &&
widget.quoteBoundsAnimationDuration!.inMilliseconds !=
_quoteBoundsAnimationDuration.inMilliseconds) {
_setupBoundsAnimation();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
    }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this

Comment on lines +56 to +57
this.currentTickAnimationDuration,
this.quoteBoundsAnimationDuration,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these can set the default duration themselves. after becoming non-nullable.

Suggested change
this.currentTickAnimationDuration,
this.quoteBoundsAnimationDuration,
this.currentTickAnimationDuration = const Duration(mili,
this.quoteBoundsAnimationDuration = const Duration(mi,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this

Comment on lines +52 to +53
Duration? currentTickAnimationDuration,
Duration? quoteBoundsAnimationDuration,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Duration? currentTickAnimationDuration,
Duration? quoteBoundsAnimationDuration,
Duration? currentTickAnimationDuration,
Duration? quoteBoundsAnimationDuration,
Suggested change
Duration? currentTickAnimationDuration,
Duration? quoteBoundsAnimationDuration,
super.currentTickAnimationDuration,
super.quoteBoundsAnimationDuration,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// TODO(NA): Consider refactoring the switch with OOP pattern. https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html
switch (_currentViewingMode) {
case ViewingMode.followCurrentTick:
if (_minElapsedTimeToFollow != null &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make minElapsedTimeToFollow non-nullable and with default value. so we don't have to check here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is done as well

@raminvakili8
raminvakili8 merged commit 697ca92 into deriv-com:fe-changes Oct 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants