Graph moves slow with indicators

Hi, I using CandleSeries with updateDataSource to get live chart.

When i add indicators to chart, the chart behavior (Scrolling) very slow.



11 Replies

HK Hariharasudhan Kanagaraj Syncfusion Team July 25, 2023 02:07 PM UTC

Hi Bar IIan,


We have analyzed your query and implemented a simple sample with candle series and indicators using the updateDataSource method with the help of ChartSeriesController. In this sample, we have included 10 candle segments at load time and added additional candle segments through dynamic updates and real-time updates with random values. We have also enabled zooming and panning through the ZoomPanBehavior and attempted to replicate the behavior you mentioned. Unfortunately, we have been unable to replicate the mentioned behavior based on the information provided.


Kindly refer the code snippet:

class _MyHomePageState extends State<MyHomePage> {

  late List<ChartSampleData> _data;

  ChartSeriesController? _chartSeriesController;

  late ZoomPanBehavior _zoomPanBehavior;

  late Random _random;

  int count = 11;

 

  @override

  void initState() {

    _data = [

      ChartSampleData(x: 01, high: 20, low: 50, open: 35, close: 45),

      ChartSampleData(x: 02, high: 60, low: 30, open: 45, close: 55),

      ChartSampleData(x: 03, high: 20, low: 50, open: 35, close: 45),

      ChartSampleData(x: 04, high: 40, low: 10, open: 20, close: 30),

      ChartSampleData(x: 05, high: 30, low: 20, open: 25, close: 30),

      ChartSampleData(x: 06, high: 70, low: 40, open: 70, close: 40),

      ChartSampleData(x: 07, high: 90, low: 60, open: 80, close: 60),

      ChartSampleData(x: 08, high: 50, low: 20, open: 20, close: 30),

      ChartSampleData(x: 09, high: 100, low: 70, open: 80, close: 90),

      ChartSampleData(x: 10, high: 80, low: 50, open: 60, close: 70),

    ];

    _random = Random();

 

    Timer.periodic(const Duration(milliseconds: 100), (Timer timer) {

      setState(() {

        _realTimeUpdate();

      });

    });

    _zoomPanBehavior = ZoomPanBehavior(

      enableMouseWheelZooming: true,

      enablePanning: true,

    );

    super.initState();

  }

 

  void _realTimeUpdate() {

    addData();

    if (_chartSeriesController != null) {

      if (_data.length == 30) {

        _data.removeAt(0);

        _chartSeriesController!.updateDataSource(

          addedDataIndex: _data.length - 1,

          removedDataIndex: 0,

        );

      } else {

        _chartSeriesController!.updateDataSource(

          addedDataIndex: _data.length - 1,

        );

      }

    }

  }

 

  void addData() {

    _data.add(

      ChartSampleData(

        x: count,

        high: _random.nextInt(100),

        low: _random.nextInt(100),

        open: _random.nextInt(100),

        close: _random.nextInt(100),

      ),

    );

    count = count + 1;

  }

 

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: SfCartesianChart(

        primaryXAxis: NumericAxis(),

        primaryYAxis: NumericAxis(),

        series: <CartesianSeries<ChartSampleData, num>>[

          CandleSeries(

            dataSource: _data,

            enableSolidCandles: true,

            xValueMapper: (ChartSampleData sales, int index) => sales.x,

            highValueMapper: (ChartSampleData sales, int index) => sales.high,

            lowValueMapper: (ChartSampleData sales, int index) => sales.low,

            openValueMapper: (ChartSampleData sales, int index) => sales.open,

            closeValueMapper: (ChartSampleData sales, int index) => sales.close,

            onRendererCreated: (controller) {

              _chartSeriesController = controller;

            },

            name: 'Candle Series',

          ),

        ],

        indicators: <TechnicalIndicators<ChartSampleData, num>>[

          TmaIndicator<ChartSampleData, num>(

            seriesName: 'Candle Series',

            period: 8,

            signalLineColor: Colors.indigo,

          ),

        ],

        zoomPanBehavior: _zoomPanBehavior,

      ),

      floatingActionButton: FloatingActionButton.small(

        onPressed: () {

          setState(() {

            _realTimeUpdate();

          });

        },

        child: const Icon(Icons.add),

      ),

    );

  }

}


We suspect that your requirement is related to on-demand loading. However, we recommend that you provide more detailed information, including any snapshots or recordings, regarding the behavior you mentioned and to reproduce the behavior in the attached sample. This will help us better understand and to provide best possible solution from our end.


Regards,
Hari Hara Sudhan. K.


Attachment: 183646_ef8109a7.zip


BI Bar Ilan August 3, 2023 08:48 AM UTC

When adding multiple Bollinger indicators the zooming and panning very very slow.


Attachment: clideo_editor_ebbcbf870d584ca7bb75a270f35ec789.mp4_cbdeb342.zip


HK Hariharasudhan Kanagaraj Syncfusion Team August 4, 2023 01:17 PM UTC

Hi Bar IIan,


In the shared snippet below, we have added three BollingerBandIndicator and a Candle Series with live data using the updateDataSource method and enable the zooming feature in x axis only. Unfortunately, we are unable to replicate the mentioned behavior from our end. We recommend you modify it and reproduce the mentioned behavior with recoding. With those additional details, it will be useful for us to provide the best possible solution from our end.


Kindly refer the code snippet below:

class MyHomePage extends StatefulWidget {

  const MyHomePage({super.key});

 

  @override

  State<MyHomePage> createState() => _MyHomePageState();

}

 

class _MyHomePageState extends State<MyHomePage> {

  late List<ChartSampleData> _data;

  late List<ChartSampleData> _firstBollingerData;

  late List<ChartSampleData> _secondBollingerData;

  late List<ChartSampleData> _thirdBollingerData;

  ChartSeriesController? _chartSeriesController;

  late ZoomPanBehavior _zoomPanBehavior;

  late Random _random;

  int _count = 11;

  int _firstCount = 16;

  int _secondCount = 20;

  int _thirdCount = 16;

 

  @override

  void initState() {

    _data = [

      ChartSampleData(x: 01, high: 120, low: 150, open: 135, close: 145),

      ChartSampleData(x: 02, high: 160, low: 130, open: 145, close: 155),

      ChartSampleData(x: 03, high: 120, low: 150, open: 135, close: 145),

      ChartSampleData(x: 04, high: 140, low: 110, open: 120, close: 130),

      ChartSampleData(x: 05, high: 130, low: 120, open: 125, close: 130),

      ChartSampleData(x: 06, high: 170, low: 140, open: 170, close: 140),

      ChartSampleData(x: 07, high: 190, low: 160, open: 180, close: 160),

      ChartSampleData(x: 08, high: 150, low: 120, open: 120, close: 130),

      ChartSampleData(x: 09, high: 200, low: 170, open: 180, close: 190),

      ChartSampleData(x: 10, high: 180, low: 150, open: 160, close: 170),

    ];

    _firstBollingerData = <ChartSampleData>[

      ChartSampleData(x: 06, close: 85),

      ChartSampleData(x: 07, close: 35),

      ChartSampleData(x: 08, close: 70),

      ChartSampleData(x: 09, close: 20),

      ChartSampleData(x: 10, close: 50),

      ChartSampleData(x: 11, close: 60),

      ChartSampleData(x: 12, close: 40),

      ChartSampleData(x: 13, close: 25),

      ChartSampleData(x: 14, close: 30),

      ChartSampleData(x: 15, close: 65),

    ];

    _secondBollingerData = <ChartSampleData>[

      ChartSampleData(x: 01, close: 50),

      ChartSampleData(x: 03, close: 55),

      ChartSampleData(x: 05, close: 60),

      ChartSampleData(x: 07, close: 65),

      ChartSampleData(x: 09, close: 40),

      ChartSampleData(x: 11, close: 35),

      ChartSampleData(x: 13, close: 30),

      ChartSampleData(x: 15, close: 25),

      ChartSampleData(x: 17, close: 20),

      ChartSampleData(x: 19, close: 10),

    ];

    _thirdBollingerData = <ChartSampleData>[

      ChartSampleData(x: 06, close: 200),

      ChartSampleData(x: 07, close: 255),

      ChartSampleData(x: 08, close: 230),

      ChartSampleData(x: 09, close: 185),

      ChartSampleData(x: 10, close: 430),

      ChartSampleData(x: 11, close: 325),

      ChartSampleData(x: 12, close: 320),

      ChartSampleData(x: 13, close: 295),

      ChartSampleData(x: 14, close: 360),

      ChartSampleData(x: 15, close: 410),

    ];

    _random = Random();

 

    Timer.periodic(const Duration(milliseconds: 300), (Timer timer) {

      setState(() {

        _realTimeUpdate();

      });

    });

    _zoomPanBehavior = ZoomPanBehavior(

      enableMouseWheelZooming: true,

      enablePanning: true,

      zoomMode: ZoomMode.x,

    );

    super.initState();

  }

 

  void _realTimeUpdate() {

    addData();

    if (_chartSeriesController != null) {

      if (_data.length == 30) {

        _data.removeAt(0);

        _firstBollingerData.removeAt(0);

        _secondBollingerData.removeAt(0);

        _thirdBollingerData.removeAt(0);

        _chartSeriesController!.updateDataSource(

          addedDataIndexes: [

            _data.length - 1,

            _firstBollingerData.length - 1,

            _secondBollingerData.length - 1,

            _thirdBollingerData.length - 1

          ],

          removedDataIndex: 0,

        );

      } else {

        _chartSeriesController!.updateDataSource(

          addedDataIndexes: [

            _data.length - 1,

            _firstBollingerData.length - 1,

            _secondBollingerData.length - 1,

            _thirdBollingerData.length - 1

          ],

        );

      }

    }

  }

 

  void addData() {

    _data.add(

      ChartSampleData(

        x: _count,

        high: _random.nextInt(100),

        low: _random.nextInt(100),

        open: _random.nextInt(100),

        close: _random.nextInt(100),

      ),

    );

    _firstBollingerData.add(

      ChartSampleData(

        x: _firstCount,

        close: _random.nextInt(100),

      ),

    );

    _secondBollingerData.add(

      ChartSampleData(

        x: _secondCount,

        close: _random.nextInt(200),

      ),

    );

    _thirdBollingerData.add(

      ChartSampleData(

        x: _thirdCount,

        close: _random.nextInt(400),

      ),

    );

    _count = _count + 1;

    _firstCount = _firstCount + 1;

    _secondCount = _secondCount + 1;

    _thirdCount = _thirdCount + 1;

  }

 

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: SfCartesianChart(

        primaryXAxis: NumericAxis(),

        primaryYAxis: NumericAxis(),

        series: <CartesianSeries<ChartSampleData, num>>[

          CandleSeries(

            dataSource: _data,

            enableSolidCandles: true,

            xValueMapper: (ChartSampleData sales, int index) => sales.x,

            highValueMapper: (ChartSampleData sales, int index) => sales.high,

            lowValueMapper: (ChartSampleData sales, int index) => sales.low,

            openValueMapper: (ChartSampleData sales, int index) => sales.open,

            closeValueMapper: (ChartSampleData sales, int index) => sales.close,

            onRendererCreated: (controller) {

              _chartSeriesController = controller;

            },

            name: 'Candle Series',

          ),

        ],

        indicators: <TechnicalIndicators<ChartSampleData, num>>[

          BollingerBandIndicator<ChartSampleData, num>(

            period: 5,

            dataSource: _firstBollingerData,

            xValueMapper: (ChartSampleData sales, int index) => sales.x,

            closeValueMapper: (ChartSampleData sales, int index) => sales.close,

            signalLineColor: Colors.yellowAccent,

            upperLineColor: Colors.purpleAccent,

            lowerLineColor: Colors.orangeAccent,

          ),

          BollingerBandIndicator<ChartSampleData, num>(

            period: 14,

            standardDeviation: 8,

            dataSource: _secondBollingerData,

            upperLineWidth: 4,

            signalLineWidth: 4,

            lowerLineWidth: 4,

            xValueMapper: (ChartSampleData sales, int index) => sales.x,

            closeValueMapper: (ChartSampleData sales, int index) => sales.close,

            signalLineColor: Colors.tealAccent,

            upperLineColor: Colors.blue,

            lowerLineColor: Colors.indigo,

          ),

          BollingerBandIndicator<ChartSampleData, num>(

            period: 8,

            dataSource: _thirdBollingerData,

            xValueMapper: (ChartSampleData sales, int index) => sales.x,

            closeValueMapper: (ChartSampleData sales, int index) => sales.close,

          ),

        ],

        zoomPanBehavior: _zoomPanBehavior,

      ),

    );

  }

}


Also shared the recording below for your reference.


Regards,

Hari Hara Sudhan. K.


Attachment: _488648_aa2cac7c.zip


BI Bar Ilan August 8, 2023 02:51 PM UTC

My chart data length is 2048, when i have add one BollingerBandIndicator the scrolling is very very slow.

I using DateTimeCategoryAxis.





HK Hariharasudhan Kanagaraj Syncfusion Team August 9, 2023 12:32 PM UTC

Hi Bar IIan,


When rendering a large number of data points in the SfCartesianChart, we prefer using DateTimeAxis instead of DateTimeCategoryAxis for better performance. We recommend that you check whether the chart scrolling is smooth when using the DateTimeAxis instead of DateTimeCategoryAxis. With this additional information, we will be able to provide the best possible solution from our end.


Regards,

Hari Hara Sudhan. K.



BI Bar Ilan August 9, 2023 12:36 PM UTC

Thanks for your quick response.

Unfortunately, I cannot use DateTimeAxis because some of my points are empty.




HK Hariharasudhan Kanagaraj Syncfusion Team August 10, 2023 01:00 PM UTC

Hi Bar IIan,


We would like to get clarification on whether the empty data points have been given to the x-axis or y-axis. Because, though it is possible to assign empty points to the x-axis, we should ignore them as it is not the right way to render the chart. However, if you have assigned the empty points to the y-axis, you can handle them as required using the emptyPointSettings property in the respective series.


We recommend using the EmptyPointMode.drop for the mode property, as it ignores the current segment with empty points and renders the next available segment. Additionally, we suggest checking the chart scrolling using the DateTimeAxis for better performance.


We have also shared the User Guide documentation for the empty point settings below. If you have any queries, please feel free to reach out to us.


User Guide: https://help.syncfusion.com/flutter/cartesian-charts/series-customization#empty-points.


Regards,

Hari Hara Sudhan. K.



BI Bar Ilan August 10, 2023 01:30 PM UTC

Hi, 

Having an empty points means I don't have  consecutive days in the data.

for example between 30.6 - 3.7 :


Image_2748_1691674044994



HK Hariharasudhan Kanagaraj Syncfusion Team August 17, 2023 02:47 PM UTC

Hi Bar IIan,


Sorry for the delay. We would like to inform you that when rendering a large number of data points, along with user interactions, the better performance can be achieved by using the DateTimeAxis only in comparision to the DateTimeCategoryAxis. However, we have already logged a FR for the DateTimeCategory Axis with zooming and we will consider this issue within the same FR shared below.


Feedback Link: https://www.syncfusion.com/feedback/44283.


Additionally, to achieve better performance and to render non-consecutive data points in the DateTimeAxis, we can implement the EnableBusinessHours feature in the SfCartesianChart, which is currently available in the WPF chart. This feature will improve the performance of the chart and shared the User Guide documentation of WPF chart regarding the Business Hours Range Calculation in DateTimeAxis using the EnableBusinessHours below for your reference.


UG: https://help.syncfusion.com/wpf/charts/axis#datetimeaxis.


Based on your use case and requirement, we can be able to decide whether the above-mentioned feature should be considered as feature request or not. If you have further queries, please get back to us.


Regards,
Hari Hara Sudhan. K.



BI Bar Ilan August 22, 2023 10:03 AM UTC

Access Denied via this link https://www.syncfusion.com/feedback/44283



HK Hariharasudhan Kanagaraj Syncfusion Team August 23, 2023 10:13 AM UTC

Hi Bar IIan,


Sorry for the inconvenience.


We missed including your email address, but now we have included it in the feedback link. So, You can be able to track the status of the feedback with the feedback link below.


Feedback Link: https://www.syncfusion.com/feedback/44283.


Regards,
Hari Hara Sudhan. K.


Loader.
Up arrow icon