In this article we're going to look at how to replicate the Sparkline Bar Chart as seen in the Realtime Views section of the YouTube Studio application. This is quite a common chart and represents datasets at a small scale, allowing you to see results at a glance.
Video
Prefer to watch a video? Here's one I made for this article:
Here's an example of the UI we'll be making in comparison to the YouTube Studio application:
NOTE: I'm still learning how to best take advantage of the
CustomPainter
andCanvas
with Flutter and I'm documenting my learning here. I'd love your feedback!
Project Setup
With that in mind - let's make a new Flutter project in the terminal:
# New Flutter project
$ flutter create flutter_sparkline
# Open in VS Code
$ cd flutter_sparkline && code .
We'll then create a new page at presentation/pages/home_page.dart
named HomePage
:
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Hello, Flutter!"),
),
);
}
}
We can then update our main.dart
accordingly:
import 'package:flutter/material.dart';
import 'package:flutter_sparkline/presentation/pages/home_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sparkline',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
primaryColor: Colors.black,
),
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
Building the Sparkline
In order to build our Sparkline Chart we're going to use the CustomPaint
widget which takes a CustomPainter
which we'll define as SparklinePainter
at lib/presentation/painters/sparkline_painter.dart
:
import 'dart:math';
import 'package:flutter/material.dart';
class SparklinePainter extends CustomPainter {
List<num> data;
Color color;
SparklinePainter({@required this.data, this.color});
@override
void paint(Canvas canvas, Size size) {
num index = 0;
num barSize = size.width / data.length;
num maxValue = data.reduce(max).toDouble();
for (num point in data) {
num barHeight = (size.height * point / maxValue).roundToDouble();
_drawBar(
canvas: canvas,
left: barSize * index,
top: size.height - barHeight,
width: barSize,
height: barHeight,
);
index++;
}
}
_drawBar({Canvas canvas, num left, num top, num width, num height}) {
Paint paint = Paint()
..color = color ?? Colors.red
..style = PaintingStyle.fill
..strokeWidth = 8.0;
Rect rect = Rect.fromLTWH(left, top, width, height);
canvas.drawRect(rect, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
We can break the above down by the following steps:
- We're getting our initial set-up data such as the
barSize
(depending on how many data points there are). We're also getting themaxValue
from thedata
points passed in. This will be used to determine ourbarHeight
in a moment. - For a particular list of numerical data (i.e.
[1, 2, 3, 4, 5]
), firstly, determine thebarHeight
from a0-height
scale. - Then we're drawing a
Rect
on screen with our specified coordinates. - Finally, we're incrementing our
index
in order to move the nextRect
along one.
Now that we've got our SparklinePainter
, we can use this inside of our SparklineChart
widget:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_sparkline/presentation/painters/sparkline_painter.dart';
class SparklineChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 100,
child: CustomPaint(
painter: SparklinePainter(
color: Color(0xFF00a7cf),
data: List.generate(
40,
(index) {
Random rnd = Random();
return rnd.nextInt(20) + 6;
},
),
),
),
);
}
}
This currently generates some fake dummy data using List.generate
, but feel free to swap out the data
with some of your choosing.
YouTube UI
Now all we need to do is update our HomePage
with some crude UI code that represents the top of the YouTube Studio app:
import 'package:flutter/material.dart';
import 'package:flutter_sparkline/sparkline_chart.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 5,
child: Scaffold(
appBar: buildAppBar(),
backgroundColor: Colors.white.withOpacity(0.85),
body: Column(children: [
SizedBox(
height: 8,
),
buildRealtimeSparklineCard(),
]),
),
);
}
Container buildRealtimeSparklineCard() {
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Realtime views",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
SizedBox(
height: 6,
),
Text(
"48 Hours • Estimated views",
style: TextStyle(color: Colors.black54, fontSize: 12),
),
SizedBox(
height: 6,
),
Text("6,500"),
],
),
SparklineChart(),
],
),
),
);
}
AppBar buildAppBar() {
return AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("Analytics", style: TextStyle(color: Colors.black)),
Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(6),
decoration:
BoxDecoration(color: Colors.grey, shape: BoxShape.circle),
child: Text(
"24",
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
SizedBox(
width: 14,
),
CircleAvatar(
maxRadius: 13,
backgroundImage: NetworkImage(
"https://yt3.ggpht.com/a-/AOh14GhB7xAslx6hmlYqG9-NDLyRj8ycHFUn7ypBMXv52Q=s288-c-k-c0xffffffff-no-rj-mo",
),
)
],
)
],
),
centerTitle: false,
brightness: Brightness.light,
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(40.0),
child: Column(
children: <Widget>[
Divider(
height: 2,
),
Container(
margin: EdgeInsets.only(left: 20),
child: TabBar(
isScrollable: true,
indicatorColor: Colors.black,
tabs: [
Tab(
child:
Text("OVERVIEW", style: TextStyle(color: Colors.black)),
),
Tab(
child:
Text("REVENUE", style: TextStyle(color: Colors.black)),
),
Tab(
child: Text("DISCOVERY",
style: TextStyle(color: Colors.black)),
),
Tab(
child:
Text("AUDIENCE", style: TextStyle(color: Colors.black)),
),
Tab(
child: Text("INTERACTIVE CONTENT",
style: TextStyle(color: Colors.black)),
),
],
),
),
],
),
),
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
);
}
}
Here's the final results of our work:
Summary
In this article we looked at how to create a Sparkline chart with the Flutter CustomPainter
. I hadn't used the CustomPainter
or Flutter Canvas
before this, so it was a fun experiment!
I'd love to hear your thoughts on Twitter or in the comments.
Code for this article: https://github.com/PaulHalliday/flutter_sparkline
Top comments (1)
Haven't explored anything with custompainter yet. This seems like a good place to start. Good walkthrough.