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 and Canvas 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 the maxValue from the data points passed in. This will be used to determine our barHeight in a moment. For a particular list of numerical data (i.e. [1, 2, 3, 4, 5] ), firstly, determine the barHeight from a 0-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 next Rect 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