fl_chart icon indicating copy to clipboard operation
fl_chart copied to clipboard

FlTitlesData -> leftTitles shows static highest value with overlap on other titles

Open large opened this issue 3 years ago • 24 comments

Describe the bug A picture says more than 1000-words image Titles are correct most of the time, like in the picture below image

This static value was not showing in fl_chart v0.40.2 that I used before.

To Reproduce

//Textstyle
              TextStyle? verticalSideTitle = Theme.of(context)
                  .textTheme
                  .bodyText2
                  ?.copyWith(
                      fontSize: 10, color: currentTheme.verticalSideTitle);

//Barchart Init
BarChart(BarChartData(
//...
titlesData: FlTitlesData(
                        show: true,
                        leftTitles: SideTitles(
                          showTitles: true,
                          reservedSize: _reservedVertical,
                          getTextStyles: (context, value) => verticalSideTitle,
                          margin: 0,
                        ),
                        topTitles: SideTitles(showTitles: false),
                        rightTitles: SideTitles(showTitles: false),
                      ),
//...
));

Versions Flutter v2.10.1 fl_chart: 0.45.0

large avatar Feb 14 '22 22:02 large

I'm encountering the same issue. I think maxIncluded and minIncluded in AxisChartHelper.iterateThroughAxis should be parameterised to give us the option to include the max and min value on the axes.

Edit: In addition to this I'm seeing that some values are printed twice in the same place. This seems to be due to rounding errors. I ran the debugger and it happens at lastPosition and therefore lastPositionOverlapsWithMax is false because 0.05 and 0.05000000000000002 are not equal. I can make a different issue of this if requested.

Debugger screenshot

cirediew avatar Feb 15 '22 15:02 cirediew

I haven't figured out where the bug are, but I got feedback from some users on it... Good test to try to pin point where, my flutter skills aren't there yet ;)

large avatar Feb 15 '22 20:02 large

Got same issue after updating from 0.41 to 0.45

catteneo avatar Feb 16 '22 11:02 catteneo

Same issue, test from 0.45, any workaround?

244317809 avatar Feb 22 '22 06:02 244317809

Guys, you waited about 9 days. But you didn't provide a reproducible code (a robust main.dart) yet. It's so simple, I explained everywhere that if you provide a reproducible code, I can help you way faster.

imaNNeo avatar Feb 23 '22 18:02 imaNNeo

@imaNNeoFighT

Two examples:

  • The top one is the one where axis values are printed in the same place twice, what I described here. You can tell by the colour 0.05 is painted multiple times.
  • The bottom one is that @large created this issue for. The value 21.5 is printed while the interval in SideTitles is set to 1.

Thank you in advance!

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';

class TestPage extends StatelessWidget {
  const TestPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: SingleChildScrollView(
          child: Column(
            children: [
              SizedBox(
                height: 300,
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: BarChart(
                    BarChartData(
                      titlesData: FlTitlesData(
                          show: true,
                          topTitles: SideTitles(showTitles: false),
                          leftTitles: SideTitles(
                              showTitles: true,
                              textAlign: TextAlign.end,
                              getTextStyles: (context, _) =>
                                  const TextStyle(color: Colors.black54),
                              getTitles: (double value) =>
                                  value.toStringAsFixed(2),
                              interval: 0.01,
                              reservedSize: 50),
                          bottomTitles: SideTitles(
                            showTitles: true,
                            interval: 1,
                            getTitles: (index) {
                              return index.toString();
                            },
                          ),
                          rightTitles: SideTitles(showTitles: false)),
                      barGroups: [
                        BarChartGroupData(
                            x: 0, barRods: [BarChartRodData(y: -0.01)]),
                        BarChartGroupData(
                            x: 1, barRods: [BarChartRodData(y: 0.03)])
                      ],
                      minY: -0.1,
                      maxY: 0.05,
                    ),
                  ),
                ),
              ),
              SizedBox(
                height: 300,
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: BarChart(
                    BarChartData(
                      titlesData: FlTitlesData(
                          show: true,
                          topTitles: SideTitles(showTitles: false),
                          leftTitles: SideTitles(
                              showTitles: true,
                              textAlign: TextAlign.end,
                              getTextStyles: (context, _) =>
                                  const TextStyle(color: Colors.black54),
                              getTitles: (double value) => value.toString(),
                              interval: 1,
                              reservedSize: 50),
                          bottomTitles: SideTitles(
                            showTitles: true,
                            interval: 1,
                            getTitles: (index) {
                              return index.toString();
                            },
                          ),
                          rightTitles: SideTitles(showTitles: false)),
                      barGroups: [
                        BarChartGroupData(
                            x: 0, barRods: [BarChartRodData(y: 10)]),
                        BarChartGroupData(
                            x: 1, barRods: [BarChartRodData(y: 21.5)])
                      ],
                      minY: 5,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Simulator Screen Shot - iPhone 13 - 2022-02-24 at 11 26 14

cirediew avatar Feb 24 '22 10:02 cirediew

I'm facing the same issue, see an example main.dart below. Hope that helps.

To Reproduce

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FL Chart Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          const SizedBox(height: 32.0),
          SizedBox(
            height: 300,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: LineChart(
                LineChartData(
                  minX: 0,
                  maxX: 7,
                  titlesData: FlTitlesData(
                    rightTitles: SideTitles(
                      showTitles: true,
                      margin: 16.0,
                      reservedSize: 40,
                      // interval: 5,
                    ),
                    leftTitles: SideTitles(
                      showTitles: false,
                    ),
                    topTitles: SideTitles(
                      showTitles: false,
                    ),
                  ),
                  lineBarsData: [
                    LineChartBarData(
                      spots: const [
                        FlSpot(0, 11.5),
                        FlSpot(1, 15.5),
                        FlSpot(3, 20.1),
                        FlSpot(7, 19.1),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

Versions

Flutter: v2.10.1 fl_chart: 0.45.0

Actual Result Simulator Screen Shot - iPhone 13 - 2022-03-01 at 15 42 54

teunklijn avatar Mar 01 '22 14:03 teunklijn

I'm facing the same issue, see an example main.dart below. Hope that helps.

To Reproduce

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FL Chart Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          const SizedBox(height: 32.0),
          SizedBox(
            height: 300,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: LineChart(
                LineChartData(
                  minX: 0,
                  maxX: 7,
                  titlesData: FlTitlesData(
                    rightTitles: SideTitles(
                      showTitles: true,
                      margin: 16.0,
                      reservedSize: 40,
                      // interval: 5,
                    ),
                    leftTitles: SideTitles(
                      showTitles: false,
                    ),
                    topTitles: SideTitles(
                      showTitles: false,
                    ),
                  ),
                  lineBarsData: [
                    LineChartBarData(
                      spots: const [
                        FlSpot(0, 11.5),
                        FlSpot(1, 15.5),
                        FlSpot(3, 20.1),
                        FlSpot(7, 19.1),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

Versions

Flutter: v2.10.1 fl_chart: 0.45.0

Actual Result Simulator Screen Shot - iPhone 13 - 2022-03-01 at 15 42 54

Well, you can override the checkToShowTitle callback to hide the min or max value. Check below code:

rightTitles: SideTitles(
    showTitles: true,
    margin: 16.0,
    reservedSize: 40,
    checkToShowTitle: (
      double minValue,
      double maxValue,
      SideTitles sideTitles,
      double appliedInterval,
      double value,
    ) {
      if (value == maxValue || value == minValue) {
        return false;
      }
      return true;
    }
    // interval: 5,
),

imaNNeo avatar Mar 04 '22 14:03 imaNNeo

Thanks, that helps. I would expect that it would respect the interval whenever set it though, instead of also showing the min and max value. Not a big problem of course, the workaround solves my problem.

teunklijn avatar Mar 04 '22 15:03 teunklijn

It's a little hard to detect texts overlapping. BTW we will try our best to do this under the hood. Thank you.

imaNNeo avatar Mar 06 '22 21:03 imaNNeo

@imaNNeoFighT I agree with @teunklijn. I don't think min and max should be forced

Like I mentioned above, adding maxIncluded and minIncluded parameters to SideTitles could be a good solution.

I think maxIncluded and minIncluded in AxisChartHelper.iterateThroughAxis should be parameterised to give us the option to include the max and min value on the axes.

Maybe defaulting them to true, but it will give us the option to exclude them if we want.

cirediew avatar Mar 08 '22 13:03 cirediew

Just came across the same problem and found a quick workaround:

image

Maybe this helps somebody :)

darioklapp avatar Apr 07 '22 13:04 darioklapp

Yes, thank you @ColdSide1755, it is much easier to handle this in the new version. Great change @imaNNeoFighT!

cirediew avatar Apr 08 '22 13:04 cirediew

As a workaround, I reverted back to 0.41.0 where the algorithm seems to be a bit different.

GitHelge avatar Apr 09 '22 09:04 GitHelge

@imaNNeoFighT @ColdSide1755 in the workarounds you have provided if the max element aligns with the interval it will not be rendered. This is an undesired behavior. I think the change needs to be made in axis_chart_helper.dart and the max value should only be included if maxIncluded is true and lastPositionOverlapsWithMax is also true. Look at the function below.

Iterable<double> iterateThroughAxis({
    required double min,
    bool minIncluded = true,
    required double max,
    bool maxIncluded = true,
    required double baseLine,
    required double interval,
  }) sync* {
    final initialValue = Utils().getBestInitialIntervalValue(min, max, interval, baseline: baseLine);
    var axisSeek = initialValue;
    final firstPositionOverlapsWithMin = axisSeek == min;
    if (!minIncluded && firstPositionOverlapsWithMin) {
      axisSeek += interval;
    }
    final diff = max - min;
    final count = diff ~/ interval;
    final lastPosition = initialValue + (count * interval);
    final lastPositionOverlapsWithMax = lastPosition == max;
    final end = !maxIncluded && lastPositionOverlapsWithMax ? max - interval : max;

    final epsilon = interval / 100000;
    if (minIncluded && !firstPositionOverlapsWithMin) {
      yield min;
    }
    while (axisSeek <= end + epsilon) {
      yield axisSeek;
      axisSeek += interval;
    }
   // updated !lastPositionOverlapsWithMax to lastPositionOverlapsWithMax
    if (maxIncluded && lastPositionOverlapsWithMax) { 
      yield max;
    }
  }

Dnathan33 avatar May 20 '22 18:05 Dnathan33

Just came across the same problem and found a quick workaround:

image

Maybe this helps somebody :)

Thank you, this works. However, I think this should work automatically because it is very confusing for new users.

SuricateTrading avatar May 25 '22 13:05 SuricateTrading

Instead of always hiding the max title, you can conditionally hide it when it visually makes sense e.g. if last item is not from actual intervals and if the difference between second last interval and max is less than 50% of interval. This way last title will not be hidden unnecessarily

leftTitles: AxisTitles(
  sideTitles: SideTitles(
    showTitles: true,
    getTitlesWidget: (value, meta) {
      Widget axisTitle = Text(value.toString());
      // A workaround to hide the max value title as FLChart is overlapping it on top of previous
      if (value == meta.max) {
        final remainder = value % meta.appliedInterval;
        if (remainder != 0.0 && remainder / meta.appliedInterval < 0.5) {
          axisTitle = const SizedBox.shrink();
        }
      }
      return SideTitleWidget(axisSide: meta.axisSide, child: axisTitle);
    },
  ),
)

absar avatar Aug 15 '22 14:08 absar

Thank you @SuricateTrading and @absar - the modded solution worked nicely

jesscmoore avatar Feb 16 '23 20:02 jesscmoore

My current workaround is to manually get the max and min values and calculate the next interval.

double roundNextInterval(double value, double interval) {
  if (value.isNegative) {
    return (value / interval).floor() * interval;
  } else {
    return (value / interval).ceil() * interval;
  }
}

Then I set the minY and maxY to these fixed valies in the LineChartData.

If this should be the default behavior or settable as an option, I could do a PR if somebody can tell me where this is currently calculated.

bobatsar avatar Oct 26 '23 07:10 bobatsar

image

how can i fix this fl_chart: ^0.63.0 BarChart( BarChartData( maxY: maxValue + 5, minY: 0, barGroups: _chartGroups(dailyBarData), // titlesData: FlTitlesData(bottomTitles: AxisTitles(sideTitles: _bottomTitles)), borderData: FlBorderData(show: false), gridData: const FlGridData( show: true, drawHorizontalLine: true, drawVerticalLine: false), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: _bottomTitles, ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false)), leftTitles: AxisTitles(sideTitles: SideTitles()),

              show: true,
            ),
            barTouchData: BarTouchData(
                handleBuiltInTouches: true,
                touchTooltipData:
                    BarTouchTooltipData(tooltipBgColor: Colors.white))),
      ),

ShakilKhan999 avatar Dec 12 '23 09:12 ShakilKhan999

The essential reason for the problem is that the scale value with baseline as the initial value and interval as the step may be relatively close to the value of max or min. Because min or max is not within the numerical dimension with baseline as the initial value. (baseline defaults to 0)

I adjust the baseline to min. There will be no problem.

LineChartData( maxY: controller.getRangeY()['max'], minY: controller.getRangeY()['min'], baselineY: controller.getRangeY()['min'] ...)

artsooter avatar Mar 29 '24 15:03 artsooter