Bhavya Mishra Bhavya wanted to be a writer, so she writes code now. Bhavya’s career has been focused on all things Android for the past five years. Drawn to Fueled by the team culture and the cutting edge technologies we use, she doesn’t plan on stopping anytime soon!

Build beautiful charts in Flutter with FL Chart

6 min read 1687

Build beautiful charts in Flutter with FL Chart

Gathering data and displaying it via charts is increasingly common in mobile apps. A popular example is a stock investment app that displays lots of charts, like prices for a stock and portfolio distribution pie charts. And today, we are going to learn how to build beautiful charts for these situations using the FL Chart package in Flutter.

This tutorial uses fabricated data to keep things simple and make it easier to understand the package implementation; you can easily replace it with data from your APIs.

Prerequisites

Let’s do a quick check of things we need before jumping right ahead:

  • The Flutter SDK
  • An editor; you can use either Visual Code or Android Studio
  • At least beginner-level knowledge of Flutter

That’s pretty much it!

Setup

Add the dependency to your pubspec.yaml file:

dependencies:
fl_chart: ^0.45.0

Creating a line chart

Line charts are one of the most useful charts when it comes to data representation. Let’s create a line chart for the annual price data for a company’s stock. We will use the LineChart widget to create a line chart — that’s how obvious it is.

The widget takes LineChartData as the key parameter with swapAnimationDuration and swapAnimationCurve as optional parameters that can be used to control the implicit animation during a state change:

LineChart(
  LineChartData(
    // control how the chart looks like here
  ),
  swapAnimationDuration: Duration(milliseconds: 150), // Optional
  swapAnimationCurve: Curves.linear, // Optional
);

A very simple implementation will look something like this:

Flutter Chart Implementation

Let’s take a look at the code:

We made a custom demo for .
No really. Click here to check it out.

class LineChartWidget extends StatelessWidget {
 final List<PricePoint> points;
 final bool isPositiveChange;

 const LineChartWidget(this.points, this.isPositiveChange, {Key? key})
     : super(key: key);

 @override
 Widget build(BuildContext context) {
   return AspectRatio(
     aspectRatio: 2,
     child: LineChart(
       LineChartData(
         lineBarsData: [
           LineChartBarData(
             spots: points.map((point) => FlSpot(point.x, point.y)).toList(),
             isCurved: false,
             colors: [
               isPositiveChange ? Colors.green : Colors.red,
             ],
             dotData: FlDotData(
               show: false,
             ),
           ),
         ],
       ),
     ),
   );
 }
}

LineChartData contains all the information on how the line chart would look. We have used the lineBarsData property that takes a list of LineChartBarData to draw one or more lines on the graph.

LineChartBarData is then used to define how the individual lines would look. It takes a list of spots that are similar to the plot points for a line graph. By default, these points will be marked with circular dots, but we can control their appearance by using dotData. It’s possible to hide them as well using the same.

We can also control if the line should be curved or not using isCurved, and the color of the line is modified using the colors property that takes a list of colors. We can use colorStops to change the color of the line after the stop point.

N.B., it is necessary to wrap the LineChart widget with either a SizedBox or AspectRatio for it to actually show up on screen. Prefer using AspectRatio so that the graph is not skewed on different screen sizes.

Now let’s clear the unnecessary clutter from this graph and make it a bit cleaner.

Graph With Less Clutter

The code for the sample looks like this:

LineChartData(
…
….
 borderData: FlBorderData(
     border: const Border(bottom: BorderSide(), left: BorderSide())),
 gridData: FlGridData(show: false),
   titlesData: FlTitlesData(
     bottomTitles: _bottomTitles,
     leftTitles: SideTitles(showTitles: false),
     topTitles: SideTitles(showTitles: false),
     rightTitles: SideTitles(showTitles: false),
   )
),
…
…

​​SideTitles get _bottomTitles => SideTitles(
     showTitles: true,
     reservedSize: 22,
     margin: 10,
     interval: 1,
     getTextStyles: (context, value) => const TextStyle(
       color: Colors.blueGrey,
       fontWeight: FontWeight.bold,
       fontSize: 16,
     ),
     getTitles: (value) {
       switch (value.toInt()) {
         case 1:
           return 'Jan';
         case 3:
           return 'Mar';
         case 5:
           return 'May';
         case 7:
           return 'Jul';
         case 9:
           return 'Sep';
         case 11:
           return 'Nov';
       }
       return '';
     },
   );

We can also add touch events on the line chart and get a callback for the touch event to perform further operations. By default, LineChartData displays a tooltip with the y value touching a spot on the line chart, but we can modify this to display whatever text we want and can style it differently as well.

We are using LineTouchData that provides a bunch of properties like touchCallback, touchTooltipData, and even getTouchedSpotIndicator to modify how the touch indicator and tooltip looks.

Here’s a simple code snippet to demonstrate the same:

LineChartData(
   lineTouchData: LineTouchData(
       enabled: true,
       touchCallback:
           (FlTouchEvent event, LineTouchResponse? touchResponse) {
         // TODO : Utilize touch event here to perform any operation
       },
       touchTooltipData: LineTouchTooltipData(
         tooltipBgColor: Colors.blue,
         tooltipRoundedRadius: 20.0,
         showOnTopOfTheChartBoxArea: true,
         fitInsideHorizontally: true,
         tooltipMargin: 0,
         getTooltipItems: (touchedSpots) {
           return touchedSpots.map(
             (LineBarSpot touchedSpot) {
               const textStyle = TextStyle(
                 fontSize: 10,
                 fontWeight: FontWeight.w700,
                 color: Colors.white,
               );
               return LineTooltipItem(
                 points[touchedSpot.spotIndex].y.toInt().toString(),
                 textStyle,
               );
             },
           ).toList();
         },
       ),
       getTouchedSpotIndicator:
           (LineChartBarData barData, List<int> indicators) {
         return indicators.map(
           (int index) {
             final line = FlLine(
                 color: Colors.grey,
                 strokeWidth: 1,
                 dashArray: _dashArray);
             return TouchedSpotIndicatorData(
               line,
               FlDotData(show: false),
             );
           },
         ).toList();
       },
       getTouchLineEnd: (_, __) => double.infinity),
….
….
  )

The complete LineChartWidget looks like this:

import 'dart:math';

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

import 'data/price_point.dart';

const _dashArray = [4, 2];

class LineChartWidget extends StatelessWidget {
 final List<PricePoint> points;
 final bool isPositiveChange;

 const LineChartWidget(this.points, this.isPositiveChange, {Key? key})
     : super(key: key);

 @override
 Widget build(BuildContext context) {
   final minY = points.map((point) => point.y).reduce(min);
   final maxY = points.map((point) => point.y).reduce(max);

   return AspectRatio(
     aspectRatio: 2,
     child: LineChart(
       LineChartData(
           lineTouchData: LineTouchData(
               enabled: true,
               touchCallback:
                   (FlTouchEvent event, LineTouchResponse? touchResponse) {
                 // TODO : Utilize touch event here to perform any operation
               },
               touchTooltipData: LineTouchTooltipData(
                 tooltipBgColor: Colors.blue,
                 tooltipRoundedRadius: 20.0,
                 showOnTopOfTheChartBoxArea: true,
                 fitInsideHorizontally: true,
                 tooltipMargin: 0,
                 getTooltipItems: (touchedSpots) {
                   return touchedSpots.map(
                     (LineBarSpot touchedSpot) {
                       const textStyle = TextStyle(
                         fontSize: 10,
                         fontWeight: FontWeight.w700,
                         color: Colors.white,
                       );
                       return LineTooltipItem(
                         points[touchedSpot.spotIndex].y.toInt().toString(),
                         textStyle,
                       );
                     },
                   ).toList();
                 },
               ),
               getTouchedSpotIndicator:
                   (LineChartBarData barData, List<int> indicators) {
                 return indicators.map(
                   (int index) {
                     final line = FlLine(
                         color: Colors.grey,
                         strokeWidth: 1,
                         dashArray: _dashArray);
                     return TouchedSpotIndicatorData(
                       line,
                       FlDotData(show: false),
                     );
                   },
                 ).toList();
               },
               getTouchLineEnd: (_, __) => double.infinity),
           lineBarsData: [
             LineChartBarData(
               spots: points.map((point) => FlSpot(point.x, point.y)).toList(),
               isCurved: false,
               colors: [
                 isPositiveChange ? Colors.green : Colors.red,
               ],
               dotData: FlDotData(
                 show: false,
               ),
             ),
           ],
           minY: minY,
           minX: 0,
           maxY: maxY,
           borderData: FlBorderData(
               border: const Border(bottom: BorderSide(), left: BorderSide())),
           gridData: FlGridData(show: false),
           titlesData: FlTitlesData(
             bottomTitles: _bottomTitles,
             leftTitles: SideTitles(showTitles: false),
             topTitles: SideTitles(showTitles: false),
             rightTitles: SideTitles(showTitles: false),
           )),
     ),
   );
 }
}

SideTitles get _bottomTitles => SideTitles(
     showTitles: true,
     reservedSize: 22,
     margin: 10,
     interval: 1,
     getTextStyles: (context, value) => const TextStyle(
       color: Colors.blueGrey,
       fontWeight: FontWeight.bold,
       fontSize: 16,
     ),
     getTitles: (value) {
       switch (value.toInt()) {
         case 1:
           return 'Jan';
         case 3:
           return 'Mar';
         case 5:
           return 'May';
         case 7:
           return 'Jul';
         case 9:
           return 'Sep';
         case 11:
           return 'Nov';
       }
       return '';
     },
   );

Wasn’t that simple? Now let’s move on to the next most commonly used chart — i.e., the pie chart.

Creating a pie chart

Now that we are a bit familiar with the classes and properties used for a line chart, it would be fairly simple to understand the pie chart; the properties and class name suffixes are very similar.

Let’s create a pie chart to display sector distribution for a user’s portfolio, where each sector is represented using a different color code.

Flutter Piechart

Let’s take a look at the code:

class PieChartWidget extends StatelessWidget {
 final List<Sector> sectors;

 const PieChartWidget(this.sectors, {Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
 return AspectRatio(
     aspectRatio: 1.0,
     child: PieChart(PieChartData(
       sectionsSpace: 2.0,
       sections: _chartSections(sectors),
       centerSpaceRadius: 48.0,
     )));
}

 List<PieChartSectionData> _chartSections(List<Sector> sectors) {
   final List<PieChartSectionData> list = [];
   for (var sector in sectors) {
     const double radius = 40.0;
     final data = PieChartSectionData(
       color: sector.color,
       value: sector.value,
       radius: radius,
       title: '',
     );
     list.add(data);
   }
   return list;
 }
}

We have used a PieChart widget to create the pie chart; this widget takes PieChartData as an argument to define how the pie chart would look. We have used sectionsSpace to modify the gap between adjacent filled sections in the pie chart.

To make the pie chart hollow from the center, we have set centerSpaceRadius to 48.0, which is 0.0 by default. sections property takes a list of PieChartSectionData to define how each section in the pie chart will look. PieChartSectionData provides control over the values and representation of individual sections of the pie.

If no value for title is provided for PieChartSectionData, then it displays the section value by default, so don’t forget to add an empty string for title if you do not wish to display anything over the pie chart.

Other graph options

Apart from the most commonly used graph options that we discussed above, this powerful library also provides you with some really interesting graph types that are worth exploring. Let’s take a quick look at them as well.

Scatter chart

Scatter Chart

This allows you to plot a number of points anywhere on the graph by specifying the x and y coordinates along with a radius and color. The most amazing part about this chart is the animations that you can play with while transitioning from one state to another.

Radar chart

Radar Chart

This allows you to create a two-dimensional graphical representation from a set of three or more data points. You can use RadarDataSet that takes a list of RawDataSet as dataEntries to draw multiple radar charts in the same graphical space.

Animations with FL Chart

One thing that makes this package stand apart from other chart libraries are the beautiful animations and the control that you can have over animations for each of the charts. When you change the chart’s state, it animates to the new state internally (using implicit animations). You can control the animation duration and curve using optional swapAnimationDuration and swapAnimationCurve properties, respectively. You can also change the chart state based on user interactions by leveraging the <FooChart>TouchData class that is available for all the chart options and can be really helpful in creating beautiful user interactions like the ones displayed below.

Conclusion

We learned how to draw the most widely used charts but this package is way more powerful than this and supports more complex charts like a bar chart, radar chart and even a scatter chart. If you wish to explore it further, do check out the Flutter package here.

Thanks for sticking around, happy coding fella! You can access the sample app used in the article here on GitHub.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Bhavya Mishra Bhavya wanted to be a writer, so she writes code now. Bhavya’s career has been focused on all things Android for the past five years. Drawn to Fueled by the team culture and the cutting edge technologies we use, she doesn’t plan on stopping anytime soon!

Leave a Reply