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 761 forks source link

[syncfusion_flutter_charts] Unable to Control Two Charts with a Single Range Slider #1941

Closed KrohnMi closed 2 months ago

KrohnMi commented 3 months ago

Bug description

I want a page with two charts. These three widgets should be controlled with one range slider. I think both charts should respond to the range slider and update accordingly.

But only one chart responds to the range slider. The second chart remains unchanged.

Steps to reproduce

  1. Create a new stateful widget.
  2. Add two chart widgets and an SfRangeSelector.
  3. Test the app by moving the slider. Only one chart will be updated; the other chart will not respond.

Code sample

Code sample ```dart import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_sliders/sliders.dart'; class Data { Data({required this.x, required this.y}); final double x; final double y; } class TwoChartsWidget extends StatefulWidget { const TwoChartsWidget({super.key}); @override State createState() => _TwoChartsWidgetState(); } class _TwoChartsWidgetState extends State { final List chartData = [ Data(x: 2.0, y: 2.2), Data(x: 3.0, y: 3.4), Data(x: 4.0, y: 2.8), Data(x: 5.0, y: 1.6), Data(x: 6.0, y: 2.3), Data(x: 7.0, y: 2.5), Data(x: 8.0, y: 2.9), Data(x: 9.0, y: 3.8), Data(x: 10.0, y: 3.7), Data(x: 11.0, y: 2.2), Data(x: 12.0, y: 3.4), Data(x: 13.0, y: 2.8), Data(x: 14.0, y: 1.6), Data(x: 15.0, y: 2.3), Data(x: 16.0, y: 2.5), Data(x: 17.0, y: 2.9), Data(x: 18.0, y: 3.8), Data(x: 19.0, y: 3.7), ]; final double _min = 2.0; final double _max = 19.0; final SfRangeValues _values = const SfRangeValues(8.0, 16.0); late RangeController _rangeController; late SfCartesianChart splineChart; @override void initState() { super.initState(); _rangeController = RangeController(start: _values.start, end: _values.end); } @override void dispose() { _rangeController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { splineChart = SfCartesianChart( margin: const EdgeInsets.only( left: 10, right: 10, bottom: 20, ), primaryXAxis: NumericAxis( minimum: _min, maximum: _max, isVisible: true, rangeController: _rangeController, ), primaryYAxis: const NumericAxis( isVisible: true, ), plotAreaBorderWidth: 0, series: >[ SplineSeries( color: const Color.fromARGB(255, 126, 184, 253), dataSource: chartData, animationDuration: 0, xValueMapper: (Data sales, int index) => sales.x, yValueMapper: (Data sales, int index) => sales.y, ), ], ); return Scaffold( body: Center( child: Padding( padding: const EdgeInsets.only(left: 10, right: 10, top: 80), child: Column( children: [ SizedBox( height: 100, child: splineChart, ), SizedBox( height: 100, child: splineChart, ), SfRangeSelector( min: _min, max: _max, interval: 2, showTicks: true, showLabels: true, controller: _rangeController, child: SizedBox( height: 130, child: SfCartesianChart( margin: const EdgeInsets.all(0), primaryXAxis: NumericAxis(minimum: _min, maximum: _max, isVisible: false), primaryYAxis: const NumericAxis(isVisible: false), plotAreaBorderWidth: 0, series: >[ SplineSeries( color: const Color.fromARGB(255, 126, 184, 253), dataSource: chartData, animationDuration: 0, xValueMapper: (Data sales, int index) => sales.x, yValueMapper: (Data sales, int index) => sales.y, ), ], ), ), ), ], ), ), ), ); } } ```

Stack Traces

Stack Traces ```dart #54 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1138:13) binding.dart:1138 #55 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:443:5) binding.dart:443 #56 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1392:15) binding.dart:1392 #57 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1313:9) binding.dart:1313 #58 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:5) binding.dart:1171 #59 _invoke (dart:ui/hooks.dart:312:13) hooks.dart:312 #60 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5) platform_dispatcher.dart:419 #61 _drawFrame (dart:ui/hooks.dart:283:31) hooks.dart:283 The RangeController sending notification was: RangeController#ccae2 start: 7.997782186162624 end: 19.0 ════════════════════════════════════════════════════════════════════════════════ ```

On which target platforms have you observed this bug?

iOS, macOS

Flutter Doctor output

Doctor output ```console Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.22.2, on macOS 14.5 23F79 darwin-arm64, locale de-DE) [✗] Android toolchain - develop for Android devices ✗ Unable to locate Android SDK. Install Android Studio from: https://developer.android.com/studio/index.html On first launch it will assist you in installing the Android SDK components. (or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions). If the Android SDK has been installed to a custom location, please use `flutter config --android-sdk` to update to that location. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) [✗] 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 (not installed) [✓] VS Code (version 1.88.1) [✓] Connected device (3 available) ! Error: Browsing on the local area network for iPad Celina. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac. The device must be opted into Developer Mode to connect wirelessly. (code -27) ! Error: Browsing on the local area network for iPad. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac. The device must be opted into Developer Mode to connect wirelessly. (code -27) [!] Network resources ✗ A network error occurred while checking "https://pub.dev/": Failed host lookup: 'pub.dev' ✗ A network error occurred while checking "https://storage.googleapis.com/": Failed host lookup: 'storage.googleapis.com' ✗ A network error occurred while checking "https://maven.google.com/": Failed host lookup: 'maven.google.com' ✗ A network error occurred while checking "https://cocoapods.org/": Failed host lookup: 'cocoapods.org' ✗ A network error occurred while checking "https://github.com/": Failed host lookup: 'github.com' ! Doctor found issues in 4 categories. ```
PreethikaSelvam commented 3 months ago

Hi @KrohnMi,

We have analyzed your query and can reproduce the reported issue. This is because using the same rangeController for multiple charts leads to state updates during the build process, causing synchronization problems. To resolve this issue, we recommend using separate rangeController instances for each chart.

We have modified your code snippet to achieve multiple chart synchronization with a single slider by using two rangeController instances, rangeController1 and rangeController2. Each controller manages the visible data range for its respective chart. We've added listeners to these controllers so that when one chart's range changes, the other chart updates accordingly. Additionally, to prevent state updates during the build process, we utilize an addPostFrameCallback. This callback ensures that updates to the controllers occur after the current frame is rendered and ensures smooth synchronization between the charts and the range selector. We have shared a code snippet and a sample for your reference. You can modify the sample based on your needs.

Code snippet:

` late final double min;

late final double max;

late final RangeController rangeController1;

late final RangeController rangeController2;

@override

void initState() {

super.initState();

const int numOfElementsToShow = 10;

min = 2.0;

max = 19.0;

rangeController1 = RangeController(

  start: min,

  end: min + numOfElementsToShow,

);

rangeController2 = RangeController(

  start: min,

  end: min + numOfElementsToShow,

);

rangeController1.addListener(() {

  SchedulerBinding.instance.addPostFrameCallback((_) {

    rangeController2.start = rangeController1.start;

    rangeController2.end = rangeController1.end;

  });

});

rangeController2.addListener(() {

  SchedulerBinding.instance.addPostFrameCallback((_) {

    rangeController1.start = rangeController2.start;

    rangeController1.end = rangeController2.end;

  });

});

}

@override

void dispose() {

chartData.clear();

rangeController1.dispose();

rangeController2.dispose();

super.dispose();

}

@override

Widget build(BuildContext context) {

final SfCartesianChart chart = SfCartesianChart(

  enableAxisAnimation: false,

  zoomPanBehavior: ZoomPanBehavior(

    enablePanning: true,

    zoomMode: ZoomMode.x,

    enableMouseWheelZooming: true,

  ),

  primaryXAxis: NumericAxis(

    isVisible: false,

    interval: 1,

    minimum: min,

    maximum: max,

    rangeController: rangeController1,

  ),

  primaryYAxis: const NumericAxis(

    isVisible: true,

    anchorRangeToVisiblePoints: false,

  ),

  series: [

    SplineSeries<ChartSampleData, double>(

      dataLabelSettings: const DataLabelSettings(isVisible: true),

      dataSource: chartData,

      animationDuration: 0,

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

      yValueMapper: (ChartSampleData sales, int index) => sales.y,

    ),

  ],

);

final SfCartesianChart chart2 = SfCartesianChart(

  enableAxisAnimation: false,

  zoomPanBehavior: ZoomPanBehavior(

    enablePanning: true,

    zoomMode: ZoomMode.x,

    enableMouseWheelZooming: true,

  ),

  primaryXAxis: NumericAxis(

    isVisible: false,

    interval: 1,

    minimum: min,

    maximum: max,

    rangeController: rangeController2,

  ),

  primaryYAxis: const NumericAxis(

    isVisible: true,

    anchorRangeToVisiblePoints: false,

  ),

  series: [

    SplineSeries<ChartSampleData, double>(

      dataLabelSettings: const DataLabelSettings(isVisible: true),

      dataSource: chartData,

      animationDuration: 0,

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

      yValueMapper: (ChartSampleData sales, int index) => sales.y,

    ),

  ],

);

return Column(

  children: <Widget>[

    Expanded(

      child: Padding(

        padding: const EdgeInsets.all(10.0),

        child: chart,

      ),

    ),

    Expanded(

      child: Padding(

        padding: const EdgeInsets.all(10.0),

        child: chart2,

      ),

    ),

    Container(

      margin:

          const EdgeInsets.only(left: 50, right: 10, top: 10, bottom: 20),

      child: SfRangeSelectorTheme(

        data: SfRangeSelectorThemeData(

          thumbRadius: 0,

          overlayRadius: 0,

          inactiveTrackHeight: 0,

          activeTrackHeight: 0,

          inactiveRegionColor: Colors.black12.withOpacity(0.3),

          activeRegionColor: Colors.black.withOpacity(0.7),

        ),

        child: SfRangeSelector(

          min: min,

          max: max,

          controller: rangeController1,

          initialValues: SfRangeValues(

            rangeController1.start,

            rangeController1.end,

          ),

          interval: 1,

          dragMode: SliderDragMode.betweenThumbs,

          child: SizedBox(

            height: 5,

            child: SfCartesianChart(

              margin: const EdgeInsets.all(0),

              primaryXAxis: const NumericAxis(isVisible: false),

              primaryYAxis: const NumericAxis(isVisible: false),

              series: [

                SplineSeries<ChartSampleData, double>(

                  dataSource: chartData,

                  color: Colors.transparent,

                  xValueMapper: (ChartSampleData sales, int index) =>

                      sales.x,

                  yValueMapper: (ChartSampleData sales, int index) =>

                      sales.y,

                ),

              ],

` Please let us know if you need any further assistance.

Regards,

Preethika Selvam. gh1941.zip

KrohnMi commented 2 months ago

Ok thanks this works