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.45k stars 679 forks source link

Data Label Mapper for RangeColumnSeries does not format separately low&high #1824

Closed Deishelon closed 1 day ago

Deishelon commented 1 month ago

Bug description

This is what I'm trying to achieve conceptually, so it's clear why this is an issue.

Imagine the following graph where on the X axis is time, and you show to the user the 'timeline' view of a given state (i.e connected/disconnected in my case) idea_ui

Now, when using SfCartesianChart with RangeColumnSeries I can provide 'low' and 'high' data points (via lowValueMapper and highValueMapper) which would indicate the start end the end of the state event.

However, when displayed back to the user the start&end would be printed as raw numbers (i.e milliseconds), which is expected: raw_numbers

However, now if one provides a dataLabelMapper one would expect that given RangeColumnSeries has two values that dataLabelMapper either would be called twice (one for low format, one for high format), or once but return 2 formatted strings (for low & high) - but this does not happen. When providing dataLabelMapper it's called once, and you can only return one thing. which means low&high data points are formatted identically.

See more in the code sample.

Steps to reproduce

  1. Implement RangeColumnSeries where primaryYAxis is DateTimeAxis
  2. Try to format the 'raw milliseconds' to sensible user output
  3. Low&high data points do not have separate way for dataLabelMapper to format them
  4. Even with dataLabelMapper provided the popup values are still 'raw'

(See code samples for full info)

Code sample

No formatting (base code) With this we can get this chart to show up: ![raw_numbers](https://github.com/syncfusion/flutter-widgets/assets/11800090/5edd9035-6c4a-4295-b1f9-af63a0056dd5) ```dart class RangeTimeData { final String name; final num low; final num high; RangeTimeData(this.name, this.low, this.high); } Widget timelineStateChart2(BuildContext context) { return SfCartesianChart( enableSideBySideSeriesPlacement: false, legend: const Legend(isVisible: true), primaryXAxis: CategoryAxis(majorGridLines: const MajorGridLines(width: 0)), primaryYAxis: DateTimeAxis(axisLine: const AxisLine(width: 0)), series: [ RangeColumnSeries( dataSource: [ RangeTimeData( "Wifi Status", DateTime(2023, DateTime.april, 21, 20, 30).millisecondsSinceEpoch, DateTime(2023, DateTime.april, 21, 25, 30).millisecondsSinceEpoch, ), ], xValueMapper: (RangeTimeData sales, _) => sales.name, lowValueMapper: (RangeTimeData sales, _) => sales.low, highValueMapper: (RangeTimeData sales, _) => sales.high, name: 'Connected', dataLabelSettings: const DataLabelSettings(isVisible: true, labelAlignment: ChartDataLabelAlignment.top), ), RangeColumnSeries( dataSource: [ RangeTimeData( "Wifi Status", DateTime(2023, DateTime.april, 21, 30, 30).millisecondsSinceEpoch, DateTime(2023, DateTime.april, 21, 40, 30).millisecondsSinceEpoch, ) ], xValueMapper: (RangeTimeData data, _) => data.name, lowValueMapper: (RangeTimeData data, _) => data.low, highValueMapper: (RangeTimeData data, _) => data.high, name: 'Disconnected', dataLabelSettings: const DataLabelSettings(isVisible: true, labelAlignment: ChartDataLabelAlignment.top), ) ], tooltipBehavior: TooltipBehavior(enable: true), isTransposed: true, ); } ```
Adding formatter Now, I've added a formatter (`dataLabelMapper: (RangeTimeData data, index) => "${data.low}-${data.high}-$index",`), just to demonstrate the point that both low and high contain the same value (even the index is `0` for both) ![both_formatted_same](https://github.com/syncfusion/flutter-widgets/assets/11800090/da90c728-c3ad-4f02-ba13-f150ef6f62dd) ```dart return SfCartesianChart( enableSideBySideSeriesPlacement: false, legend: const Legend(isVisible: true), primaryXAxis: CategoryAxis(majorGridLines: const MajorGridLines(width: 0)), primaryYAxis: DateTimeAxis(axisLine: const AxisLine(width: 0)), series: [ RangeColumnSeries( dataSource: [ RangeTimeData( "Wifi Status", DateTime(2023, DateTime.april, 21, 20, 30).millisecondsSinceEpoch, DateTime(2023, DateTime.april, 21, 25, 30).millisecondsSinceEpoch, ), ], xValueMapper: (RangeTimeData sales, _) => sales.name, lowValueMapper: (RangeTimeData sales, _) => sales.low, highValueMapper: (RangeTimeData sales, _) => sales.high, name: 'Connected', //dataLabelMapper: (RangeTimeData data, index) => "${data.low}-${data.high}-$index", dataLabelSettings: const DataLabelSettings(isVisible: true, labelAlignment: ChartDataLabelAlignment.top), ), RangeColumnSeries( dataSource: [ RangeTimeData( "Wifi Status", DateTime(2023, DateTime.april, 21, 30, 30).millisecondsSinceEpoch, DateTime(2023, DateTime.april, 21, 40, 30).millisecondsSinceEpoch, ) ], xValueMapper: (RangeTimeData data, _) => data.name, lowValueMapper: (RangeTimeData data, _) => data.low, highValueMapper: (RangeTimeData data, _) => data.high, name: 'Disconnected', dataLabelMapper: (RangeTimeData data, index) => "${data.low}-${data.high}-$index", dataLabelSettings: const DataLabelSettings(isVisible: true, labelAlignment: ChartDataLabelAlignment.top), ) ], tooltipBehavior: TooltipBehavior(enable: true), isTransposed: true, ); ```
Multiple formatters Same code as above, but now both `RangeColumnSeries` have formatter, but it seems like it formats only the first one (please note how the red box is missing any data format labels, but blue box has; but code has formatters for both): ![formatted_only_one](https://github.com/syncfusion/flutter-widgets/assets/11800090/a915a673-3531-493f-89a2-6ac90de25016) ```dart return SfCartesianChart( enableSideBySideSeriesPlacement: false, legend: const Legend(isVisible: true), primaryXAxis: CategoryAxis(majorGridLines: const MajorGridLines(width: 0)), primaryYAxis: DateTimeAxis(axisLine: const AxisLine(width: 0)), series: [ RangeColumnSeries( dataSource: [ RangeTimeData( "Wifi Status", DateTime(2023, DateTime.april, 21, 20, 30).millisecondsSinceEpoch, DateTime(2023, DateTime.april, 21, 25, 30).millisecondsSinceEpoch, ), ], xValueMapper: (RangeTimeData sales, _) => sales.name, lowValueMapper: (RangeTimeData sales, _) => sales.low, highValueMapper: (RangeTimeData sales, _) => sales.high, name: 'Connected', dataLabelMapper: (RangeTimeData data, index) => "${data.low}-${data.high}-$index", dataLabelSettings: const DataLabelSettings(isVisible: true, labelAlignment: ChartDataLabelAlignment.top), ), RangeColumnSeries( dataSource: [ RangeTimeData( "Wifi Status", DateTime(2023, DateTime.april, 21, 30, 30).millisecondsSinceEpoch, DateTime(2023, DateTime.april, 21, 40, 30).millisecondsSinceEpoch, ) ], xValueMapper: (RangeTimeData data, _) => data.name, lowValueMapper: (RangeTimeData data, _) => data.low, highValueMapper: (RangeTimeData data, _) => data.high, name: 'Disconnected', dataLabelMapper: (RangeTimeData data, index) => "${data.low}-${data.high}-$index", dataLabelSettings: const DataLabelSettings(isVisible: true, labelAlignment: ChartDataLabelAlignment.top), ) ], tooltipBehavior: TooltipBehavior(enable: true), isTransposed: true, ); ```

Also, please note how the values in the popup is not formatted at all, despite dataLabelMapper provided.: popup_not_formatted

Expected behaviour:

Screenshots or Video

Demonstrated above with the code snippets.

Stack Traces

Not a crash - not relevant.

On which target platforms have you observed this bug?

Web, Linux

Flutter Doctor output

Doctor output ```console flutter doctor -v [✓] Flutter (Channel stable, 3.19.2, on Fedora Linux 39 (KDE Plasma) 6.8.6-200.fc39.x86_64, locale en_NZ.UTF-8) • Flutter version 3.19.2 on channel stable at /opt/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 7482962148 (8 weeks ago), 2024-02-27 16:51:22 -0500 • Engine revision 04817c99c9 • Dart version 3.3.0 • DevTools version 2.31.1 [!] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /home/deishelon/Android/Sdk ✗ cmdline-tools component is missing Run `path/to/sdkmanager --install "cmdline-tools;latest"` See https://developer.android.com/studio/command-line for more details. ✗ Android license status unknown. Run `flutter doctor --android-licenses` to accept the SDK licenses. See https://flutter.dev/docs/get-started/install/linux#android-setup for more details. [✓] Chrome - develop for the web • Chrome at google-chrome [✗] Linux toolchain - develop for Linux desktop • clang version 17.0.6 (Fedora 17.0.6-2.fc39) • cmake version 3.27.7 • ninja version 1.11.1 • pkg-config version 1.9.5 ✗ GTK 3.0 development libraries are required for Linux development. They are likely available from your distribution (e.g.: apt install libgtk-3-dev) [✓] Android Studio (version 2023.2) • Android Studio at /home/deishelon/.local/share/JetBrains/Toolbox/apps/android-studio • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) [✓] IntelliJ IDEA Ultimate Edition (version 2024.1) • IntelliJ at /home/deishelon/.local/share/JetBrains/Toolbox/apps/intellij-idea-ultimate • Flutter plugin version 79.0.3 • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] Connected device (2 available) • Linux (desktop) • linux • linux-x64 • Fedora Linux 39 (KDE Plasma) 6.8.6-200.fc39.x86_64 • Chrome (web) • chrome • web-javascript • Google Chrome 124.0.6367.60 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 2 categories. ```
LokeshPalani commented 4 weeks ago

Hi @Deishelon,

Query 1: How to format 'low' and 'high' data points individually for the dataLabels.

You can achieve your requirement by using the onDataLabelRender callback in the SfCartesianChart. By using this, you can get ‘high’ and ‘low’ data points. When the onDataLabelRender callback is triggered, the high value will be called first, followed by the low value. You can format the dataLabel text separately by setting the bool property _isHigh to true to display high dataPoints and changing the _isHigh to false to display low dataPoints. We have provided a code snippet, screenshot, user guide documentation, and sample for your reference.

Code Snippet:

bool _isHigh = true;

 body: SfCartesianChart(
         isTransposed: true,
           onDataLabelRender: (DataLabelRenderArgs dataLabelArgs) {
            if (_isHigh) {
              // It represents for high dataPoints.
              final int millisecondsSinceEpoch = int.parse(dataLabelArgs.text!);
              final dateTime =
                  DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch);
              final String formattedDate = DateFormat.jm().format(dateTime);
              dataLabelArgs.text = formattedDate;
            } else {
              // It represents for low dataPoints.
              final int millisecondsSinceEpoch = int.parse(dataLabelArgs.text!);
              final dateTime =
               DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch);
              final String formattedDate = DateFormat.jm().format(dateTime);
              dataLabelArgs.text = formattedDate;
            }
            // Changing _isHigh boolean value to true when it is false and false when it is true.
            _isHigh = !_isHigh;
          },
          },

UG Link,

https://help.syncfusion.com/flutter/cartesian-charts/callbacks#ondatalabelrender

Screeshot:

image

Query 2: How to format the text in the tooltip like in the dataLabels.

You can achieve your requirement by using the builder property in the TooltipBehavior. By using this, you can format the tooltip text, like in the dataLabels based on the seriesIndex. We have shared code snippet, screenshot, user guide documentation, and sample for your reference.

UG Link,

https://help.syncfusion.com/flutter/cartesian-charts/tooltip#tooltip-template

Code Snippet:

 tooltipBehavior: TooltipBehavior(
            enable: true,
            builder: (data, point, series, pointIndex, seriesIndex) {
              String tooltipHeader = series.name.toString();
              const textStyle = TextStyle(color: Colors.white);
              const padding = EdgeInsets.only(bottom: 5);
              return ClipRRect(
                borderRadius: BorderRadius.circular(15),
                child: Container(
                  color: Colors.black26,
                  padding: const EdgeInsets.all(5),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        tooltipHeader,
                        textAlign: TextAlign.center,
                        style: textStyle,
                      ),
                      const Padding(padding: padding),
                      const SizedBox(
                        height: 2,
                        width: 80,
                        child: ColoredBox(color: Colors.white),
                      ),
                      Text(' ${rangeData1[pointIndex].name}', style: textStyle),
                      Text(
                          'High : ${DateFormat.jm().format(
                           DateTime.fromMillisecondsSinceEpoch(seriesIndex == 0
                                ? rangeData1[pointIndex].high
                                : rangeData2[pointIndex].high),
                          )}',
                          style: textStyle),
                      Text(
                          'Low : ${DateFormat.jm().format(
                            DateTime.fromMillisecondsSinceEpoch(seriesIndex == 0
                                ? rangeData1[pointIndex].low
                                : rangeData2[pointIndex].low),
                          )}',
                          style: textStyle),
                    ],
                  ),
                ),
              );
            },
          ),

Screenshot:

image

Note: We have prepared the workaround sample based on the two series only.

Regards, Lokesh P.

github_1824.zip