Skip to content
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

Performance problem with LineCharts #322

Closed
Wizzel1 opened this issue Apr 27, 2020 · 21 comments
Closed

Performance problem with LineCharts #322

Wizzel1 opened this issue Apr 27, 2020 · 21 comments
Labels
enhancement New feature or request Line Chart

Comments

@Wizzel1
Copy link

Wizzel1 commented Apr 27, 2020

Hi, i am making a Coronatracker app.

I am implementing a LineChart Widget with the Data for each day.
Unbenannt

The maxY is equal to the tracked days (number of days since 22nd January)
The maxX is equal to the highest number of confirmed cases divided by 100000 (working with the absolute number took the chart forever to build)

Everything works as intended, but it impacts the smoothness of the scrolling of the singlechildscrollview, in which the chart is implemented.

Do you have any suggestions ?

@imaNNeo
Copy link
Owner

imaNNeo commented Apr 27, 2020

Hi,
If you provide a reproducible code, it helps us to find out what's going on.
I don't have any idea without reading the code and seeing the sample
Thanks!

@Wizzel1
Copy link
Author

Wizzel1 commented Apr 27, 2020

Not sure if you can work with this, but i copied the entire class and hardcoded values and wrote some comments.

class LineChartSample1 extends StatefulWidget {
  final List<Day> dayList;
  final formatter = new NumberFormat('#,###,###');
  LineChartSample1(this.dayList);

  @override
  State<StatefulWidget> createState() => LineChartSample1State();
}

class LineChartSample1State extends State<LineChartSample1> {
  double _lineWidth = 4;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 1.23,
      child: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.all(Radius.circular(18)),
        ),
        child: Stack(
          children: <Widget>[
            Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Expanded(
                  child: Padding(
                    padding: EdgeInsets.fromLTRB(10, 20, 20, 20),
                    child: LineChart(
                      sampleData1(),
                      swapAnimationDuration: Duration(milliseconds: 250),
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  LineChartData sampleData1() {
    return LineChartData(
      lineTouchData: LineTouchData(
        touchTooltipData: LineTouchTooltipData(
          getTooltipItems: (List<LineBarSpot> barSpots) {

            //This part is used to convert the Date to a readable format
            String date = widget.dayList[barSpots[0].spotIndex].date;
            List<String> temp = date.replaceAll('-', ' ').split(' ');
            temp[1].length != 2 ? temp[1] = '0${temp[1]}' : 0;
            temp[2].length != 2 ? temp[2] = '0${temp[2]}' : 0;
            String usedDate =
                DateFormat('d MMMM').format(DateTime.parse(temp[0] + temp[1] + temp[2]));

            String confirmed =
                widget.formatter.format(widget.dayList[barSpots[0].spotIndex].confirmed).toString();
            String recovered =
                widget.formatter.format(widget.dayList[barSpots[2].spotIndex].recovered).toString();
            String deaths =
                widget.formatter.format(widget.dayList[barSpots[1].spotIndex].deaths).toString();
            LineTooltipItem dateItem = LineTooltipItem(
                usedDate,
                kTitleTextstyle.copyWith(
                    color: Colors.grey,
                    fontSize: 15,
                    fontFamily: GoogleFonts.iBMPlexSans().fontFamily));
            LineTooltipItem confirmedItem = LineTooltipItem(
                confirmed,
                kTitleTextstyle.copyWith(
                    color: kInfectedColor,
                    fontSize: 15,
                    fontFamily: GoogleFonts.iBMPlexMono().fontFamily,
                    fontWeight: FontWeight.w600));
            LineTooltipItem recoveredItem = LineTooltipItem(
                recovered,
                kTitleTextstyle.copyWith(
                    color: kRecovercolor,
                    fontSize: 15,
                    fontFamily: GoogleFonts.iBMPlexMono().fontFamily,
                    fontWeight: FontWeight.w600));
            LineTooltipItem deathsItem = LineTooltipItem(
                deaths,
                kTitleTextstyle.copyWith(
                    color: kDeathColor,
                    fontSize: 15,
                    fontFamily: GoogleFonts.iBMPlexMono().fontFamily,
                    fontWeight: FontWeight.w600));
            return [dateItem, confirmedItem, recoveredItem, deathsItem];
          },
          tooltipBgColor: kBackgroundColor,
        ),
        touchCallback: (LineTouchResponse touchResponse) {},
        handleBuiltInTouches: true,
      ),
      gridData: FlGridData(
        horizontalInterval: widget.dayList.last.confirmed < 500000 ? 1.0 : 5.0,
        show: true,
      ),
      titlesData: FlTitlesData(
        bottomTitles: SideTitles(
          showTitles: true,
          reservedSize: 22,
          textStyle: TextStyle(
              color: Color(0xff72719b),
              fontWeight: FontWeight.bold,
              fontSize: 16,
              fontFamily: GoogleFonts.iBMPlexSans().fontFamily),
          margin: 10,
          getTitles: (value) {
            switch (value.toInt()) {
              case 11:
                return 'February';
              case 40:
                return 'March';
              case 72:
                return 'April';
            }
            return '';
          },
        ),
        leftTitles: SideTitles(
          showTitles: true,
          textStyle: TextStyle(
              color: Color(0xff75729e),
              fontWeight: FontWeight.bold,
              fontSize: 14,
              fontFamily: GoogleFonts.iBMPlexSans().fontFamily),
          getTitles: (value) {
            switch (value) {
              case 1:
                return '100k';
              case 5:
                return '500k';
              case 10:
                return '1M';
              case 15:
                return '1.5M';
              case 20:
                return '2M';
              case 25:
                return '2.5M';
              default:
                return '';
            }
          },
          margin: 8,
          reservedSize: 30,
        ),
      ),
      borderData: FlBorderData(
        show: false,
        border: const Border(
          bottom: BorderSide(
            color: Color(0xff4e4965),
            width: 4,
          ),
          left: BorderSide(
            color: Colors.transparent,
          ),
          right: BorderSide(
            color: Colors.transparent,
          ),
          top: BorderSide(
            color: Colors.transparent,
          ),
        ),
      ),
      minX: 0,
      maxX: 95,
      maxY: 25,
      minY: 0,
      lineBarsData: _createData(widget.dayList),
    );
  }

  List<LineChartBarData> _createData(List<Day> raw) {
    LineChartBarData confirmedData = LineChartBarData(
      spots: List.from(raw.map((e) {
        double index = raw.indexOf(e).toDouble();
        return FlSpot(index, e.confirmed / 100000);
      })),
      isCurved: true,
      colors: [
        kInfectedColor,
      ],
      barWidth: _lineWidth,
      isStrokeCapRound: true,
      dotData: FlDotData(
        show: false,
      ),
      belowBarData: BarAreaData(
        show: false,
      ),
    );
    LineChartBarData recoveredData = LineChartBarData(
      spots: List.from(raw.map((e) {
        double index = raw.indexOf(e).toDouble();
        return FlSpot(index, e.recovered / 100000);
      })),
      isCurved: true,
      colors: [
        kRecovercolor,
      ],
      barWidth: _lineWidth,
      isStrokeCapRound: true,
      dotData: FlDotData(
        show: false,
      ),
      belowBarData: BarAreaData(
        show: false,
      ),
    );
    LineChartBarData deathsData = LineChartBarData(
      spots: List.from(raw.map((e) {
        double index = raw.indexOf(e).toDouble();
        return FlSpot(index, e.deaths / 100000);
      })),
      isCurved: true,
      colors: [
        kDeathColor,
      ],
      barWidth: _lineWidth,
      isStrokeCapRound: true,
      dotData: FlDotData(
        show: false,
      ),
      belowBarData: BarAreaData(
        show: false,
      ),
    );
    LineChartBarData dateData = LineChartBarData(
      spots: List.from(raw.map((e) {
        double index = raw.indexOf(e).toDouble();
        return FlSpot(index, 0);
      })),
      isCurved: true,
      colors: [
        Colors.transparent,
      ],
      barWidth: _lineWidth,
      isStrokeCapRound: true,
      dotData: FlDotData(
        show: false,
      ),
      belowBarData: BarAreaData(
        show: false,
      ),
    );
    return [confirmedData, recoveredData, deathsData, dateData];
  }
}

@imaNNeo
Copy link
Owner

imaNNeo commented Apr 28, 2020

To Reproduce
Provide us a completely reproducible code (contains the main function) in a file, it helps us to find the bug immediately.

@bohdan1krokhmaliuk
Copy link

I met same thing - all you need is scale chart date and just show right numbers - than it will work fine

@Wizzel1
Copy link
Author

Wizzel1 commented Apr 29, 2020

Im sorry, i don't quite understand. Can you please elaborate ?

@bohdan1krokhmaliuk
Copy link

Im sorry, i don't quite understand. Can you please elaborate ?

Looks like with bigger number chart will draw more points. I suppose if 96k on y axis it draws at least 96k times, so you can try to descale your chart so you can draw from 0 to 96 instead of 0 to 96k and show user the number multiplied by 1k. So at the very top of the chart you will have for example point (6, 96), and you may show you see the point (6, 96k) it will work, an your chart will not be laggy. I use maximum up to 100 points for each axis and it works perfectly.

P. S. The chart by it own may have up to a 100 maximum on x and on y, and you just fit you point in this value

@Wizzel1
Copy link
Author

Wizzel1 commented May 1, 2020

To Reproduce
Provide us a completely reproducible code (contains the main function) in a file, it helps us to find the bug immediately.

Hi, i uploaded the app, please take a look. Hope this helps : https://github.com/Wizzel1/corona

@Wizzel1
Copy link
Author

Wizzel1 commented May 11, 2020

@imaNNeoFighT was the repo helpful to you ?

@imaNNeo
Copy link
Owner

imaNNeo commented May 11, 2020

Hi, I didn't have time to check it and apologize,
I will check it ASAP.
Thanks!

@imaNNeo
Copy link
Owner

imaNNeo commented May 12, 2020

It doesn't help me,
please simplify your problem.
Thanks!

@igoriuz
Copy link

igoriuz commented May 20, 2020

I can confirm that there's definitely a performance problem with line charts working with dates in milliseconds. I divided also by 100000 to get it running. But with increasing points, it has difficulties to build, sometimes it's building until it crashes the whole app.

Trying to provide an example soon.

@igoriuz
Copy link

igoriuz commented May 20, 2020

I've created a sample chart in examples at my forked repo: https://github.com/igoriuz/fl_chart
This is just a proof of concept. If you wish, i can extend this sample and improve it with further logic (like calculating min/max with small offsets and divide the chart with N intervalls etc.)

If you turn useRawMilliSeconds to true, dates won't be divided through 100000 and you'll see the crashing behaviour.

Just for some context: Theres a dummy object which holds the date in milliseconds for the x axis and a temperature value for the y axis. For each day i just create one FlSpot. This means for last 7 days, there are just 7 spots. This example works really well unless you turn useRawMilliSeconds to true.

@imaNNeo
Copy link
Owner

imaNNeo commented May 24, 2020

Hi @igoriuz
Thanks for the explanation,
where you did create that sample?
I mean in which file

@igoriuz
Copy link

igoriuz commented May 24, 2020

All these three files from this commit: igoriuz@e1ce52b

@imaNNeo
Copy link
Owner

imaNNeo commented May 24, 2020

Okay I'm on it.

@igoriuz
Copy link

igoriuz commented May 24, 2020

I'm unsure but i think i noticed that LineCharts can't work with null values for y axis. Thank you very much!

@imaNNeo
Copy link
Owner

imaNNeo commented Jun 2, 2020

Hi,
We've just released 0.10.0 version,
please check it out and let me know whether it is fixed or not.
Thanks!

@igoriuz
Copy link

igoriuz commented Jun 3, 2020

It fixed my problem. Thank you very much.

@imaNNeo
Copy link
Owner

imaNNeo commented Jun 3, 2020

You're welcome.
Cheers!

@Wizzel1
Copy link
Author

Wizzel1 commented Jun 4, 2020

@imaNNeoFighT yes, way better !

@imaNNeo
Copy link
Owner

imaNNeo commented Jun 5, 2020

Great,
If you think this issue is solved, please close it.
Thanks!

@Wizzel1 Wizzel1 closed this as completed Jun 6, 2020
@imaNNeo imaNNeo added enhancement New feature or request Line Chart labels Jun 6, 2020
ezmegy pushed a commit to ezmegy/fl_chart that referenced this issue Nov 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Line Chart
Projects
None yet
Development

No branches or pull requests

4 participants