Skip to content

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

@Deishelon

Description

@Deishelon

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

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<RangeTimeData, String>(
          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<RangeTimeData, String>(
          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

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<RangeTimeData, String>(
          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<RangeTimeData, String>(
          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

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<RangeTimeData, String>(
          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<RangeTimeData, String>(
          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:

  • dataLabelMapper to be able to format 'low' and 'high' data points individually
    -- maybe either via the 'index' (i.e 0 is low, and 1 is high)
    -- or can return a struct with 2 strings
    -- or have 2 data mappers lowDataLabelMapper & highDataLabelMapper
  • When 2 series provide data mapper - all series on UI should show the formatted values
  • The values in the popup should be formatted too

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
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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    chartsCharts componentsolvedSolved the query using existing solutions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions