Skip to content

SfCartesianChart - Inconsistent Data Label Position in Bar Chart #2313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alunparanggi opened this issue Mar 22, 2025 · 2 comments
Closed

SfCartesianChart - Inconsistent Data Label Position in Bar Chart #2313

alunparanggi opened this issue Mar 22, 2025 · 2 comments
Labels
charts Charts component solved Solved the query using existing solutions waiting for customer response Cannot make further progress until the customer responds.

Comments

@alunparanggi
Copy link

alunparanggi commented Mar 22, 2025

Bug description

In some cases, the data labels in the bar chart either disappear completely or shift slightly, while in other cases, they appear correctly.

I'm not sure what is causing this issue—perhaps the Transform widget or some other factor. I have tried avoiding a builder and instead using the angle: 90 parameter in DataLabelSettings, but the issue still occurs.

Steps to reproduce

There are no specific steps to reproduce this behavior.

Code sample

Code sample
@override
  Widget build(BuildContext context) {
    return SizedBox(
      child: SfCartesianChart(
          tooltipBehavior: TooltipBehavior(
              enable: true,
              color: Colors.blue,
              builder: (dynamic data, dynamic point, dynamic series,
                  int pointIndex, int seriesIndex) {
                return _tooltip(data: data, isIntake: seriesIndex == 0);
              }),
          primaryXAxis: CategoryAxis(),
          series: <CartesianSeries<CalorieHistory, String>>[
            // calorie intake
            ColumnSeries<CalorieHistory, String>(
              dataSource: caloriesData7Days,
              color: barColor,
              xValueMapper: (CalorieHistory data, _) =>
                  DateHelper.getDayOfWeek(data.date).substring(0, 3),
              yValueMapper: (CalorieHistory data, _) => data.caloriesIntake,
              dataLabelSettings: DataLabelSettings(
                  isVisible: true,
                  offset: Offset(0, -42),
                  labelIntersectAction: LabelIntersectAction.shift,
                  builder: (dynamic data, dynamic point, dynamic series,
                      int pointIndex, int seriesIndex) {
                    return Transform.rotate(
                      angle: 90 * 3.14159265359 / 180, // Rotasi 90 derajat
                      child: Text(
                        '${data.caloriesIntake.toStringAsFixed(2)} kcal',
                        style: AppStyle.regular8r.copyWith(color: Colors.white),
                      ),
                    );
                  }),
            ),
            // calorie burnt
            ColumnSeries<CalorieHistory, String>(
              dataSource: caloriesData7Days,
              color: Colors.deepOrange,
              xValueMapper: (CalorieHistory data, _) =>
                  DateHelper.getDayOfWeek(data.date).substring(0, 3),
              yValueMapper: (CalorieHistory data, _) => data.caloriesBurnt,
              dataLabelSettings: DataLabelSettings(
                  isVisible: true,
                  offset: Offset(0, -42),
                  labelIntersectAction: LabelIntersectAction.none,
                  builder: (dynamic data, dynamic point, dynamic series,
                      int pointIndex, int seriesIndex) {
                    return Transform.rotate(
                      angle: 90 * 3.14159265359 / 180,
                      child: Text(
                        '${data.caloriesBurnt.toStringAsFixed(2)} kcal',
                        style: AppStyle.regular8r.copyWith(color: Colors.black),
                      ),
                    );
                  }),
            ),
          ]),
    );
  }

// model & data
class CalorieHistory {
  DateTime date;
  double caloriesIntake;
  double caloriesBurnt;
  String notes;
  String burntNotes;

  CalorieHistory({
    required this.date,
    this.caloriesBurnt = 0,
    this.caloriesIntake = 0,
    this.notes = '',
    this.burntNotes = '',
  });
}

List<CalorieHistory> caloriesData7Days = [
  CalorieHistory(
    date: DateTime.parse("2025-03-17 00:00:00"),
    caloriesIntake: 1100,
    caloriesBurnt: 1242.1,
    notes: "",
    burntNotes: "",
  ),
  CalorieHistory(
    date: DateTime.parse("2025-03-18 00:00:00"),
    caloriesIntake: 553,
    caloriesBurnt: 1132.6083,
    notes: "",
    burntNotes: "",
  ),
  CalorieHistory(
    date: DateTime.parse("2025-03-19 00:00:00"),
    caloriesIntake: 256,
    caloriesBurnt: 804.37,
    notes: "",
    burntNotes: "",
  ),
  CalorieHistory(
    date: DateTime.parse("2025-03-20 00:00:00"),
    caloriesIntake: 767,
    caloriesBurnt: 321.85,
    notes: "",
    burntNotes: "",
  ),
  CalorieHistory(
    date: DateTime.parse("2025-03-21 00:00:00"),
    caloriesIntake: 0,
    caloriesBurnt: 0,
    notes: "",
    burntNotes: "",
  ),
  CalorieHistory(
    date: DateTime.parse("2025-03-22 00:00:00"),
    caloriesIntake: 0,
    caloriesBurnt: 1827.47,
    notes: "",
    burntNotes: "",
  ),
];

Screenshots or Video

Screenshots / Video demonstration

labelIntersectAction: LabelIntersectAction.none
Image

when labelIntersectAction: LabelIntersectAction.shift some labels are completely gone
Image

Stack Traces

Stack Traces
no stack trace is available

On which target platforms have you observed this bug?

iOS

Flutter Doctor output

Doctor output
[!] Flutter (Channel stable, 3.29.2, on macOS 13.7.3 22H417 darwin-x64, locale en-US) [3.6s]
    • Flutter version 3.29.2 on channel stable at /Users/alunparanggi/fvm/versions/3.29.2
    ! Warning: `dart` on your path resolves to /usr/local/Cellar/dart/2.17.3/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/alunparanggi/fvm/versions/3.29.2. Consider adding /Users/alunparanggi/fvm/versions/3.29.2/bin to the front of your path.
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision c236373904 (8 days ago), 2025-03-13 16:17:06 -0400
    • Engine revision 18b71d647a
    • Dart version 3.7.2
    • DevTools version 2.42.3
    • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades.

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) [8.5s]
    • Android SDK at /Users/alunparanggi/Library/Android/sdk/
    • Platform android-35, build-tools 34.0.0
    • ANDROID_HOME = /Users/alunparanggi/Library/Android/sdk/
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)
    • All Android licenses accepted.

[!] Xcode - develop for iOS and macOS (Xcode 15.1) [6.6s]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15C65
    ! CocoaPods 1.15.2 out of date (1.16.2 is recommended).
        CocoaPods is a package manager for iOS or macOS platform code.
        Without CocoaPods, plugins will not work on iOS or macOS.
        For more info, see https://flutter.dev/to/platform-plugins
      To update CocoaPods, see https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods

[✓] Chrome - develop for the web [37ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.1) [35ms]
    • Android Studio at /Applications/Android Studio.app/Contents
    • 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.7+0-17.0.7b1000.6-10550314)

[✓] VS Code (version 1.98.2) [32ms]
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.106.0

[✓] Connected device (2 available) [18.0s]
    • iPhone 15 (mobile) • CD0DF359-07AA-4D10-BC02-17B25EE64F92 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-17-2 (simulator)
    • Chrome (web)       • chrome                               • web-javascript • Google Chrome 134.0.6998.119
    ! Error: Browsing on the local area network for Apw. 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 [644ms]
    • All expected network resources are available.

! Doctor found issues in 2 categories.
@LavanyaGowtham2021 LavanyaGowtham2021 added charts Charts component open Open labels Mar 25, 2025
@PreethikaSelvam
Copy link
Contributor

Hi @alunparanggi,

According to our current data label implementation, we adjust the label placement when it exceeds the source boundary to ensure visibility. In your case, the labels for the first and last bars are repositioned within the chart’s bounds, causing a slight shift. This behavior is intentional to prevent labels from being clipped.

To remove this adjustment, we suggest using Transform.translate in the data label builder with a dynamically adjusted offset, ensuring labels remain correctly positioned based on the platform and index, preventing overlap or out-of-bounds placement for a consistent display across devices. You can adjust the dynamicOffsetX values as needed. We have shared a modified code snippet, sample and output for your reference.

Code snippet:

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 400,
      child: SfCartesianChart(
        tooltipBehavior: TooltipBehavior(
          enable: true,
          color: Colors.blue,
          builder: (dynamic data, dynamic point, dynamic series, int pointIndex,
              int seriesIndex) {
            return _tooltip(data: data, isIntake: seriesIndex == 0);
          },
        ),
        primaryXAxis: CategoryAxis(),
        series: <CartesianSeries<CalorieHistory, String>>[
          // Calorie intake
          ColumnSeries<CalorieHistory, String>(
            dataSource: caloriesData7Days,
            color: barColor,
            xValueMapper: (CalorieHistory data, _) =>
                DateHelper.getDayOfWeek(data.date).substring(0, 3),
            yValueMapper: (CalorieHistory data, _) => data.caloriesIntake,
            dataLabelSettings: DataLabelSettings(
              isVisible: true,
              offset: Offset(0, -42),
              labelIntersectAction: LabelIntersectAction.shift,
              builder: (dynamic data, dynamic point, dynamic series,
                  int pointIndex, int seriesIndex) {
                double dynamicOffsetX = 0;
                if (!kIsWeb && Platform.isAndroid) {
                  if (pointIndex == 0) {
                    dynamicOffsetX = -17; // Adjust as per the requirement.
                  }
                }
                return Transform.translate(
                  offset: Offset(dynamicOffsetX, 0),
                  child: Transform.rotate(
                    angle: 90 * 3.14159265359 / 180,
                    child: Text(
                      '${data.caloriesIntake.toStringAsFixed(2)} kcal',
                      style: TextStyle(color: Colors.white, fontSize: 10),
                    ),
                  ),
                );
              },
            ),
          ),
          // Calorie burnt
          ColumnSeries<CalorieHistory, String>(
            dataSource: caloriesData7Days,
            color: Colors.deepOrange,
            xValueMapper: (CalorieHistory data, _) =>
                DateHelper.getDayOfWeek(data.date).substring(0, 3),
            yValueMapper: (CalorieHistory data, _) => data.caloriesBurnt,
            dataLabelSettings: DataLabelSettings(
              isVisible: true,
              offset: Offset(0, -42),
              labelIntersectAction: LabelIntersectAction.none,
              builder: (dynamic data, dynamic point, dynamic series,
                  int pointIndex, int seriesIndex) {
                double dynamicOffsetX = 0;
                if (!kIsWeb && Platform.isAndroid) {
                  if (pointIndex == caloriesData7Days.length - 1) {
                    dynamicOffsetX = 19; // Adjust as per the requirement.
                  }
                }
                return Transform.translate(
                  offset: Offset(dynamicOffsetX, 0),
                  child: Transform.rotate(
                    angle: 90 * 3.14159265359 / 180,
                    child: Text(
                      '${data.caloriesBurnt.toStringAsFixed(2)} kcal',
                      style: TextStyle(color: Colors.black, fontSize: 10),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

Output:
Android:

Image

Windows:

Image

Web:

Image

Please let us know if you need any further assistance.

gh2313 (2).zip

Regards,
Preethika Selvam.

@Saravanan-Madhesh Saravanan-Madhesh added waiting for customer response Cannot make further progress until the customer responds. and removed open Open labels Apr 9, 2025
@LavanyaGowtham2021 LavanyaGowtham2021 added the solved Solved the query using existing solutions label Apr 24, 2025
@LavanyaGowtham2021
Copy link
Collaborator

Please reopen this ticket, if you need further assistance with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
charts Charts component solved Solved the query using existing solutions waiting for customer response Cannot make further progress until the customer responds.
Projects
None yet
Development

No branches or pull requests

4 participants