DEV Community

Cover image for Stock charts with fl_chart library
Kamil Powałowski
Kamil Powałowski

Posted on

Stock charts with fl_chart library

There is no better way to present data than a pie chart as we saw in this Dilbert comic strip.

But sometimes you want to display values that changed over time. More suitable for these tasks are the line and bar charts. It doesn’t matter which type of chart you want to use in your Flutter mobile app - all of them you can create using fl_chart Package made by Iman Khoshabi - imaNNeoFighT.

Challange

To get used to fl_chart library I created a challenge for myself - display stock data using Flutter. The chart should look pretty and I should be able to read some information by just looking at the chart. Here is an effect of my work:
Apple's 2018 stock price chart generated using fl_chart library

Solution

Get data

The first problem that I was forced to solve was a way to get data for this challenge. Most of the stock charts' data APIs are paid and required registration. After some research, I found about eodhistoricaldata.com. They provide example endpoints with fixed user token for testing purposes. To my excitation, sample data was for AAPL (Apple) stock data on NASDAQ. Using https://eodhistoricaldata.com/api/eod/AAPL.US?from=2018-01-01&to=2018-12-31&api_token=OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX&period=d&fmt=json URL call I was able to get Apple stock prices for whole 2018 year. To make things easier for this challenge I saved the result of this call as data.json in assets directory and added it to project in pubspec.yaml:

...
flutter:
  assets:
    - assets/data.json
...

Load data

My data.json file contains an array/list of items that look like this:

[
  {
    "date": "2018-01-02",
    "open": 170.16,
    "high": 172.3,
    "low": 169.26,
    "close": 172.26,
    "adjusted_close": 167.1997,
    "volume": 25555934
  },
  ...
]

To use them I’ve created datum.dart model with content:

class Datum {
  Datum({this.date, this.close});

  final DateTime date;
  final double close;

  Datum.fromJson(Map<String, dynamic> json)
      : date = DateTime.parse(json['date']),
        close = json['close'].toDouble();
}

As you can see I took only two values that I’ll need for displaying the chart - date of entry and close price. Then I prepared the code to load this stock values to memory:

import 'dart:async';
import 'dart:convert' show jsonDecode;
import 'package:flutter/services.dart' show rootBundle;

import 'package:fluttersafari/datum.dart';

Future<List<Datum>> loadStockData() async {
  final String fileContent = await rootBundle.loadString('assets/data.json');
  final List<dynamic> data = jsonDecode(fileContent);
  return data.map((json) => Datum.fromJson(json)).toList();
}

For this simple project jsonDecode function available in dart:convert package is sufficient but for bigger projects, I would recommend json_serializable as explained in JSON and serialization article of Flutter documentation.

Add library

To use fl_chart I added the library to the project. This required another edit of pubspec.yaml file:

...
dependencies:
  flutter:
    sdk: flutter
  fl_chart: ^0.6.0
...

And calling flutter pub get after it.

Prepare data

Before I'll pass our data to fl_chart I have to transfer it to form understand by this library. Axis based charts are expecting a list of FlSpot objects that will represent points of interest on our chart. Each FlSpot object contains two double values (x and y). My X axis will display time and Y will show a close price.

final List<Datum> data = await loadStockData();

final List<FlSpot> values = data
  .map((datum) => FlSpot(datum.date.millisecondsSinceEpoch.toDouble(), datum.close))
  .toList();

setState(() {
  _values = values;
});

I've used millisecondsSinceEpoch to get an integer number for each data point. Full solution (attached at the end of blogpost) requires to provide minX, maxX, minY, maxY values which present lowest and highest numbers (with additional offset if needed) limiting chart in both directions.

Display chart

Now, when I have data ready to display, it's time to create a LineChart widget.

LineChart(_mainData());

Nothing interesting here, right? The real fun starts with LineChartData object.

LineChartData _mainData() {
  return LineChartData(
    gridData: _gridData(),
    titlesData: FlTitlesData(
      bottomTitles: _bottomTitles(),
      leftTitles: _leftTitles(),
    ),
    lineBarsData: [_lineBarData()],
  );
}

We can control each aspect of LineChart widget. From the look of the grid, color and shape of chart border to labels on any side of the cart to most important - lines that visualize our data. All available parameters can be inspected on the documentation page.

Line and background

Now let's focus on some elements starting from crème de la crème - the _lineBarData() function that will provide stock data information for LineChartData widget.

LineChartBarData _lineBarData() {
  return LineChartBarData(
    spots: _values,
    colors: _gradientColors,
    colorStops: const [0.25, 0.5, 0.75],
    gradientFrom: const Offset(0.5, 0),
    gradientTo: const Offset(0.5, 1),
    barWidth: 2,
    isStrokeCapRound: true,
    dotData: const FlDotData(show: false),
    belowBarData: BarAreaData(
      show: true,
      colors: _gradientColors.map((color) => color.withOpacity(0.3)).toList(),
      gradientColorStops: const [0.25, 0.5, 0.75],
      gradientFrom: const Offset(0.5, 0),
      gradientTo: const Offset(0.5, 1),
    ),
  );
}

This may look scary to you at the begging but despite providing transformed values in spots: _values I'm setting just a few parameters that will give this chart these awesome gradient colors. Please take a look at belowBarData property. Using this field I can control how the area below chart line looks. I just set it to three gradient colors stored in the _gradientColors variable.

 final List<Color> _gradientColors = [
    const Color(0xFF6FFF7C),
    const Color(0xFF0087FF),
    const Color(0xFF5620FF),
  ];

Labels

displaying left and bottom labels may be a bit tricky at first. Let's take a look at this code:

SideTitles _bottomTitles() {
  return SideTitles(
    showTitles: true,
    textStyle: TextStyle(
      color: Colors.white54,
      fontSize: 14,
    ),
    getTitles: (value) {
      final DateTime date =
          DateTime.fromMillisecondsSinceEpoch(value.toInt());
      return DateFormat.MMM().format(date);
    },
    margin: 8,
    interval: (_maxX - _minX) / 6,
  );
}

For each data point, LineChart will call getTitles function (which in this case converts data back to DateTime object and returns short month name) and tries to display it below the chart. To not clutter chart with data, I used the interval parameter to limit how often getTitles should be called (I decided to display only 6 labels below the chart).
A similar solution is used for _leftTitles() but their interval calculations are more complicated so I leave them to check in full example available at the end of the blogpost.

SideTitles _leftTitles() {
  return SideTitles(
    showTitles: true,
    textStyle: TextStyle(
      color: Colors.white54,
      fontSize: 14,
    ),
    getTitles: (value) =>
        NumberFormat.compactCurrency(symbol: '\$').format(value),
    reservedSize: 28,
    margin: 12,
    interval: _leftTitlesInterval,
  );
}

Grid

Last, but not least - we can control the way the grid is displayed.

FlGridData _gridData() {
  return FlGridData(
    show: true,
    drawVerticalLine: false,
    getDrawingHorizontalLine: (value) {
      return const FlLine(
        color: Colors.white12,
        strokeWidth: 1,
      );
    },
    checkToShowHorizontalLine: (value) {
      return (value - _minY) % _leftTitlesInterval == 0;
    },
  );
}

An optional checkToShowHorizontalLine function will tell which horizontal lines should be displayed. To fit grid lines to horizontal values, I've used logic related to a previously computed _leftTitlesInterval variable (to be inspected in the full code repository).

Summary

Making eye-appealing charts usually required two very different steps. We have to prepare data first and then load it on the user interface. The first one is fully on you. But for UI, you can use a fl_chart or different library that can help you with this task. I hope that my challenge will motivate you for own experiments with this awesome library.
For full implementation of this project go to Flutter Safari repository on Github.

Top comments (1)

Collapse
 
muhammed_afthad profile image
Muhammed Afthad

That horizontal check with value not working with all values