Skip to content

[syncfusion_charts] Uniform Label Display with intersect? #2357

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

Open
DrNiels opened this issue May 23, 2025 · 7 comments
Open

[syncfusion_charts] Uniform Label Display with intersect? #2357

DrNiels opened this issue May 23, 2025 · 7 comments
Labels
charts Charts component waiting for customer response Cannot make further progress until the customer responds.

Comments

@DrNiels
Copy link

DrNiels commented May 23, 2025

Bug description

I have a chart with a fixed interval value. If it becomes too thin, labels of the x axis are hidden by default. That works fine, but with labels having different widths, the resulting look can sometimes become pretty chaotic. Is there a way to force a uniform pattern on the hidden labels, e.g., only showing each third, depending on the available width?

In the code sample, AxisLabelIntersectAction.hide shows, that sometimes every third and sometimes every second label. That provides a really chaotic impression and a fixed interval would be highly appreciated. Or is there already an approach to do that?

Steps to reproduce

  1. Load the code sample
  2. See all available AxisLabelIntersectActions and see that none produces the desired output
  3. See the desired output as last entry, but that's obviously optimized for the current scenario and width and won't hold on flexible width

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';

void main() async {
  runApp(MaterialApp(home: TestChart()));
}

class TestChart extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => TestChartState();
}

class TestChartState extends State<TestChart> {
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Center(
        child: Wrap(
          children: [
            for (AxisLabelIntersectAction intersectAction
                in AxisLabelIntersectAction.values)
              SizedBox(
                width: 400,
                child: SfCartesianChart(
                  title: ChartTitle(text: intersectAction.toString()),
                  primaryYAxis: NumericAxis(),
                  primaryXAxis: DateTimeAxis(
                    intervalType: DateTimeIntervalType.hours,
                    interval: 1,
                    labelIntersectAction: intersectAction,
                    minimum: DateTime(2025, 2, 17),
                    maximum: DateTime(2025, 2, 18),
                  ),
                ),
              ),
            SizedBox(
              width: 400,
              child: SfCartesianChart(
                title: ChartTitle(text: 'Desired'),
                primaryYAxis: NumericAxis(),
                primaryXAxis: DateTimeAxis(
                  intervalType: DateTimeIntervalType.hours,
                  interval: 3,
                  minorTicksPerInterval: 2,
                  minimum: DateTime(2025, 2, 17),
                  maximum: DateTime(2025, 2, 18),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Screenshots or Video

Screenshots / Video demonstration

Image

Stack Traces

Stack Traces

Not applicable

On which target platforms have you observed this bug?

Web

Flutter Doctor output

Doctor output

I use syncfusion_flutter_charts in version 29.2.4

[√] Flutter (Channel stable, 3.32.0, on Microsoft Windows [Version 10.0.19045.5854], locale de-DE) [617ms]
    • Flutter version 3.32.0 on channel stable at C:\Users\Niels\Documents\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision be698c48a6 (4 days ago), 2025-05-19 12:59:14 -0700
    • Engine revision 1881800949
    • Dart version 3.8.0
    • DevTools version 2.45.1

[√] Windows Version (10 Education 64-bit, 22H2, 2009) [2,4s]

[√] Android toolchain - develop for Android devices (Android SDK version 34.0.0-rc4) [3,0s]
    • Android SDK at C:\Users\Niels\AppData\Local\Android\Sdk
    • Platform android-34, build-tools 34.0.0-rc4
    • Java binary at: C:\Program Files\Android\Android Studio1\jbr\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.11+0--11852314)
    • All Android licenses accepted.

[√] Chrome - develop for the web [162ms]
    • Chrome at C:\Users\Niels\AppData\Local\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.8.6) [160ms]
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
    • Visual Studio Community 2022 version 17.8.34525.116
    • Windows 10 SDK version 10.0.19041.0

[!] Android Studio (version 2021.2) [26ms]
    • Android Studio at C:\Program Files\Android\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
    X Unable to determine bundled Java version.
    • Try updating or re-installing Android Studio.

[√] Android Studio (version 2024.1) [25ms]
    • Android Studio at C:\Program Files\Android\Android Studio1
    • 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.11+0--11852314)

[√] VS Code, 64-bit edition (version 1.96.0) [24ms]
    • VS Code at C:\Program Files\Microsoft VS Code
    • Flutter extension version 3.110.0

[√] Connected device (3 available) [343ms]
    • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.19045.5854]
    • Chrome (web)      • chrome  • web-javascript • Google Chrome 136.0.7103.114
    • Edge (web)        • edge    • web-javascript • Microsoft Edge 128.0.2739.42

[√] Network resources [748ms]
    • All expected network resources are available.

! Doctor found issues in 1 category.
@VijayakumarMariappan VijayakumarMariappan added charts Charts component open Open labels May 26, 2025
@Mugunthan-Ramalingam
Copy link
Contributor

Hi @DrNiels,

We would like to inform you that the AxisLabelIntersectAction property is designed to manage overlapping axis labels. When using the hide option, it automatically hides the next label if it overlaps with the previous labels. This logic is applied sequentially to all axis labels. Additionally, the chart calculates an appropriate automatic interval based on the available axis size to minimize overlaps.

If you prefer to control exactly which labels appear, you can disable the automatic interval by setting the interval property to a fixed value.

Could you please describe your exact expectations for how overlapping labels should appear when using the hide intersect action so that we can assist you more effectively?

Regards,
Mugunthan.

@VijayakumarMariappan VijayakumarMariappan added waiting for customer response Cannot make further progress until the customer responds. and removed open Open labels May 28, 2025
@DrNiels
Copy link
Author

DrNiels commented Jun 2, 2025

My expectation or preference would be that the hiding of ticks would be uniform instead of only hiding the least required number.

If you check the screenshot, you can see that the hide action displays "12AM", "3AM", "5AM", ... . As such the first step between two visible ticks is 3 while the next is 2. From 9 AM to 12 PM there's again a step of 3 ticks. That does look a bit wild on the x axis. Instead, it would be nice if the axis would determine the highest required step size, in this case 3 ticks. If that was applied to the x axis, the look would be uniform as "Show every third tick" looks clear and customers can easily understand it. Sure, you show less ticks in total, but have a good looking axis in return, as shown in the "Desired" chart of the screenshot.

@sfHariHaraSudhan sfHariHaraSudhan added open Open and removed waiting for customer response Cannot make further progress until the customer responds. labels Jun 3, 2025
@Mugunthan-Ramalingam
Copy link
Contributor

Hi @DrNiels,

Thank you for your detailed explanation. We have understood your query.

As mentioned previously, the AxisLabelIntersectAction property is designed to manage overlapping axis labels. When using the hide option, it automatically hides the next label if it overlaps with the previous labels. This logic is applied sequentially to all axis labels.

For example, If the first label like "12 AM" has a wider width due to two digits, it may cause the next one or two labels to be hidden to avoid overlap. As a result, the next visible label might be "3 AM". Later, labels like "5 AM" (with a narrower width) may appear, leading to inconsistent spacing such as "3 AM", "5 AM", "6 AM". This behavior tries to fit as many labels as possible but may create uneven gaps.

In your case, if you are looking for an alternative to using the interval property, you can use the axisLabelFormatter callback to apply custom logic to show labels at fixed intervals (e.g., every 3 hours). Also, set AxisLabelIntersectAction to none to prevent automatic hiding.

Below is a code snippet showing how to display labels every 3 hours by calculating the difference between the current label and a fixed start time:

primaryXAxis: DateTimeAxis(
axisLabelFormatter: (AxisLabelRenderDetails args) {
  final start = DateTime(2025, 2, 17);
  final current = args.value;
// Calculate how many hours have passed since the start.
  final hoursSinceStart = ((current - start.millisecondsSinceEpoch) / Duration.millisecondsPerHour).round(); 
// Only show every 3rd hour label.
  if (hoursSinceStart % 3 != 0) {
    return  ChartAxisLabel('', args.textStyle);
  }
  return  ChartAxisLabel(args.text, args.textStyle);
},
labelIntersectAction: AxisLabelIntersectAction.none,
. . . .
),

Output:

Image

UG Link:

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

If you have any further queries, please get back to us. We are always happy to assist you.

Regards,
Mugunthan.

@DrNiels
Copy link
Author

DrNiels commented Jun 4, 2025

Hi @Mugunthan-Ramalingam,

thank you for your feedback. Only displaying every second/third/... label is easy. The complicated part is to determine the required interval. If I have enough space, I want to show every label. If I don't, then every second. If there's not enough space for that either, every third, etc..

@Mugunthan-Ramalingam
Copy link
Contributor

Hi @DrNiels,

As mentioned earlier, the AxisLabelIntersectAction property is specifically designed to manage overlapping axis labels. When the hide option is enabled, it automatically hides only those labels that overlap with the previous one. This process is handled sequentially along the axis, and this is the default behavior.

Please note that the axisLabelFormatter is a possible workaround and not an exact solution for managing label visibility. However, you can customize the shared code snippet further based on your specific requirements.

Code snippet:

primaryXAxis: DateTimeAxis(
  axisLabelFormatter: (AxisLabelRenderDetails args) {
    final DateTime start = DateTime(2025, 2, 17);
    final DateTime current = DateTime.fromMillisecondsSinceEpoch(
      args.value.toInt(),
    );
    final int hoursSinceStart = current.difference(start).inHours;
 
    // Measure label width
    final TextPainter textPainter = TextPainter(
      text: TextSpan(text: args.text, style: args.textStyle),
      textDirection: TextDirection.ltr,
    )..layout();
 
    final double labelWidth = textPainter.width + 8; // padding
    final double chartWidth = MediaQuery.of(context).size.width;
 
    // Max number of labels that fit without overlap
    final int maxLabels = (chartWidth / labelWidth).floor();
 
    // Get axis range
    final num minVal =
        args.axis.actualRange?.minimum ?? args.value - 1;
    final num maxVal =
        args.axis.actualRange?.maximum ?? args.value + 1;
    final DateTime min = DateTime.fromMillisecondsSinceEpoch(
      minVal.toInt(),
    );
    final DateTime max = DateTime.fromMillisecondsSinceEpoch(
      maxVal.toInt(),
    );
    final int totalHours = max.difference(min).inHours;
 
    // Calculate interval dynamically to fit labels in chartWidth
    int interval = 1;
    while ((totalHours / interval) > maxLabels) {
      interval++;
    }
    interval = interval.clamp(1, 24);
 
    // Show label only if hour is divisible by interval
    if (hoursSinceStart % interval != 0) {
      return ChartAxisLabel('', args.textStyle);
    }
 
    return ChartAxisLabel(args.text, args.textStyle);
  },
  labelIntersectAction: AxisLabelIntersectAction.none,
  . . .
),

Demo:

Image

Regards,
Mugunthan.

@DrNiels
Copy link
Author

DrNiels commented Jun 11, 2025

Thank you for the snippet. I'll try to implement it into my code. Assuming everything works fine with my current PR (#2369), would you accept a PR where I add a new AxisLabelIntersectOption like .hiddenUniform that does this? I could imagine I'm not the only one who would like their labels uniform.

@Mugunthan-Ramalingam
Copy link
Contributor

Hi @DrNiels,

Thank you for your contribution and for taking the time to submit a pull request with your proposed changes.

We truly appreciate your effort, and we’ll take your suggestions into consideration during our future implementation planning.

Regards,
Mugunthan.

@sfHariHaraSudhan sfHariHaraSudhan added waiting for customer response Cannot make further progress until the customer responds. and removed open Open labels Jun 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
charts Charts component waiting for customer response Cannot make further progress until the customer responds.
Projects
None yet
Development

No branches or pull requests

4 participants