How to Create a Line Chart in Flutter using fl_chart

Line charts are useful for visualizing data, they make it easy to observe patterns and changes over time. In this tutorial, we will learn how to create line charts in Flutter using the fl_chart package. We will cover the installation process, setting up the necessary widgets, and customizing the chart’s appearance. We will also explore touch interactions, grid lines, titles, and borders. By the end, you will be able to create line charts that look professional and effectively visualize data in your Flutter applications.

This will be the line chart that we are creating:

Installing the package

To get started, we have to make sure that we have the fl_chart package installed in our Flutter project. We can install the package by executing the following command: flutter pub add fl_chart.

After installing the package make sure that the package is underneath the dependencies inside your pubspec.yaml file:

dependencies:
  fl_chart: ^0.63.0
  flutter:
    sdk: flutter

Once the package is installed, we can start creating our line chart.

Creating the Line Chart

To keep our code organized and easy to manage, we will create a separate file called line_chart_widget.dart in the lib directory. This file will contain the necessary components to build our line chart.

Inside this file, we will define two widgets: the private _LineChart widget and the LineChartWidget itself. The _LineChart widget contains all the functionalities needed for the line chart, while the LineChartWidget serves as a wrapper that returns the _LineChart widget.

Let us start by creating a private _LineChart widget with the following code:

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

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

  @override
  Widget build(BuildContext context) {
    return LineChart(lineChartData);
  }

  LineChartData get lineChartData => LineChartData();

  LineTouchData lineTouchData() => LineTouchData();

  FlGridData gridData() => FlGridData();

  FlTitlesData titlesData() => FlTitlesData();

  FlBorderData borderData() => FlBorderData();

  List<LineChartBarData> lineBarsData() => [];
}

In this code snippet, we created a private _LineChart widget, the widget returns a LineChart widget from the fl_chart package. The LineChart widget requires a set of data called LineChartData. Inside the LineChartWidget, we will create a getter called lineChartData that returns the necessary data for the line chart. Other than that we will have a lot of helper functions that will be called by our getter to create the necessary data.

Let us continue by writing the code for the lineChartData getter, see the code below:

LineChartData get lineChartData => LineChartData(
  lineTouchData: lineTouchData(),
  gridData: gridData(),
  titlesData: titlesData(),
  borderData: borderData(),
  lineBarsData: lineBarsData(),
  minX: 0,
  maxX: 6,
  minY: 0,
  maxY: 6,
);

The LineChartData has a lot of parameters we need to provide. Let us go through each parameter to understand what they do:

  1. lineTouchData: This parameter is responsible for configuring the touch interactions for the line chart. It defines how the chart behaves when the user interacts with it, such as tapping or dragging.
  2. gridData: This parameter is used to customize the grid lines of the chart. It allows us to specify the appearance of horizontal and vertical grid lines and set properties like color and visibility.
  3. titlesData: This parameter is used to configure the titles and labels of the chart. It includes properties like the chart title, axis titles, and labels for the data points.
  4. borderData: This parameter allows us to set the appearance of the chart’s border. We can specify properties like the color, width, and border radius to customize the border of the chart.
  5. lineBarsData: This parameter is used to provide the data for the line bars in the chart. It includes information about the lines to be displayed, such as their colors, widths, and the data points they represent.
  6. minX: This parameter defines the minimum value on the X-axis (horizontal axis) of the chart. It specifies the starting point of the X-axis.
  7. maxX: This parameter defines the maximum value on the X-axis (horizontal axis) of the chart. It specifies the ending point of the X-axis.
  8. minY: This parameter defines the minimum value on the Y-axis (vertical axis) of the chart. It determines the lowest point on the Y-axis.
  9. maxY: This parameter defines the maximum value on the Y-axis (vertical axis) of the chart. It determines the highest point on the Y-axis.

By adjusting these settings, we can customize different parts of the line chart, like how it looks, the data points displayed, how users can interact with it, and the range of the axes.

Now that we have our getter function finished let us write the code for each helper function.

1. lineTouchData

For the lineTouchData function we want to return LineTouchData with the handleBuiltInTouches parameter set to true, see the code below:

LineTouchData lineTouchData() => LineTouchData(
  handleBuiltInTouches: true,
);

Setting the handleBuiltInTouches property to true will make sure that we get indicators when we are tapping on our chart:

2. gridData

The gridData function returns an instance of the FlGridData class. The FlGridData class is responsible for customizing the grid lines of our line chart, see the following code:

FlGridData gridData() => FlGridData(
  show: true,
  drawVerticalLine: true,
  horizontalInterval: 1,
  verticalInterval: 1,
  getDrawingHorizontalLine: (value) => FlLine(
    color: Colors.white.withOpacity(0.2),
    strokeWidth: 1,
  ),
  getDrawingVerticalLine: (value) => FlLine(
    color: Colors.white.withOpacity(0.2),
    strokeWidth: 1,
  ),
);

Inside the FlGridData function, we set the following properties:

  • show: which is set to true, indicating that the grid lines should be visible.
  • drawVerticalLine: which is set to true, specifying that vertical grid lines should be drawn.
  • horizontalInterval: which is set to 1, defining the interval between each horizontal grid line.
  • verticalInterval: which is set to 1, determining the interval between each vertical grid line.

Additionally, for the getDrawingHorizontalLine and getDrawingVerticalLine properties we defined two functions. These functions are used to customize the appearance of the grid lines.

In the getDrawingHorizontalLine property, we create and return an instance of the FlLine class, which represents a horizontal grid line. We set the color to Colors.white.withOpacity(0.2) and the strokeWidth to 1, defining the color and thickness of the line.

For getDrawingVerticalLine, we do apply the same logic but of course, this will be applied on the vertical grid lines.

3. titlesData

The titlesData function returns an instance of FlTitlesData. This object is responsible for configuring the titles and labels in the line chart.

FlTitlesData titlesData() => FlTitlesData(
  bottomTitles: AxisTitles(
    sideTitles: bottomTitles(),
  ),
  rightTitles: AxisTitles(
    sideTitles: SideTitles(showTitles: false),
  ),
  topTitles: AxisTitles(
    sideTitles: SideTitles(showTitles: false),
  ),
  leftTitles: AxisTitles(
    sideTitles: leftTitles(),
  ),
);

Inside the returned FlTitlesData instance, we set up the following properties:

  • bottomTitles: Configures the titles for the bottom axis. We specify the sideTitles property and call the bottomTitles() function to get the configuration for the bottom titles.
  • rightTitles and topTitles: Both are configured to not show any titles, by setting the sideTitles property of the SideTitles to false.
  • leftTitles: Configures the titles for the left axis. We specify the sideTitles property and call the leftTitles function to get the configuration for the left titles.

bottomTitles

The bottomTitles function returns an instance of SideTitles that configures the bottom titles.

SideTitles bottomTitles() => SideTitles(
  getTitlesWidget: bottomTitleWidgets,
  interval: 1,
  reservedSize: 32,
  showTitles: true,
);

In this code snippet, we provided the following properties:

  • showTitles: Set to true to show the titles.
  • reservedSize: Specifies the space reserved for the titles, which is 32 in this case.
  • interval: Defines the interval between each title, which is set to 1.
  • getTitlesWidget: Refers to the bottomTitleWidgets method, which returns a widget based on the value and meta information.

bottomTitleWidgets

The bottomTitleWidgets function has the following parameters value and meta and returns a widget for the bottom titles.

SideTitleWidget bottomTitleWidgets(double value, TitleMeta meta) {
  String text = switch (value.toInt()) {
    0 => 'Mon',
    1 => 'Tue',
    2 => 'Wed',
    3 => 'Thu',
    4 => 'Fri',
    5 => 'Sat',
    6 => 'Sun',
    _ => '',
  };

  return SideTitleWidget(
    axisSide: meta.axisSide,
    space: 10,
    child: Text(text, style: TextStyle(
        fontWeight: FontWeight.bold,
        fontSize: 16,
        color: Colors.white,
      ),
    ),
  );
}

Inside this function, we use a switch expression to determine the appropriate text for each value. For example, when value is 0, it returns the string “Mon”. The text will be used inside a Text widget we assign to the child property in our SideTitleWidget. Furthermore inside the SideTitleWidget we set the following properties:

  • axisSide: Specifies the side of the axis where the title should be displayed.
  • space: Defines the spacing between the title and the axis.
  • child: Defines the content of the title, which in this case is a Text widget. The text variable is passed as the text content of the Text widget.

Switch expressions are introduced in Dart 3, if you want to learn more about them make sure to check out my related post: Difference between Switch Statements and Expressions in Dart

leftTitles

The leftTitles function returns an instance of SideTitles that configures the left titles. It sets the following properties:

SideTitles leftTitles() => SideTitles(
  getTitlesWidget: leftTitleWidgets,
  interval: 1,
  reservedSize: 40,
  showTitles: true,
);

In this code snippet, we provided the following properties:

  • getTitlesWidget: Refers to the leftTitleWidgets method, which returns a widget based on the value and meta information.
  • interval: Defines the interval between each title, which is set to 1.
  • reservedSize: Specifies the space reserved for the titles, which is 40 in this case.
  • showTitles: Sets to true to show the titles.

leftTitleWidgets

The leftTitleWidgets function takes in a value and meta and returns a widget for the left titles.

Text leftTitleWidgets(double value, TitleMeta meta) {
  String text = switch (value.toInt()) {
    1 => '5',
    2 => '10',
    3 => '15',
    4 => '20',
    5 => '25',
    6 => '30',
    7 => '35',
    _ => '',
  };

  return Text(
    text,
    textAlign: TextAlign.center,
    style: TextStyle(
        fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white),
  );
}

The leftTitleWidgets function also uses a switch case expression, it determines the appropriate text for each value and returns it. If the value does not match any case, it returns an empty string. The function itself returns a Text widget with the appropriate text and given style.

4. borderData

The borderData function returns a FlBorderData object. The FlBorderData is responsible for configuring the border appearance of a chart.

FlBorderData borderData() => FlBorderData(
  show: true,
  border: Border(
    bottom: BorderSide(
        color: const Color(0xFF50E4FF).withOpacity(0.2), width: 4),
    left: const BorderSide(color: Colors.transparent),
    right: const BorderSide(color: Colors.transparent),
    top: const BorderSide(color: Colors.transparent),
  ),
);

The properties used in this code snippet are:

  • show: Specifies whether to show the border.
  • border: Defines the border properties using the Border class. In this case, the bottom border is set with a color of semi-transparent light blue (Color(0xFF50E4FF).withOpacity(0.2)) and a width of 4. The left, right, and top borders are set to be transparent (Colors.transparent).

5. lineBarsData

The lineBarsData function returns two LineChartBarData objects.

List<LineChartBarData> lineBarsData() => [
  lineChartBarDataCurrentWeek(),
  lineChartBarDataPreviousWeek(),
];

These objects represent the line chart bars data for the current week and the previous week. Both LineChartBarData objects are created by functions.

LineChartBarData lineChartBarDataCurrentWeek() => LineChartBarData(
  isCurved: true,
  curveSmoothness: 0,
  color: const Color(0xFF50E4FF),
  barWidth: 2,
  isStrokeCapRound: true,
  dotData: FlDotData(show: true),
  belowBarData: BarAreaData(show: false),
  spots: const [
    FlSpot(0, 1),
    FlSpot(1, 0),
    FlSpot(2, 2),
    FlSpot(3, 2),
    FlSpot(4, 3),
    FlSpot(5, 1),
    FlSpot(6, 0),
  ],
);

The lineChartBarDataCurrentWeek function defines the properties for the line chart bars of the current week.

  • isCurved: Determines whether the line is curved.
  • curveSmoothness: Specifies the smoothness of the curve.
  • color: Sets the color of the line.
  • barWidth: Defines the width of the bar.
  • isStrokeCapRound: Determines if the stroke has a round cap.
  • dotData: Specifies the data for dots on the line.
  • belowBarData: Defines the data below the line.
  • spots: Represents the spots (points) on the line with their respective coordinates. Where the first value is X-Axis and the second the Y-Axis.
LineChartBarData lineChartBarDataPreviousWeek() => LineChartBarData(
  isCurved: true,
  curveSmoothness: 0,
  color: Colors.deepOrangeAccent.withOpacity(0.8),
  barWidth: 2,
  isStrokeCapRound: true,
  dotData: FlDotData(show: true),
  belowBarData: BarAreaData(show: false),
  spots: const [
    FlSpot(0, 0),
    FlSpot(1, 1),
    FlSpot(2, 2),
    FlSpot(3, 4),
    FlSpot(4, 5),
    FlSpot(5, 0),
    FlSpot(6, 1),
  ],
);

Similarly, the lineChartBarDataPreviousWeek function defines the properties for the line chart bars of the previous week. It has similar properties to lineChartBarDataCurrentWeek but with a different color and of course, the spots have different data.

Now that we have completed the _LineChart widget, we can start using it.

Creating the LineChartWidget

In our line_chart_widget.dart file, we can finally create our LineChartWidget. This widget extends a StatelessWidget.

class LineChartWidget extends StatelessWidget {
  const LineChartWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return _LineChart();
  }
}

Initially, the LineChartWidget simply returns the _LineChart widget. Now we can access the widget inside our main.dart` file:

import 'package:codeonwards_demo/line_chart_widget.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Line Chart demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Line Chart demo'),
        ),
        body: Center(child: LineChartWidget()),
      ),
    );
  }
}

Inside our main.dart file we return a MaterialApp widget. That has a Scaffold widget set as home, with an AppBar. The Scaffold displays our LineChartWidget.

When we run our main.dart file we will end up with the following result:

Right now we can only see our data, this is because most of our elements are white.

Wrapping the _LineChart widget

We can wrap the _LineChart widget with a Container widget and give it a blue color. This will act as a background color for our line chart. This will also make the white elements appear.

class LineChartWidget extends StatelessWidget {
  const LineChartWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: _LineChart()
    );
  }
}

As you can see this already improved our line chart by a lot, but we are still not there:

Adding a Title and Making the Line Chart Fit the Screen

Next, we want to include a line chart title at the top and ensure that the LineChartWidget remains inside of the screen.

class LineChartWidget extends StatelessWidget {
  const LineChartWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: Column(
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 10),
            child: Text(
              'New words per day',
              style: TextStyle(
                color: Colors.white,
                fontSize: 16,
                fontWeight: FontWeight.bold,
                letterSpacing: 2,
              ),
              textAlign: TextAlign.center,
            ),
          ),
          Expanded(
            child: Padding(
              padding: EdgeInsets.only(right: 25, left: 2.5, bottom: 10),
              child: _LineChart(),
            ),
          ),
        ],
      ),
    );
  }
}

We achieve this by placing the title and the chart inside a Column widget. The title is added using a Text widget with customized styling and the chart is wrapped with an Expanded widget to take up the available space.

Now our line chart has an appropriate title and fit in our screen:

Scaling Down the LineChartWidget

To complete the design, we can use the AspectRatio widget to scale down the LineChartWidget.

class LineChartWidget extends StatelessWidget {
  const LineChartWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: AspectRatio(
        aspectRatio: 1.25,
        child: Column( ... ),
      ),
    );
  }
}

In this code snippet, we used the AspectRatio widget and set its aspectRatio property to 1.25. With this approach, we maintain the chart’s proportions while making it fit within the available space.

Final Code

In case you ran into trouble while following the tutorial this is the final code:

line_chart_widget.dart

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

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

  @override
  Widget build(BuildContext context) {
    return LineChart(lineChartData);
  }

  LineChartData get lineChartData => LineChartData(
    lineTouchData: lineTouchData(),
    gridData: gridData(),
    titlesData: titlesData(),
    borderData: borderData(),
    lineBarsData: lineBarsData(),
    minX: 0,
    maxX: 6,
    minY: 0,
    maxY: 6,
  );

  LineTouchData lineTouchData() => LineTouchData(
    handleBuiltInTouches: true,
  );

  FlGridData gridData() => FlGridData(
    show: true,
    drawVerticalLine: true,
    horizontalInterval: 1,
    verticalInterval: 1,
    getDrawingHorizontalLine: (value) => FlLine(
      color: Colors.white.withOpacity(0.2),
      strokeWidth: 1,
    ),
    getDrawingVerticalLine: (value) => FlLine(
      color: Colors.white.withOpacity(0.2),
      strokeWidth: 1,
    ),
  );

  FlTitlesData titlesData() => FlTitlesData(
    bottomTitles: AxisTitles(
      sideTitles: bottomTitles(),
    ),
    rightTitles: AxisTitles(
      sideTitles: SideTitles(showTitles: false),
    ),
    topTitles: AxisTitles(
      sideTitles: SideTitles(showTitles: false),
    ),
    leftTitles: AxisTitles(
      sideTitles: leftTitles(),
    ),
  );

  SideTitles bottomTitles() => SideTitles(
    getTitlesWidget: bottomTitleWidgets,
    interval: 1,
    reservedSize: 32,
    showTitles: true,
  );

  SideTitleWidget bottomTitleWidgets(double value, TitleMeta meta) {
    String text = switch (value.toInt()) {
      0 => 'Mon',
      1 => 'Tue',
      2 => 'Wed',
      3 => 'Thu',
      4 => 'Fri',
      5 => 'Sat',
      6 => 'Sun',
      _ => '',
    };

    return SideTitleWidget(
      axisSide: meta.axisSide,
      space: 10,
      child: Text(text, style: TextStyle(
        fontWeight: FontWeight.bold,
        fontSize: 16,
        color: Colors.white,
      ),
      ),
    );
  }

  SideTitles leftTitles() => SideTitles(
    getTitlesWidget: leftTitleWidgets,
    interval: 1,
    reservedSize: 40,
    showTitles: true,
  );

  Text leftTitleWidgets(double value, TitleMeta meta) {
    String text = switch (value.toInt()) {
      1 => '5',
      2 => '10',
      3 => '15',
      4 => '20',
      5 => '25',
      6 => '30',
      7 => '35',
      _ => '',
    };

    return Text(
      text,
      textAlign: TextAlign.center,
      style: TextStyle(
          fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white),
    );
  }

  FlBorderData borderData() => FlBorderData(
    show: true,
    border: Border(
      bottom: BorderSide(
          color: const Color(0xFF50E4FF).withOpacity(0.2), width: 4),
      left: const BorderSide(color: Colors.transparent),
      right: const BorderSide(color: Colors.transparent),
      top: const BorderSide(color: Colors.transparent),
    ),
  );

  List<LineChartBarData> lineBarsData() => [
    lineChartBarDataCurrentWeek(),
    lineChartBarDataPreviousWeek(),
  ];

  LineChartBarData lineChartBarDataCurrentWeek() => LineChartBarData(
    isCurved: true,
    curveSmoothness: 0,
    color: const Color(0xFF50E4FF),
    barWidth: 2,
    isStrokeCapRound: true,
    dotData: FlDotData(show: true),
    belowBarData: BarAreaData(show: false),
    spots: const [
      FlSpot(0, 1),
      FlSpot(1, 0),
      FlSpot(2, 2),
      FlSpot(3, 2),
      FlSpot(4, 3),
      FlSpot(5, 1),
      FlSpot(6, 0),
    ],
  );

  LineChartBarData lineChartBarDataPreviousWeek() => LineChartBarData(
    isCurved: true,
    curveSmoothness: 0,
    color: Colors.deepOrangeAccent.withOpacity(0.8),
    barWidth: 2,
    isStrokeCapRound: true,
    dotData: FlDotData(show: true),
    belowBarData: BarAreaData(show: false),
    spots: const [
      FlSpot(0, 0),
      FlSpot(1, 1),
      FlSpot(2, 2),
      FlSpot(3, 4),
      FlSpot(4, 5),
      FlSpot(5, 0),
      FlSpot(6, 1),
    ],
  );
}

class LineChartWidget extends StatelessWidget {
  const LineChartWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: AspectRatio(
        aspectRatio: 1.25,
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 10),
              child: Text(
                'New words per day',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                  letterSpacing: 2,
                ),
                textAlign: TextAlign.center,
              ),
            ),
            Expanded(
              child: Padding(
                padding: EdgeInsets.only(right: 25, left: 2.5, bottom: 10),
                child: _LineChart(),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

main.dart

import 'package:codeonwards_demo/line_chart_widget.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Line Chart demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Line Chart demo'),
        ),
        body: Center(child: LineChartWidget()),
      ),
    );
  }
}

Conclusion

In this tutorial, we learned how to create a line chart in Flutter using the fl_chart package. We installed the package and set it up in our project. Then, we created a separate file to define the components of the line chart. Inside the _LineChart widget, we set up the chart and provided the necessary data. We also added functions to configure touch interactions, grid lines, titles, and borders. We created line bars data for the current week and previous week. Finally, we created the LineChartWidget to wrap the chart and make it look better. By following this tutorial, we were able to create a functional and visually appealing line chart for our Flutter application.

Tijn van den Eijnde
Tijn van den Eijnde
Articles: 87

Leave a Reply

Your email address will not be published. Required fields are marked *