Stock charts with fl_chart library
Tags: flutter, dart, charts, stock
Published: 2020-01-02 21:34:10

There is no better way to present data than a pie chart as we saw in this [Dilbert comic strip](http://dilbert.com/strip/2009-03-07).

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](https://pub.dev/packages/fl_chart) 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](https://thepracticaldev.s3.amazonaws.com/i/3cd67f80gv7lf1s408o0.png)

## 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](https://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`: 
```yaml
...
flutter:
  assets:
    - assets/data.json
...
```

### Load data
My `data.json` file contains an array/list of items that look like this:
```json
[
  {
    "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:
```dart
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:
```dart
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](https://flutter.dev/docs/development/data-and-backend/json) 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:
```yaml
...
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.
```dart
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.
```dart
LineChart(_mainData());
```

Nothing interesting here, right? The real fun starts with `LineChartData` object.
```dart
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](https://github.com/imaNNeoFighT/fl_chart/blob/master/repo_files/documentations/line_chart.md) 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.
```dart
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.
```dart
 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:
```dart
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.
```dart
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.
```dart
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](https://github.com/kamilpowalowski/fluttersafari/tree/fl_chart) repository on Github.