We use cookies to give you the best experience on our website. If you continue to browse, then you agree to our privacy policy and cookie policy. Image for the cookie policy date

Trackball/tooltip dissapears when charts data set is updated.

Hello!


I have a chart that gets updated every other second or so from a web socket, so this is essentially a real time chart.I have created TrackballBehavior. I'm utilizing the TrackballBehavior builder method to customize the tooltip UI. 

Here's the problem: 

When i interact with the chart, the tooltip appears, but whenever i update the state (update the data source), the tooltip disappears. Anyway to get around this?

Here is my code:


@override

Widget build(BuildContext context) {

  final xAxisMinimum = current.lastOrNull?.timestamp.subtract(state.currentInterval.duration);

  final maxConsumption = current.map((e) => e.consumption).fold<double>(0, max).roundUpToNearest(1);

  const minConsumption = 0.0;

  final chart = SfCartesianChart(

    plotAreaBorderWidth: 0,

    margin: const EdgeInsets.only(right: 10.0, left: 8.0),

    trackballBehavior: TrackballBehavior(

      activationMode: ActivationMode.singleTap,

      tooltipDisplayMode: TrackballDisplayMode.groupAllPoints,

      tooltipAlignment: ChartAlignment.near,

      shouldAlwaysShow: true,

      enable: true,

      builder: (context, details) {

        final stat = _getHanDeviceDataFromLabel(details.groupingModeInfo?.points.firstOrNull?.dataLabelMapper);

        if (stat != null) {

          return ChartToolTip(

            key: const Key("date_time_tool_tip_key"),

            title: state.currentInterval.tooltipDateFormat(stat.timestamp),

            rows: [

              ChartToolTipRowConfig(

                title: stat.isMocked ? Strings.commonNoData : stat.consumption.kwFormatted(),

                dotColor: colors.secondaryColor,

              ),

            ],

          );

        }

        return const SizedBox();

      },

    ),

    zoomPanBehavior: ZoomPanBehavior(

      zoomMode: ZoomMode.x,

      enablePanning: true,

      enablePinching: true,

      enableDoubleTapZooming: true,

    ),

    primaryXAxis: DateTimeAxisExtension.defaultDateTimeAxis().copyWith(

      axisLine: AxisLine(color: colors.black180),

      majorTickLines: MajorTickLines(color: colors.black180),

      dateFormat: state.currentInterval.chartDateFormat,

      intervalType: state.currentInterval.chartInterval,

      minimum: xAxisMinimum,

      visibleMinimum: xAxisMinimum,

      maximum: current.lastOrNull?.timestamp,

      visibleMaximum: current.lastOrNull?.timestamp,

      desiredIntervals: 4,

      labelStyle: current.isEmpty ? const TextStyle(color: Colors.transparent) : null,

    ),

    primaryYAxis: NumericAxisExtension.defaultNumericAxis().copyWith(

      axisLine: const AxisLine(width: 0),

      majorTickLines: const MajorTickLines(size: 0),

      maximum: maxConsumption,

      minimum: minConsumption,

      opposedPosition: true,

      desiredIntervals: 4,

      labelStyle: current.isEmpty ? const TextStyle(color: Colors.transparent) : null,

    ),

    series: <ChartSeries<BaseHanDeviceData, DateTime>>[

      AreaSeries<BaseHanDeviceData, DateTime>(

        dataSource: current,

        xValueMapper: (BaseHanDeviceData data, _) => data.timestamp,

        yValueMapper: (BaseHanDeviceData data, _) => data.consumption,

        animationDuration: 0,

        borderColor: colors.secondaryColor80,

        onCreateShader: (details) => _gradientColors.createShader(details.rect),

        dataLabelMapper: (_, index) => index.toString(),

      ),

    ],

  );

  return Column(

    children: [

      Row(

        mainAxisAlignment: MainAxisAlignment.end,

        children: [

          Text(

            "kW",

            style: fonts.mini.copyWith(

              color: colors.black100,

            ),

          ),

        ],

      ).horizontalPadding(spacing16),

      const SizedBox(height: spacing24),

      current.isEmpty ? Stack(

        alignment: Alignment.center,

        children: [

          chart,

          Text(Strings.commonNoDataForPeriod)

        ],

      ) : chart,

    ],

  );

}


BaseHanDeviceData? _getHanDeviceDataFromLabel(String? indexString) {

  final parsedIndex = int.tryParse(indexString ?? "");

  if (parsedIndex != null) {

    if (current.containsIndex(parsedIndex)) {

      return current[parsedIndex];

    }

  }

  return null;

}



9 Replies 1 reply marked as answer

MP Martin Pyk Pyk Pettersen August 11, 2023 10:24 AM UTC

Update:

I managed to prevent the tooltip/trackball from disappearing by updating the data source using 

ChartSeriesController. But now i ran into another problem, the tooltip/trackball does not move with the x axis as the data source gets updated.



HK Hariharasudhan Kanagaraj Syncfusion Team August 14, 2023 01:30 PM UTC

Hi Martin,


We have analyzed your query and prepared a sample with updateDataSource method and trackball behavior. Here, we are rendering the LineSeries in the real time update using the updateDataSource method with the help of ChartSeriesController and to make the trackball tooltip always visible at the last previous point, passed the specified data point index to the showByIndex method as shown in the code snippet below.


class _MyWidgetState extends State<MyWidget> {

  _MyWidgetState() {

    timer = Timer.periodic(const Duration(seconds: 1), _updateDataSource);

  }

 

  Timer? timer;

  List<ChartData>? chartData;

  ChartSeriesController? _chartSeriesController;

  int count = 19;

 

  late SfCartesianChart chart;

  late TrackballBehavior _trackballBehavior;

 

  @override

  void initState() {

    chartData = <ChartData>[

      ChartData(0, 42),

      ChartData(1, 47),

      ChartData(2, 33),

      ChartData(3, 49),

      ChartData(4, 54),

      ChartData(5, 41),

      ChartData(6, 58),

      ChartData(7, 51),

      ChartData(8, 98),

      ChartData(9, 41),

      ChartData(10, 53),

      ChartData(11, 72),

      ChartData(12, 86),

      ChartData(13, 52),

      ChartData(14, 94),

      ChartData(15, 92),

      ChartData(16, 86),

      ChartData(17, 72),

      ChartData(18, 94),

    ];

    _trackballBehavior = TrackballBehavior(

      enable: true,

      shouldAlwaysShow: true,

    );

    super.initState();

  }

 

  @override

  Widget build(BuildContext context) {

    chart = SfCartesianChart(

      primaryXAxis: NumericAxis(),

      primaryYAxis: NumericAxis(),

      trackballBehavior: _trackballBehavior,

      series: <LineSeries<ChartData, int>>[

        LineSeries<ChartData, int>(

          onRendererCreated: (ChartSeriesController controller) {

            _chartSeriesController = controller;

          },

          dataSource: chartData!,

          color: const Color.fromRGBO(192, 108, 132, 1),

          xValueMapper: (ChartData sales, _) => sales.country,

          yValueMapper: (ChartData sales, _) => sales.sales,

          animationDuration: 0,

        ),

      ],

    );

 

    return MaterialApp(

      home: Scaffold(

        body: Center(

          child: chart,

        ),

      ),

    );

  }

 

  void _updateDataSource(Timer timer) {

    chartData!.add(ChartData(count, getRandomInt(10, 100)));

    if (chartData!.length == 20) {

      chartData!.removeAt(0);

      _chartSeriesController?.updateDataSource(

        addedDataIndexes: <int>[chartData!.length - 1],

        removedDataIndexes: <int>[0],

      );

      _trackballBehavior.showByIndex(chartData!.length - 1);

    } else {

      _chartSeriesController?.updateDataSource(

        addedDataIndexes: <int>[chartData!.length - 1],

      );

      _trackballBehavior.showByIndex(chartData!.length - 1);

    }

 

    count = count + 1;

  }

 

  int getRandomInt(int min, int max) {

    final random = math.Random();

    return min + random.nextInt(max - min);

  }

 

  @override

  void dispose() {

    timer?.cancel();

    chartData!.clear();

    _chartSeriesController = null;

    super.dispose();

  }

}


You can modify the attached sample below according to your needs, and if you have further queries, please get back to us.


Regards,
Hari Hara Sudhan. K.


Attachment: _183988_98dbe09.zip


MP Martin Pyk Pyk Pettersen August 15, 2023 01:52 PM UTC

Thats close but not quite. I want the tooltip to be stuck on the point selected as the data source updates. In your example it always displays the value of the data in the last data source index.



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

Hi Martin,


Unfortunately, we are unable to understand your requirement clearly and we need some clarifications regarding the points mentioned below:


  1. Do you need the trackball tooltip to move along with the data points as they get updated? Or do you want the trackball tooltip to remain fixed on the selected data points when clicked, as shown in the recording below?
  2. We also recommend that you share any recordings or rough sketches that demonstrate the expected output and provide a detailed explanation of the requirements.


With that additional information, it will be very useful for us to provide the best possible solution from our end.


Regards,

Hari Hara Sudhan. K.



MP Martin Pyk Pyk Pettersen August 24, 2023 06:08 AM UTC

need the trackball tooltip to move along with the data points as they get updated. See attachment for desired behavior. There you can see the tooltip move to the left with the data point as new data points are added.


Attachment: screenshots_d52fc31e.zip


HK Hariharasudhan Kanagaraj Syncfusion Team August 24, 2023 11:56 AM UTC

Hi Martin,


We have prepared a sample to add and remove the segment of the Line Series in a programmatic update using the updateDataSource method. We have achieved the mentioned requirement using the TrackballBehavior.showByIndex method and onTrackballPositionChanging callback. Here, the current index of the trackball can be obtained using the onTrackballPositionChanging callback and when we update the Line Series, we have passed the current index of the trackball to the TrackballBehavior.showByIndex method as shown in the code snippet below.


class _MyAppState extends State<MyApp> {

  List<ChartData>? chartData;

  ChartSeriesController? _chartSeriesController;

  int count = 19;

  int? index;

  Offset? offset;

 

  late TrackballBehavior _trackballBehavior;

 

  @override

  void initState() {

    chartData = <ChartData>[

      ChartData(0, 42),

      ChartData(1, 47),

      ChartData(2, 33),

      ChartData(3, 49),

      ChartData(4, 54),

      ChartData(5, 41),

      ChartData(6, 58),

      ChartData(7, 51),

      ChartData(8, 98),

      ChartData(9, 41),

      ChartData(10, 53),

      ChartData(11, 72),

      ChartData(12, 86),

      ChartData(13, 52),

      ChartData(14, 94),

      ChartData(15, 92),

      ChartData(16, 86),

      ChartData(17, 72),

      ChartData(18, 94),

    ];

    _trackballBehavior = TrackballBehavior(

      enable: true,

      shouldAlwaysShow: true,

      activationMode: ActivationMode.singleTap,

    );

    super.initState();

  }

 

  void _addDataSource() {

    chartData!.add(

      ChartData(

        count,

        getRandomInt(10, 100),

      ),

    );

 

    _chartSeriesController?.updateDataSource(

      addedDataIndex: count,

    );

    if (index != null) {

      _trackballBehavior.showByIndex(index!);

    }

    count = count + 1;

  }

 

  void _removeDataSource() {

    chartData!.removeAt(count - 1);

    _chartSeriesController?.updateDataSource(

      removedDataIndex: chartData!.length,

    );

    if (index != null) {

      _trackballBehavior.showByIndex(index!);

    }

    count = count - 1;

  }

 

  int getRandomInt(int min, int max) {

    final random = math.Random();

    return min + random.nextInt(max - min);

  }

 

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      debugShowCheckedModeBanner: false,

      home: Scaffold(

        body: Center(

          child: Stack(

            children: [

              SfCartesianChart(

                onTrackballPositionChanging: (TrackballArgs trackballArgs) {

                  index = trackballArgs.chartPointInfo.dataPointIndex;

                },

                primaryXAxis: NumericAxis(),

                primaryYAxis: NumericAxis(),

                series: <LineSeries<ChartData, int>>[

                  LineSeries<ChartData, int>(

                    onRendererCreated: (ChartSeriesController controller) {

                      _chartSeriesController = controller;

                    },

                    dataSource: chartData!,

                    color: Colors.green,

                    xValueMapper: (ChartData sales, _) => sales.country,

                    yValueMapper: (ChartData sales, _) => sales.sales,

                    animationDuration: 0,

                  ),

                ],

                trackballBehavior: _trackballBehavior,

              ),

              Align(

                alignment: Alignment.bottomRight,

                child: Padding(

                  padding: const EdgeInsets.all(10.0),

                  child: Row(

                    mainAxisAlignment: MainAxisAlignment.end,

                    children: [

                      FloatingActionButton.small(

                        onPressed: _addDataSource,

                        child: const Icon(Icons.add),

                      ),

                      const SizedBox(width: 10),

                      FloatingActionButton.small(

                        onPressed: _removeDataSource,

                        child: const Icon(Icons.remove),

                      ),

                    ],

                  ),

                ),

              ),

            ],

          ),

        ),

      ),

    );

  }

 

  @override

  void dispose() {

    chartData!.clear();

    _chartSeriesController = null;

    super.dispose();

  }

}


Also attached the sample below for your reference and you can modify the sample according to your needs. If you have further queries, please get back to us.


Regards,

Hari Hara Sudhan. K.


Attachment: _183988_805a852f.zip

Marked as answer

HK Hariharasudhan Kanagaraj Syncfusion Team August 24, 2023 12:00 PM UTC

Hi Martin,


Also attached the recording below regarding the mentioned requirement for your reference.


Regards,
Hari Hara Sudhan. K.


Attachment: 183988_d781029.zip


MP Martin Pyk Pyk Pettersen August 24, 2023 12:03 PM UTC

Thanks! This resolves my problem



HK Hariharasudhan Kanagaraj Syncfusion Team August 25, 2023 12:54 PM UTC

Hi Martin,


Most Welcome. Kindly get back to us if you have further queries. We are always happy to assist you.


Regards,

Hari Hara Sudhan. K.


Loader.
Live Chat Icon For mobile
Up arrow icon