Custom styled Bar Chart with Flutter using charts_flutter
Today I'm going to show you can customize your bar chart a bit using the chart_flutter package. I show you how you can style bars and labels, taking inspiration from series The Witcher.
There are several libraries for crating charts in Flutter. One of them is charts_flutter, currently version 0.12.0 with null-safety available on pub.dev repository. This library allows us to create many kinds of charts that you can view in their online gallery.
The problem with charts_flutter
is a slight lack of documentation on how to adapt the charts to our projects. π₯ Β
Today I'm going to show you how you can do it and style bars and labels, taking inspiration from my favorite series The Witcher.
Step. 0 β pre-config
In order for our chart to be modeled on the The Witcher series, we need at least a special font (and maybe two swords π) that we will be using. You can google it and download it from any source, I used that.
Then we need to load and install our font in the project:
- Create an
assets
folder with afonts
subfolder in the root directory - Download the font package and move
.ttf
file to theassets/fonts
folder - In
pubspec.yaml
, uncomment theasset
key and add thefonts
path:
assets:
- assets/fonts/
- For the
fonts
key, also add the path to the font and give it a name that you will use later in the rest of the code:
fonts:
- family: thewitcher
fonts:
- asset: assets/fonts/Thewitcher-jnOj.ttf
Step. 1 β Scaffolding
Now that the pre-configuration is complete, we can import and call our bar chart at the place we choose (main.dart
in my case). We do this with sample data CustomBarChart.withSampleData ()
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
const Text(
'The Witcher bar chart',
style: TextStyle(
fontFamily: 'thewitcher',
fontSize: 18,
),
),
WitcherChart.withSampleData(),
],
),
),
),
);
}
}
Step 2 - Sample data
To feed the bar chart, we will need to create our sample data, and for this we will need a simple class:
class BarData {
final int coins;
final int monster;
const BarData(this.coins, this.monster);
}
Then create a list variable of type BarData
with any numbers you want (important! First value - coins must start at 1 and increase by 1):
static final data = <BarData>[
const BarData(1, 11),
const BarData(2, 14),
const BarData(3, 8),
const BarData(4, 10),
const BarData(5, 15),
const BarData(6, 1),
const BarData(7, 9),
const BarData(8, 9),
const BarData(8, 3),
const BarData(10, 12),
const BarData(11, 18),
const BarData(12, 8),
const BarData(13, 3),
const BarData(14, 4),
const BarData(15, 10),
const BarData(16, 12),
const BarData(17, 10),
const BarData(18, 8),
const BarData(19, 15),
const BarData(20, 12),
];
Finally, add a private method called _createSampleData
and return the Series
list of there (this type provides the package):
static List<Series<BarData, String>> _createSampleData() {
return [
Series(
id: 'Data',
data: data,
domainFn: (BarData data, _) => data.coins.toString(),
measureFn: (BarData data, _) => data.monster,
),
];
}
Last but not least, add a factory that returns data and that we previously called in main.dart
:
factory WitcherChart.withSampleData() => WitcherChart(_createSampleData());
With this we can create a basic chart π
Step. 3 β Basic bar chart
To do that is quite simple, in the witcher_chart.dart
we need to return the BarChart
class in build method and passed previously created :
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(15.0),
child: SizedBox(
height: 200,
child: BarChart(
seriesList,
animate: true,
),
),
);
}
Be sure to add the seriesList
variable to the top of the chart class:
class WitcherChart extends StatelessWidget {
final List<Series<dynamic, String>> seriesList;
This should generate a simple graph with our default data.
Step. 4 β Custom styles
Now our chart looks the default, but we can go ahead and adjust the domainAxis
(x-axis) and primaryMeasureAxis
(y-axis) labels independently.
So letβs start with the x-axis.
In the BarChart
class (it can be under the animate
property) we add the domainAxis
property with the following values:
domainAxis: OrdinalAxisSpec(
tickProviderSpec: StaticOrdinalTickProviderSpec(xStaticLabels),
renderSpec: SmallTickRendererSpec(
labelStyle: TextStyleSpec(
lineHeight: 3,
fontSize: 11,
color: Color.fromHex(code: '#4d80e4'),
),
lineStyle: const LineStyleSpec(
color: Color.transparent,
),
),
),
In the above code, we can manage the label text style in the labelStyle
property, and we can also change the color (or hide) of the first bottom line of the chart - this is the value 0 - in the lineStyle
property.
In the tickProviderSpec
property we can also pass a staticTicks
list where we can even change the style of each label. To do this, create a list of xStaticLabels
with labels:
static final xStaticLabels = <TickSpec<String>>[
// Possible to define style of labels directly inside
const TickSpec(
"1",
label: "1 monster",
style: TextStyleSpec(
fontFamily: <ANY_OTHER_FONT>,
),
),
const TickSpec(
"10",
label: "10 monsters",
style: TextStyleSpec(
fontFamily: 'thewitcher',
),
),
TickSpec(
"20",
label: "20 monsters",
style: TextStyleSpec(
fontFamily: 'thewitcher',
color: ColorUtil.fromDartColor(Colors.orangeAccent),
),
),
];
We can also round the corners of our bar by adding this line:
defaultRenderer: BarRendererConfig(
cornerStrategy: const ConstCornerStrategy(<YOUR_VALUE>),
);
Okay, we can style the y-axis (primaryMeasureAxis
) similarly:
primaryMeasureAxis: NumericAxisSpec(
tickFormatterSpec: BasicNumericTickFormatterSpec(
(num? value) => "$value \u{1F4B0}"),
tickProviderSpec: StaticNumericTickProviderSpec(yStaticLabels),
renderSpec: GridlineRendererSpec(
labelStyle: TextStyleSpec(
fontSize: 14,
color: Color.fromHex(code: '#f8b739'),
),
labelAnchor: TickLabelAnchor.after,
),
),
The difference is that tickFormatterSpec
accept a function, so we can return a value from our yStaticLabels
and also add e.g. Unicode (coin for The Witcher π) at the end using string interpolation.
As with the xStaticLabels
list, create a yStaticLabels
list and pass it in the StaticNumericTickProviderSpec
class:
static final yStaticLabels = <TickSpec<int>>[
// Possible to define style of labels directly inside
const TickSpec(
5,
style: TextStyleSpec(
fontFamily: <ANY_OTHER_FONT>,
),
),
const TickSpec(
10,
style: TextStyleSpec(
fontFamily: 'thewitcher',
),
),
const TickSpec(
15,
style: TextStyleSpec(
fontFamily: 'thewitcher',
),
),
TickSpec(
20,
style: TextStyleSpec(
fontFamily: 'thewitcher',
color: ColorUtil.fromDartColor(Colors.greenAccent),
),
),
];
Step. 5 β BONUS feature
As an additional feature, we will select the lowest and highest value of the chart and change their color. It's pretty simple, just add these two lines to the _createSampleData
method:
final highestValue = data.reduce((curr, next) => curr.monster > next.monster ? curr : next);
final smallestValue = data.reduce((curr, next) => curr.monster < next.monster ? curr : next);
And in the _createSampleData
method in the return statement, extend the existing code with the following colorFn
property:
return [
Series(
id: 'Data',
data: data,
domainFn: (BarData data, _) => data.coins.toString(),
measureFn: (BarData data, _) => data.monster,
colorFn: (data, _) {
if (data.monster == highestValue.monster) {
return Color.fromHex(code: '#2d3561');
}
if (data.monster == smallestValue.monster) {
return Color.fromHex(code: '#ff0000');
}
return Color.fromHex(code: '#af0404');
},
),
];
Summary π
In this tutorial, I showed you how you can customize your bar chart a bit using the chart_flutter
package. To learn more about this package's capabilities, check it out on pub.dev.
Thanks for reading β₯οΈβ₯οΈ
If this article was helpful, please leave a comment or π
The full source code for this tutorial can be found here:
