How to change Marker positon change in the charts so that it shows above the charts area

Hi, 

I want to change the position of the marker in the trackball such that it shows on the top of the chart. Currently I can customize the look of the marker but I cannot change the y offset. 

Thanks in advance. 

Ekta


Attachment: IMG_1405.jpg_e3cf1187.zip

3 Replies

HK Hariharasudhan Kanagaraj Syncfusion Team October 19, 2023 04:03 PM UTC

Hi Ekta, 


We have prepared a sample and achieved the mentioned requirement by rendering the trackball tooltip and trackball marker above the chart area. This was done using the CustomPaint Widget, based on the position obtained and calculated from the onTrackballPositionChanging callback. In this sample, we used the onActualRangeChanged callback to obtain the visibleMaximum of the y-axis, the pointToPixel method to convert the point values to pixel values, and the ValueNotifier Widget to update the respective chart and CustomPainter when the position is changed.


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 ChartSeriesController _chartSeriesController;

  late List<ChartSampleData> _chartSeriesData;

  late TrackballBehavior _trackballBehavior;

  Offset? _chartOriginPosition;

  num? yMaxPixelValue;

  int pointIndex = 0;

 

  final ValueNotifier<int> count = ValueNotifier(0);

  final ValueNotifier<Offset?> _topEndChartPosition = ValueNotifier(null);

  final ValueNotifier<Offset?> _dataPointPosition = ValueNotifier(null);

  final GlobalKey<SfCartesianChartState> _chartGlobalKey = GlobalKey();

  final GlobalKey<State> _containerGlobalKey = GlobalKey();

  final EdgeInsets _chartPadding = const EdgeInsets.all(10);

 

  @override

  void initState() {

    _chartSeriesData = [

      ChartSampleData(DateTime(2001), 40),

      ChartSampleData(DateTime(2002), 80),

      ChartSampleData(DateTime(2003), 40),

      ChartSampleData(DateTime(2004), 60),

      ChartSampleData(DateTime(2005), 50),

      ChartSampleData(DateTime(2006), 90),

      ChartSampleData(DateTime(2007), 110),

      ChartSampleData(DateTime(2008), 70),

      ChartSampleData(DateTime(2009), 120),

      ChartSampleData(DateTime(2010), 100),

    ];

 

    _trackballBehavior = TrackballBehavior(

      enable: true,

      lineWidth: 0,

      activationMode: ActivationMode.singleTap,

      tooltipAlignment: ChartAlignment.near,

      tooltipSettings: const InteractiveTooltip(enable: false),

    );

    super.initState();

  }

 

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: Stack(

        children: [

          Center(

            child: SizedBox(

              width: 500,

              height: 300,

              child: SfCartesianChart(

                key: _chartGlobalKey,

                onActualRangeChanged: (rangeChangedArgs) {

                  if (rangeChangedArgs.orientation ==

                      AxisOrientation.vertical) {

                    CartesianChartPoint point =

                        CartesianChartPoint(0, rangeChangedArgs.visibleMax);

                    yMaxPixelValue =

                        _chartSeriesController.pointToPixel(point).dy;

                  }

                },

                onTrackballPositionChanging: (TrackballArgs trackballArgs) {

                  _topEndChartPosition.value = Offset(

                    trackballArgs.chartPointInfo.markerXPos!,

                    yMaxPixelValue!.toDouble() -

                        (_chartPadding.top + _chartPadding.bottom),

                  );

 

                  DateTime xValue = _chartSeriesData[

                          trackballArgs.chartPointInfo.dataPointIndex!]

                      .x;

                  num yValue = _chartSeriesData[

                          trackballArgs.chartPointInfo.dataPointIndex!]

                      .y;

 

                  CartesianChartPoint point = CartesianChartPoint(

                    xValue.millisecondsSinceEpoch.toDouble(),

                    yValue,

                  );

                  _dataPointPosition.value =

                      _chartSeriesController.pointToPixel(point);

 

                  pointIndex = trackballArgs.chartPointInfo.dataPointIndex!;

                  count.value = count.value + 1;

                },

                onChartTouchInteractionUp: (ChartTouchInteractionArgs tapArgs) {

                  _topEndChartPosition.value = null;

                  _dataPointPosition.value = null;

                  count.value = 0;

                },

                margin: _chartPadding,

                primaryXAxis: DateTimeAxis(),

                primaryYAxis: NumericAxis(),

                series: <CartesianSeries<ChartSampleData, DateTime>>[

                  LineSeries<ChartSampleData, DateTime>(

                    onRendererCreated: (controller) {

                      _chartSeriesController = controller;

                    },

                    dataSource: _chartSeriesData,

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

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

                    color: Colors.cyan,

                  ),

                ],

                trackballBehavior: _trackballBehavior,

              ),

            ),

          ),

          const SizedBox(height: 10),

          ValueListenableBuilder(

            valueListenable: _topEndChartPosition,

            builder: (BuildContext context, Offset? value, Widget? child) {

              final RenderBox chartBox = _chartGlobalKey.currentContext!

                  .findRenderObject() as RenderBox;

              // This is global position.

              _chartOriginPosition = chartBox.localToGlobal(Offset.zero);

              return CustomPaint(

                painter: CirclePainter(

                  topEndChartPosition: _topEndChartPosition.value,

                  dataPointPosition: _dataPointPosition.value,

                  chartOriginPosition: _chartOriginPosition!,

                  globalKey: _chartGlobalKey,

                  chartPadding: _chartPadding,

                  markerFillStyle: PaintingStyle.fill,

                  lineFillStyle: PaintingStyle.fill,

                ),

              );

            },

          ),

          ValueListenableBuilder(

            valueListenable: count,

            builder: (BuildContext context, int? value, Widget? child) {

              double width = 150;

              double height = 100;

              TextStyle textStyle = TextStyle(

                fontWeight: FontWeight.w700,

                color: Colors.blueGrey.shade500,

                fontSize: 14,

                letterSpacing: 1,

              );

              if (count.value > 0) {

                return Positioned(

                  left: (_topEndChartPosition.value!.dx +

                          _chartOriginPosition!.dx) -

                      (width / 2) +

                      _chartPadding.left,

                  top: (_topEndChartPosition.value!.dy +

                          _chartOriginPosition!.dy) -

                      height +

                      _chartPadding.bottom,

                  height: height,

                  width: width,

                  child: Container(

                    key: _containerGlobalKey,

                    height: height,

                    width: width,

                    color: Colors.black12,

                    child: Column(

                      mainAxisAlignment: MainAxisAlignment.center,

                      crossAxisAlignment: CrossAxisAlignment.start,

                      mainAxisSize: MainAxisSize.max,

                      children: [

                        Expanded(

                          child: Padding(

                            padding: const EdgeInsets.all(5.0),

                            child: Text(

                              'TOTAL',

                              style: textStyle,

                            ),

                          ),

                        ),

                        Expanded(

                          child: Padding(

                            padding: const EdgeInsets.all(5.0),

                            child: RichText(

                              text: TextSpan(

                                text: _chartSeriesData[pointIndex].y.toString(),

                                style: const TextStyle(

                                  fontWeight: FontWeight.bold,

                                  fontSize: 20,

                                  color: Colors.black,

                                ),

                                children: [

                                  TextSpan(

                                    text: ' steps',

                                    style: textStyle,

                                  ),

                                ],

                              ),

                            ),

                          ),

                        ),

                        Expanded(

                          child: Padding(

                            padding: const EdgeInsets.all(5.0),

                            child: Text(

                              DateFormat('MMM dd, yyyy')

                                  .format(_chartSeriesData[pointIndex].x),

                              style: textStyle,

                            ),

                          ),

                        ),

                      ],

                    ),

                  ),

                );

              } else {

                return Container();

              }

            },

          ),

        ],

      ),

    );

  }

}

 

class CirclePainter extends CustomPainter {

  CirclePainter({

    required this.topEndChartPosition,

    required this.dataPointPosition,

    required this.chartOriginPosition,

    required this.globalKey,

    required this.chartPadding,

    required this.markerFillStyle,

    required this.lineFillStyle,

    this.markerColor,

    this.markerRadius,

    this.lineColor,

    this.lineWidth,

  });

 

  final Offset? topEndChartPosition;

  final Offset? dataPointPosition;

  final Offset chartOriginPosition;

  final GlobalKey? globalKey;

  final EdgeInsets chartPadding;

  final PaintingStyle markerFillStyle;

  final PaintingStyle lineFillStyle;

  final Color? markerColor;

  final double? markerRadius;

  final Color? lineColor;

  final double? lineWidth;

 

  @override

  void paint(Canvas canvas, Size size) {

    if (globalKey != null) {

      if (topEndChartPosition != null && dataPointPosition != null) {

        canvas.drawLine(

          dataPointPosition!.translate(

            chartOriginPosition.dx + chartPadding.left,

            chartOriginPosition.dy + chartPadding.top,

          ),

          topEndChartPosition!.translate(

            chartOriginPosition.dx + chartPadding.left,

            chartOriginPosition.dy + chartPadding.top,

          ),

          Paint()

            ..strokeWidth = lineWidth ?? 3.0

            ..color = lineColor ?? Colors.grey

            ..style = lineFillStyle,

        );

        canvas.drawCircle(

          topEndChartPosition!.translate(

            chartOriginPosition.dx + chartPadding.left,

            chartOriginPosition.dy +

                chartPadding.top +

                ((markerRadius ?? 5.0) * 2),

          ),

          markerRadius ?? 5.0,

          Paint()

            ..color = markerColor ?? Colors.red

            ..style = markerFillStyle,

        );

      }

    }

  }

 

  @override

  bool shouldRepaint(CustomPainter oldDelegate) {

    return true;

  }

}

 

class ChartSampleData {

  final DateTime x;

  final num y;

 

  ChartSampleData(this.x, this.y);

}


Snapshot :


Also shared 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: 185027_4d00e2e2.zip


EK Ekta March 28, 2024 11:00 PM UTC

Hello,

This code doesnt work with version ^24.2.8. I am getting error in CartesianChartPoint(0, rangeChangedArgs.visibleMax) and If I explicitly provide the params CartesianChartPoint(x: 0, y: rangeChangedArgs.visibleMax):, I get type mismatch error at run time. Can you help me with the changes, 

I am getting the following error at run time - 

════════ Exception caught by rendering library ═════════════════════════════════ LateInitializationError: Field '_renderSize@1397452274' has not been initialized.
The relevant error-causing widget was:
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by rendering library ═════════════════════════════════ RenderBox was not laid out: RenderNumericAxis#1b438 relayoutBoundary=up3 NEEDS-PAINT 'package:flutter/src/rendering/box.dart': Failed assertion: line 1972 pos 12: 'hasSize'
The relevant error-causing widget was: The following RenderObject was being processed when the exception was fired: RenderCartesianAxes#e85c3 relayoutBoundary=up2 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE RenderObject: RenderCartesianAxes#e85c3 relayoutBoundary=up2 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by rendering library ═════════════════════════════════ 'package:syncfusion_flutter_charts/src/charts/base.dart': Failed assertion: line 803 pos 12: '_cartesianAxes!._plotAreaConstraints != null': is not true.
The relevant error-causing widget was:
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by rendering library ═════════════════════════════════ RenderBox was not laid out: RenderCartesianChartArea#a63d2 relayoutBoundary=up1 NEEDS-PAINT 'package:flutter/src/rendering/box.dart': Failed assertion: line 1972 pos 12: 'hasSize'
The relevant error-causing widget was:


LP Lokesh Palani Syncfusion Team April 2, 2024 01:19 PM UTC

Hi Ekta,


We have validated the reported issue and modified the code snippet with 25.1.38. We have shared the code snippet and sample below for your reference. Please let us know if you have any further requirements.


Code Snippet:


      onActualRangeChanged: (rangeChangedArgs) {

                  if (rangeChangedArgs.orientation ==

                      AxisOrientation.horizontal) {

                    _xMin = DateTime.fromMillisecondsSinceEpoch(

                        rangeChangedArgs.visibleMin);

                  }

                  if (rangeChangedArgs.orientation ==

                      AxisOrientation.vertical) {

                    CartesianChartPoint<DateTime> point =

                        CartesianChartPoint<DateTime>(

                      x: _xMin,

                      y: rangeChangedArgs.visibleMax,

                    );

                    yMaxPixelValue =

                        _chartSeriesController.pointToPixel(point).dy;

                  }

                },

 

       onTrackballPositionChanging: (TrackballArgs trackballArgs) {

                  _topEndChartPosition.value = Offset(

                    trackballArgs.chartPointInfo.markerXPos!,

                    yMaxPixelValue!.toDouble() -

                        (_chartPadding.top + _chartPadding.bottom),

                  );

                  DateTime xValue = _chartSeriesData[

                          trackballArgs.chartPointInfo.dataPointIndex!]

                      .x;

                  num yValue = _chartSeriesData[

                          trackballArgs.chartPointInfo.dataPointIndex!]

                      .y;

                  CartesianChartPoint<DateTime> point =

                      CartesianChartPoint<DateTime>(

                    x: xValue,

                    y: yValue,

                  );

                  _dataPointPosition.value =

                      _chartSeriesController.pointToPixel(point);

                  pointIndex = trackballArgs.chartPointInfo.dataPointIndex!;

                  count.value = count.value + 1;

                },

 

  canvas.drawLine(

          dataPointPosition!.translate(

            chartOriginPosition.dx - chartPadding.top + 50,

            chartOriginPosition.dy + chartPadding.bottom,

          ),

          topEndChartPosition!.translate(

            chartOriginPosition.dx - chartPadding.top + 50,

            chartOriginPosition.dy + chartPadding.bottom,

          ),

          linePaint,

        );

 



Regards,

Lokesh P.


Attachment: forum_185027__e0a3d95c.zip

Loader.
Up arrow icon