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

10 min read 2872

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. FL Chart provides widgets for creating highly customizable line, bar, pie, scatter, and radar charts.

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.

Contents

Prerequisites

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

  • The Flutter SDK
  • A code editor; you can use VS Code, Android Studio, or any code editor of your choosing
  • At least beginner-level knowledge of Flutter

That’s pretty much it!

Setup

In this tutorial, we’ll use a pre-developed example app to demonstrate various charts and the chart package’s features. To follow along, download or clone the example app from GitHub.

Enter the following command to install dependencies:

flutter pub get

Next, run your app with flutter run to make sure that everything works fine. Once you run the app, you will see three charts, as shown in the following preview:

Flutter Charts Demo app
Flutter charts demo app.

Let’s take a look at how to create and customize these charts.

Creating a line chart

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

The LineChart 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
  ),
  swapAnimationDuration: Duration(milliseconds: 150), // Optional
  swapAnimationCurve: Curves.linear, // Optional
);

Now, let’s add a basic line to the chart to get started. Here’s the source code of the line_chart_widget.dart file:

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

import 'package:flutter_chart_demo/data/price_point.dart';

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

  const LineChartWidget(this.points, {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,
                // dotData: FlDotData(
                //   show: false,
                // ),
              ),
            ],
          ),
      ),
    );
  }
}

LineChatData contains all the information on how the line chart will look. We’ve used the lineBarsData property that takes a list of LineChartBarData to draw one or more lines on the graph. We ensured the line will not be curved by setting isCurved to false.

The above example will look something like this:

Flutter Chart Implementation
Flutter chart implementation.

LineChartBarData is used to define how the individual lines will look. It takes a list of spots that are similar to the plot points for a line graph. By default, these points will be represented with filled circle markers, but we can control their appearance by using dotData.

Try removing the above source code’s commented code lines; you’ll see the line chart without markers, as shown below:

Flutter Line Chart without Markers
Flutter line chart without markers.

Let’s take a look at the code:

return AspectRatio(
  aspectRatio: 2,
  child: LineChart(
    LineChartData(
        lineBarsData: [
          LineChartBarData(
            spots: points.map((point) => FlSpot(point.x, point.y)).toList(),
            isCurved: false,
            dotData: FlDotData(
              show: false,
            ),
          ),
        ],
      ),
  ),
);

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

Now, let’s add some horizontal labels, clear the unnecessary clutter from this graph, and make it a bit cleaner, as shown in the following preview:

Flutter Line Chart with Clean Design
Flutter line chart with clean design.

The code for the sample looks like this:


More great articles from LogRocket:


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

import 'package:flutter_chart_demo/data/price_point.dart';

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

  const LineChartWidget(this.points, {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,
                dotData: FlDotData(
                  show: false,
                ),
                color: Colors.red
              ),
            ],
            borderData: FlBorderData(
                border: const Border(bottom: BorderSide(), left: BorderSide())),
            gridData: FlGridData(show: false),
            titlesData: FlTitlesData(
              bottomTitles: AxisTitles(sideTitles: _bottomTitles),
              leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
              topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
              rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
            ),
          ),
      ),
    );
  }

  SideTitles get _bottomTitles => SideTitles(
    showTitles: true,
    getTitlesWidget: (value, meta) {
      String text = '';
      switch (value.toInt()) {
        case 1:
          text = 'Jan';
          break;
        case 3:
          text = 'Mar';
          break;
        case 5:
          text = 'May';
          break;
        case 7:
          text = 'Jul';
          break;
        case 9:
          text = 'Sep';
          break;
        case 11:
          text = 'Nov';
          break;
      }

      return Text(text);
    },
  );
}

Customizing the tooltip

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 location on the line chart. However, we can modify the tooltip to display whatever text we want and we can also style it differently.

We’re using LineTouchData which provides a bunch of properties like touchCallback, touchTooltipData, and even getTouchedSpotIndicator to modify the appearance of the touch indicator and tooltip.

We can use touchTooltipData to customize the default tooltip and getTouchedSpotIndicator to customize the touch event feedback in the rendered chart area.

Check out the following preview:

Flutter Line Chart with Customized Tooltip
Flutter line chart with customized tooltip.

We can implement the above chart by adding the following parameter data to the LineChartData widget.

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.toStringAsFixed(2),
              textStyle,
            );
          },
        ).toList();
      },
    ),
    getTouchedSpotIndicator:
        (LineChartBarData barData, List<int> indicators) {
      return indicators.map(
        (int index) {
          final line = FlLine(
              color: Colors.grey,
              strokeWidth: 1,
              dashArray: [2, 4]);
          return TouchedSpotIndicatorData(
            line,
            FlDotData(show: false),
          );
        },
      ).toList();
    },
    getTouchLineEnd: (_, __) => double.infinity
  ),

Here we customized the tooltip, but the library determines when to show a particular tooltip. For example, we need to tap and hold to get a tooltip for a line edge. This library is so flexible that it lets you handle when to show a particular tooltip.

Creating a toggleable tooltip

We can toggle tooltips as follows:

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

import 'package:flutter_chart_demo/data/price_point.dart';

class LineChartWidget extends StatefulWidget {
  const LineChartWidget({Key? key, required this.points}) : super(key: key);

  final List<PricePoint> points;

  @override
  State<LineChartWidget> createState() => _LineChartWidgetState(points: this.points);
}

class _LineChartWidgetState extends State<LineChartWidget> {
  final List<PricePoint> points;
  late int showingTooltipSpot;

  _LineChartWidgetState({required this.points});

  @override
  void initState() {
    showingTooltipSpot = -1;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final _lineBarsData = [
      LineChartBarData(
        spots: points.map((point) => FlSpot(point.x, point.y)).toList(),
        isCurved: false,
        dotData: FlDotData(
          show: false,
        ),
        color: Colors.red
      ),
    ];
    return AspectRatio(
      aspectRatio: 2,
      child: LineChart(
        LineChartData(
            lineBarsData: _lineBarsData,
            borderData: FlBorderData(
                border: const Border(bottom: BorderSide(), left: BorderSide())),
            gridData: FlGridData(show: false),
            titlesData: FlTitlesData(
              bottomTitles: AxisTitles(sideTitles: _bottomTitles),
              leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
              topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
              rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
            ),
            showingTooltipIndicators: showingTooltipSpot != -1 ? [ShowingTooltipIndicators([
                LineBarSpot(_lineBarsData[0], showingTooltipSpot,
                    _lineBarsData[0].spots[showingTooltipSpot]),
              ])] : [],
            lineTouchData: LineTouchData(
                enabled: true,
                touchTooltipData: LineTouchTooltipData(
                  tooltipBgColor: Colors.blue,
                  tooltipRoundedRadius: 20.0,
                  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.toStringAsFixed(2),
                          textStyle,
                        );
                      },
                    ).toList();
                  },
                ),
                handleBuiltInTouches: false,
                touchCallback: (event, response) {
                  if (response?.lineBarSpots != null && event is FlTapUpEvent) {
                    setState(() {
                      final spotIndex = response?.lineBarSpots?[0].spotIndex ?? -1;
                      if(spotIndex == showingTooltipSpot) {
                        showingTooltipSpot = -1;
                      }
                      else {
                        showingTooltipSpot = spotIndex;
                      }
                    });
                  }
                },
              ),
          ),
      ),
    );
  }

  SideTitles get _bottomTitles => SideTitles(
    showTitles: true,
    getTitlesWidget: (value, meta) {
      String text = '';
      switch (value.toInt()) {
        case 1:
          text = 'Jan';
          break;
        case 3:
          text = 'Mar';
          break;
        case 5:
          text = 'May';
          break;
        case 7:
          text = 'Jul';
          break;
        case 9:
          text = 'Sep';
          break;
        case 11:
          text = 'Nov';
          break;
      }

      return Text(text);
    },
  );
}

Next, we need to use the named parameter points from main.dart :

//....
children: <Widget>[
  LineChartWidget(points: pricePoints),
//....

Here we implemented toggleable tooltips with the following modifications to the previous example code:

  • Made LineChartWidget stateful to hold information about the tooltip that is visible currently
  • Turned off the inbuilt tooltip handling feature by setting handleBuiltInTouches to false
  • Stored details about the touched line index in showingTooltipSpot by implementing a function for touchCallback
  • Showed tooltips conditionally with showingTooltipIndicators

Run the above code, to see toggleable tooltips as shown below:

Flutter Line Chart with Toggleable Tooltip
Flutter line chart with toggleable tooltip.

Similarly, we can implement toggleable tooltips for any supported chart type.

Wasn’t that simple? Now let’s move on to the next most popular chart – the pie chart.

Creating a bar chart

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

Let’s create a bar chart using the same data set generator used for the line chart.

Look at the source code in the bar_chart_widget.dart file:

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

import 'package:flutter_chart_demo/data/price_point.dart';

class BarChartWidget extends StatefulWidget {
  const BarChartWidget({Key? key, required this.points}) : super(key: key);

  final List<PricePoint> points;

  @override
  State<BarChartWidget> createState() => _BarChartWidgetState(points: this.points);
}

class _BarChartWidgetState extends State<BarChartWidget> {
  final List<PricePoint> points;

  _BarChartWidgetState({required this.points});

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 2,
      child: BarChart(
        BarChartData(
            barGroups: _chartGroups(),
            borderData: FlBorderData(
                border: const Border(bottom: BorderSide(), left: BorderSide())),
            gridData: FlGridData(show: false),
            titlesData: FlTitlesData(
              bottomTitles: AxisTitles(sideTitles: _bottomTitles),
              leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
              topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
              rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
            ),
          ),
      ),
    );
  }

  List<BarChartGroupData> _chartGroups() {
    return points.map((point) =>
      BarChartGroupData(
        x: point.x.toInt(),
        barRods: [
          BarChartRodData(
            toY: point.y
          )
        ]
      )

    ).toList();
  }

  SideTitles get _bottomTitles => SideTitles(
    showTitles: true,
    getTitlesWidget: (value, meta) {
      String text = '';
      switch (value.toInt()) {
        case 0:
          text = 'Jan';
          break;
        case 2:
          text = 'Mar';
          break;
        case 4:
          text = 'May';
          break;
        case 6:
          text = 'Jul';
          break;
        case 8:
          text = 'Sep';
          break;
        case 10:
          text = 'Nov';
          break;
      }

      return Text(text);
    },
  );
}

Here we created a bar chart by providing a list of BarChartGroupData instances via the barGroups parameter. Similar to line chart titles, the above code uses the titlesData parameter. We made this widget stateful since we will extend this widget source to update chart data dynamically.

Once you run the above code, you’ll see the bar chart, as shown in the following preview:

Flutter Bar Chart
Flutter bar chart.

How to create a negative bar chart

In some scenarios, it’s necessary to depict negative bar chart segments. Let’s update the above code to include negative y values too!

First, update the _chartGroups method as follows to include negative y values:

List<BarChartGroupData> _chartGroups() {
  return points.map((point) {
    final double y = (Random().nextBool() ? 1 : -1) * point.y;
    return BarChartGroupData(
      x: point.x.toInt(),
      barRods: [
        BarChartRodData(
          toY: y,
          color: y > 0 ? Colors.blue : Colors.red,
        )
      ]
    );
  }
  ).toList();
}

Make sure to import Dart math package too:

import 'dart:math';

Comment out the following line from the titlesData setup to display labels on the left side of the chart:

leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),

You will get a multicolored bar chart with both positive and negative y values:

Flutter Bar Chart with Positive and Negative Rods
Flutter bar chart with both positive and negative rods.

Here, the chart renders positive bars in blue and negative value bars in red. You can include negative values in line charts as well.

Updating chart data in Flutter

In Flutter, we typically make stateful widgets if we need to perform dynamic updates. But, how do we update chart data?

We can indeed make stateful chart widgets and update chart datasets dynamically with the setState method. Then, the FL Chart library will render updated graphical elements like any other Flutter widget.

Let’s update the previous bar chart periodically with different chart data. In the previous bar chart, we generated a random sign (+ or -) with Random().nextBool() within the build method, so that y values are updated during each widget render. So, we can simply call setState(() {}) to update the chart.

Add the following method implementation to BarChartWidget:

@override
initState() {
  Timer.periodic(const Duration(seconds: 1), (timer) {
    setState((){});
  });
  super.initState();
}

Also, be sure to import the async package:

import 'dart:async';

Once you run the project you’ll see periodically updated data on the bar chart:

Updating Flutter Bar Chart Data with setState
Updating Flutter bar chart data with setState.

Similarly, it’s possible to update any chart data source with setState and a stateful widget. Also, you can implement depict live data on charts with Dart timers, as I demonstrated in the above example.

Creating a pie chart

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 Pie Chart
Flutter pie chart.

Here’s a look at the pie_chart_widget.dart file:

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chart_demo/data/sector.dart';

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(
          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.

To make the pie chart hollow from the center, we have set centerSpaceRadius to 48.0. 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 the title is provided for PieChartSectionData, then it displays the section value by default. So don’t forget to add an empty string for the 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

ScatterChart allows us to plot several points anywhere on the graph by specifying the x and y coordinates along with a radius and color. The most amazing aspect of this chart is the animations that we can play with while transitioning from one state to another.

You can browse sample scatter chart implementations from the official documentation.

Radar chart

Radar Chart

RadarChart allows us to create a two-dimensional graphical representation from a set of three or more data points. We can use RadarDataSet which takes a list of R``adarEntries as dataEntries to draw multiple radar charts in the same graphical space.

You can browse sample radar chart implementations from the official documentation.

Animations with FL Chart

One thing that makes this package stand apart from other chart libraries is the beautiful animations and the control that you can have over animations for each of the charts.

When we change the chart’s state, it animates to the new state internally (using implicit animations). We can control the animation duration and curve using optional swapAnimationDuration and swapAnimationCurveproperties, respectively. We can also change the chart state based on user interactions by leveraging the <FooChart>TouchData class. This class is available for all chart options and can be really helpful in creating beautiful user interactions like the ones displayed below.

Bar chart touch interactions

Bar chart touch interactions

Pie chart touch interactions

Pie chart touch interactions

Conclusion

This article demonstrated how to draw the most widely used charts using the FL Chart package in Flutter. But, FL Chart is way more powerful than this and supports more complex charts like scatter charts and radar charts, as well as animations. If you wish to explore it further, check out the Flutter package here.

Thanks for sticking around, happy coding!

: Full visibility into your web and mobile 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 and mobile 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!

One Reply to “Build beautiful charts in Flutter with FL Chart”

Leave a Reply