arrow-left arrow-right brightness-2 chevron-left chevron-right facebook-box facebook loader magnify menu-down rss-box star twitter-box youtube-box twitter white-balance-sunny window-close
UI Design: Sparkline Bar Chart in Flutter (YouTube Studio)
4 min read

UI Design: Sparkline Bar Chart in Flutter (YouTube Studio)

UI Design: Sparkline Bar Chart in Flutter (YouTube Studio)

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:

  1. 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.
  2. For a particular list of numerical data (i.e. [1, 2, 3, 4, 5]), firstly, determine the barHeight from a 0-height scale.
  3. Then we're drawing a Rect on screen with our specified coordinates.
  4. 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