syncfusion / flutter-widgets

Syncfusion Flutter widgets libraries include high quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base.
1.56k stars 760 forks source link

Experiencing RangeError in SfCartesianChart CandleSeries #1916

Closed bilalep closed 2 months ago

bilalep commented 3 months ago

Bug description

I am currently facing a RangeError in candle chart. I think I am doing something wrong. but couldn't figure it out. I am using bloc to convert a stream of (double, DateTime) into a list of a custom wrapper class for open, close, high. low and time. And this error is encountered whenever a new candle is added to the chart. It goes away for a brief amount time and comes back after next candle insertion.

Steps to reproduce

.

Code sample

Code sample ```dart SfCartesianChart( plotAreaBorderWidth: 0, zoomPanBehavior: _zoomPanBehavior, onChartTouchInteractionMove: (ChartTouchInteractionArgs args) { isPointerMoved = true; }, onChartTouchInteractionUp: (ChartTouchInteractionArgs args) { isPointerMoved = false; }, margin: EdgeInsets.zero, primaryXAxis: DateTimeAxis( // visibleMinimum: visibleRange?.start, // visibleMaximum: visibleRange?.end, majorGridLines: const MajorGridLines(width: 0), edgeLabelPlacement: EdgeLabelPlacement.shift, onRendererCreated: (p0) { // print('onRendererCreated'); // _xAxisController = p0; // _xAxisController?.visibleMinimum = visibleRange?.start; // // _xAxisController?.visibleMaximum = visibleRange?.end; }, ), primaryYAxis: NumericAxis( minimum: minY, maximum: maxY, majorGridLines: const MajorGridLines(width: 0.5), labelFormat: '{value}', opposedPosition: true, axisLine: const AxisLine(width: 0), ), series: _getCandleSeries(), trackballBehavior: TrackballBehavior( enable: true, activationMode: ActivationMode.singleTap, ), ) List> _getCandleSeries() { return >[ CandleSeries( animationDuration: 300, enableSolidCandles: true, dataSource: widget.chartData, xValueMapper: (CandleChartData sales, _) => sales.timestamp, lowValueMapper: (CandleChartData sales, _) => sales.low, highValueMapper: (CandleChartData sales, _) => sales.high, openValueMapper: (CandleChartData sales, _) => sales.open, closeValueMapper: (CandleChartData sales, _) => sales.close, ), ]; } ```

Screenshots or Video

Screenshots / Video demonstration [Upload media here]

Stack Traces

Stack Traces ```dart [log] #0 List.[] (dart:core-patch/growable_array.dart:264:36) #1 CandleSegment.onPaint (package:syncfusion_flutter_charts/src/charts/series/candle_series.dart:480:35) #2 CartesianSeriesRenderer.paintSegments (package:syncfusion_flutter_charts/src/charts/series/chart_series.dart:4067:23) #3 CartesianSeriesRenderer.onPaint (package:syncfusion_flutter_charts/src/charts/series/chart_series.dart:4044:5) #4 ChartSeriesRenderer.paint (package:syncfusion_flutter_charts/src/charts/series/chart_series.dart:2409:5) #5 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:3239:7) #6 PaintingContext.paintChild (package:flutter/src/rendering/object.dart:250:13) #7 RenderBoxContainerDefaultsMixin.defaultPaint (package:flutter/src/rendering/box.dart:3158:15) #8 RenderStack.paintStack (package:flutter/src/rendering/stack.dart:633:5) #9 RenderStack.paint (package:flutter/src/rendering/stack.dart:649:7) #10 RenderChartPlotArea.paint (package:syncfusion_flutter_charts/src/charts/base.dart:1211:11) #11 RenderCartesianChartPlotArea.paint (package:syncfusion_flutter_charts/src/charts/base.dart:1661:11) #12 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:3239:7) #13 PaintingContext.paintChild (package:flutter/src/rendering/object.dart:250:13) #14 RenderBoxContainerDefaultsMixin.defaultPaint (package:flutter/src/rendering/box.dart:3158:15) #15 RenderCartesianChartArea.paint (package:syncfusion_flutter_charts/src/charts/base.dart:882:5) #16 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:3239:7) #17 PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:166:11) #18 PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:109:5) #19 PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1182:31) #20 PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1192:15) #21 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:579:23) #22 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1138:13) #23 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:443:5) #24 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1392:15) #25 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1313:9) #26 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:5) #27 _invoke (dart:ui/hooks.dart:312:13) #28 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5) #29 _drawFrame (dart:ui/hooks.dart:283:31) ```

On which target platforms have you observed this bug?

iOS

Flutter Doctor output

Doctor output ```console Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.22.0, on macOS 14.4.1 23E224 darwin-arm64, locale en-IN) [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 15.4) [✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome) ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable. [✓] Android Studio (version 2023.3) [✓] VS Code (version 1.89.1) [✓] Connected device (4 available) [✓] Network resources ! Doctor found issues in 1 category. ```
PreethikaSelvam commented 3 months ago

Hi @bilalep,

We have analyzed your query and found that the reported issue occurs only when we use the bloc to convert a stream, replacing the first data points with the new data. This issue is scheduled to be fixed in our upcoming weekly release which is scheduled on June 25, 2024. We will update you here once the release is rolled out and we appreciate your patience until then.

Additionally, if your requirement is to add new data to the end of the pervious data using bloc to convert a stream, we suggest the below code snippets and sample to achieve your requirement.

Code Snippet:

` late CandleBloc _bloc;

List _chartData = [];

@override

void initState() {

super.initState();

_bloc = CandleBloc();

_bloc.candleStream.listen((data) {

  setState(() {

    _chartData.addAll(data);

  });

});

}

@override

void dispose() {

_bloc.dispose();

super.dispose();

}

@override

Widget build(BuildContext context) {

return Scaffold(

  appBar: AppBar(

    title: Text('Candle Chart Demo'),

  ),

  body: Center(

    child: _chartData.isNotEmpty

        ? Padding(

            padding: const EdgeInsets.all(8.0),

            child: SizedBox(

              height: 300,

              child: SfCartesianChart(

                primaryXAxis: DateTimeAxis(),

                primaryYAxis: NumericAxis(),

                series: _getCandleSeries(_chartData),

              ),

            ),

          )

        : CircularProgressIndicator(),

  ),

);

}

List<CandleSeries<CandleChartData, DateTime>> _getCandleSeries(

  List<CandleChartData> data) {

return <CandleSeries<CandleChartData, DateTime>>[

  CandleSeries<CandleChartData, DateTime>(

    dataSource: data,

    xValueMapper: (CandleChartData sales, _) => sales.timestamp,

    lowValueMapper: (CandleChartData sales, _) => sales.low,

    highValueMapper: (CandleChartData sales, _) => sales.high,

    openValueMapper: (CandleChartData sales, _) => sales.open,

    closeValueMapper: (CandleChartData sales, _) => sales.close,

  ),

];

}

}

class CandleBloc {

final _candleController = StreamController<List>.broadcast();

Stream<List> get candleStream => _candleController.stream;

CandleBloc() {

_startGeneratingData();

}

void _startGeneratingData() {

Timer.periodic(Duration(seconds: 1), (timer) {

  final candle = CandleChartData(

    open: 20 + Random().nextInt(10).toDouble(),

    close: 20 + Random().nextInt(10).toDouble(),

    high: 20 + Random().nextInt(10).toDouble(),

    low: 20 + Random().nextInt(10).toDouble(),

    timestamp: DateTime.now(),

  );

  _addCandle(candle);

});

}

void _addCandle(CandleChartData candle) {

_candleController.sink.add([candle]);

}

void dispose() {

_candleController.close();

}

}

`

Please let us know if you need any further assistance.

Regards,

Preethika Selvam. gh1916.zip

PreethikaSelvam commented 3 months ago

Hi @bilalep,

We have fixed the range exception that occurs when using the bloc to convert a stream for replacing the first data points with the new data points. This fix has been included in the latest version below. Therefore, we kindly request that you upgrade the syncfusion_flutter_charts package to the latest version below to avoid this issue.

Version: https://pub.dev/packages/syncfusion_flutter_charts/versions/26.1.39

Root cause: Missed to consider the candle line rendering when the candle series, open and close values are the same.

Regards,

Preethika Selvam.