How to Create a Line Chart in Flutter using fl_chart
Line charts are a 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 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 the 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:
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.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.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.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.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.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.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.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.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 totrue
, indicating that the grid lines should be visible.drawVerticalLine
: which is set totrue
, specifying that vertical grid lines should be drawn.horizontalInterval
: which is set to1
, defining the interval between each horizontal grid line.verticalInterval
: which is set to1
, 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 thesideTitles
property and call thebottomTitles()
function to get the configuration for the bottom titles.rightTitles
andtopTitles
: Both are configured to not show any titles, by setting thesideTitles
property of theSideTitles
tofalse
.leftTitles
: Configures the titles for the left axis. We specify thesideTitles
property and call theleftTitles
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 totrue
to show the titles.reservedSize
: Specifies the space reserved for the titles, which is32
in this case.interval
: Defines the interval between each title, which is set to1
.getTitlesWidget
: Refers to thebottomTitleWidgets
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 aText
widget. Thetext
variable is passed as the text content of theText
widget.
Switch expressions are introduced in Dart 3, if you want to learn more about them make sure to check out my related post:

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 theleftTitleWidgets
method, which returns a widget based on the value and meta information.interval
: Defines the interval between each title, which is set to1
.reservedSize
: Specifies the space reserved for the titles, which is40
in this case.showTitles
: Sets totrue
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 theBorder
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 of4
. 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 ensuring fit in 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.