Aiman_Irfan
Aiman_Irfan

Reputation: 375

How to implement API data in a line chart

I am trying to implement my API data in a chart using fl_chart dependencies in flutter. But I just cannot figure out how to implement it.

Here is how I implement my data:

@override
Widget build(BuildContext context) {
  return ListView.builder(
    padding: EdgeInsets.zero,
    shrinkWrap: true,
    scrollDirection: Axis.vertical,
    physics: NeverScrollableScrollPhysics(),
    itemCount: 1,
    itemBuilder: (context, index){
    // ignore: unused_local_variable
    int number = index + 1;
      return Container(
        width: MediaQuery.of(context).size.width * 0.50,
        child: LineChart(
          LineChartData(
            gridData: FlGridData(
              show: true,
              drawVerticalLine: true,
              getDrawingHorizontalLine: (value) {
                return FlLine(
                  color: const Color(0xff37434d),
                  strokeWidth: 1,
                );
              },
              getDrawingVerticalLine: (value) {
                return FlLine(
                  color: const Color(0xff37434d),
                  strokeWidth: 1,
                );
              },
            ),
            titlesData: FlTitlesData(
              show: true,
              bottomTitles: SideTitles(
                showTitles: true,
                reservedSize: 22,
                getTextStyles: (value) =>
                    const TextStyle(color: Color(0xff68737d), fontWeight: FontWeight.bold, fontSize: 16),
                getTitles: (value) {
                  switch (value.toInt()) {
                    case 2:
                      return 'MAR';
                    case 5:
                      return 'JUN';
                    case 8:
                      return 'SEP';
                  }
                  return '';
                },
                margin: 8,
              ),
              leftTitles: SideTitles(
                showTitles: true,
                getTextStyles: (value) => const TextStyle(
                  color: Color(0xff67727d),
                  fontWeight: FontWeight.bold,
                  fontSize: 15,
                ),
                getTitles: (value) {
                  switch (value.toInt()) {
                    case 1:
                      return '10k';
                    case 3:
                      return '30k';
                    case 5:
                      return '50k';
                  }
                  return '';
                },
                reservedSize: 28,
                margin: 12,
              ),
            ),
            borderData:
                FlBorderData(show: true, border: Border.all(color: const Color(0xff37434d), width: 1)),
            minX: 0,
            maxX: 11,
            minY: 0,
            maxY: 6,
            lineBarsData: [
              LineChartBarData(
                spots: [
                  FlSpot(0 , pings[number.toString()][index].volume),
                  FlSpot(2.6, 2),
                  FlSpot(4.9, 5),
                  FlSpot(6.8, 3.1),
                  FlSpot(8, 4),
                  FlSpot(9.5, 3),
                  FlSpot(11, 4),
                ],
                isCurved: true,
                colors: gradientColors,
                barWidth: 5,
                isStrokeCapRound: true,
                dotData: FlDotData(
                  show: true,
                ),
                belowBarData: BarAreaData(
                  show: true,
                  colors: gradientColors.map((color) => color.withOpacity(0.3)).toList(),
                ),
              ),
            ],
          )

And here is how i call my data:

Map<String, List<TankPing>> pings;

   initState() {
    Services.fetchPing().then((tankPings) => {
      setState((){
        pings = tankPings;
      })
    });
    super.initState();
  }

My API call is in another file. I call the API like below:

static Future<Map<String, List<TankPing>>> fetchPing() async {
    String url3 = 'https://api.orbital.katsana.com/devices/graph-data';
    Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
    final SharedPreferences prefs = await _prefs;
    final token = prefs.getString('access_token');
    final response3 = await http.get(url3, headers: {
      'Authorization': 'Bearer $token'
    });

    if(response3.statusCode == 200) {
      final tankPings = tankPingFromJson(response3.body);
      return tankPings;
    }else if(response3.statusCode == 400) {
      print('Connection to server is bad');
    }else if(response3.statusCode == 500){
      print('No authorization');
    }
  }

I am trying to implement it inside of FlSPot() function. But then U receive this error:

The method '[]' was called on null.
Receiver: null
Tried calling: []("1")

Here is my model:

import 'dart:convert';

Map<String, List<TankPing>> tankPingFromJson(dynamic str) => Map.from(json.decode(str)).map((k, v) => MapEntry<String, List<TankPing>>(k, List<TankPing>.from(v.map((x) => TankPing.fromJson(x)))));

String tankPingToJson(Map<String, List<TankPing>> data) => json.encode(Map.from(data).map((k, v) => MapEntry<String, dynamic>(k, List<dynamic>.from(v.map((x) => x.toJson())))));

class TankPing {
    TankPing({
        this.trackedAt,
        this.fuel,
        this.level,
        this.volume,
    });

    DateTime trackedAt;
    double fuel;
    double level;
    double volume;

    factory TankPing.fromJson(Map<String, dynamic> json) => TankPing(
        trackedAt: DateTime.parse(json["tracked_at"]),
        fuel: json["fuel"].toDouble(),
        level: json["level"].toDouble(),
        volume: json["volume"].toDouble(),
    );

    Map<String, dynamic> toJson() => {
        "tracked_at": trackedAt.toString(),
        "fuel": fuel,
        "level": level,
        "volume": volume,
    };
}

Here is how the API look:

{
    "1": [
        {
            "tracked_at": "2020-11-20T19:41:21.000000Z",
            "fuel": 87.03,
            "level": 3.0460554,
            "volume": 50665.14
        },
        {
            "tracked_at": "2020-11-22T00:19:41.000000Z",
            "fuel": 85.75,
            "level": 3.0012249,
            "volume": 50051.86
        },
        {
            "tracked_at": "2020-11-22T00:32:00.000000Z",
            "fuel": 84.17,
            "level": 2.9460489,
            "volume": 49265.04
        },
]

My API is very long and it looks like that. Any help would be appreciated.

Upvotes: 3

Views: 2357

Answers (2)

Aiman_Irfan
Aiman_Irfan

Reputation: 375

I just post the code example in here. If you have any question, you can ask me and I will try to answer the question I can because this code is like almost 2 or 3 years old now and I did not work on this project anymore. Hope the code below helps you!

import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:intl/intl.dart';

import 'custom_symbol_renderer.dart';
import 'package:orbital_app/Model/tank_ping.dart';
import 'package:orbital_app/Provider/api_provider.dart';

class TankChart extends StatefulWidget {
  //This is my API class object to extract the data
  TankChart({Key key}) : super(key: key);
  @override
  _TankChartState createState() => _TankChartState();
}

class _TankChartState extends State<TankChart> {
  var ping;
  var tankInfo;
 
  // Since I am using a Provider in this code, I call the API here
  getPingProvider(){
    setState((){
      ping = Provider.of<TankPingProvider>(context, listen: false);
      ping.getTankPing(context);
    });
  }

  getInfoProvider(){
    setState((){
      tankInfo = Provider.of<TankInfoProvider>(context, listen: false);
      tankInfo.getTankInfo(context);
    });
  }

  @override
  initState() {
    super.initState();
    getPingProvider();
    getInfoProvider();
  } 

  @override
  Widget build(BuildContext context) {
    
    // Here I format the time to normal human time
    final numericFormatter = charts.BasicNumericTickFormatterSpec.fromNumberFormat(
      NumberFormat.compact()
    );
    final ping = Provider.of<TankPingProvider>(context);
    return  ListView.builder(
      padding: EdgeInsets.zero,

      // Here I want everything to be shrink and expand when the user needs it
      shrinkWrap: true,

      // Here is where I set whether the graph can be expand by user vertical 
      // scroll
      physics: NeverScrollableScrollPhysics(),

      //The data from the API is here
      itemCount: ping.tankPing.length,
      itemBuilder: (context, index){
        if(ping.tankPing.length == null){
          return CircularProgressIndicator();
        } else if(ping.tankPing == null){
          return  CircularProgressIndicator();
        } else{
          int no = index + 1;
          final size = MediaQuery.of(context).size;
          
          // Here is the API dot or data dot on the graph
          List<charts.Series<TankPing, DateTime>> series = [
              charts.Series(
                id: '${tankInfo.tankInfos.data[index].name}',
                data: ping.tankPing[no.toString()],
                colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
                domainFn: (TankPing ping, _) => ping.trackedAt,
                measureFn: (TankPing ping, _) => ping.volume
              ),
            ];

            return Container(
              height: 250,
              child: Card(
                child: Column(
                  children: [
                    Expanded(
                      child: Padding(
                        padding: const EdgeInsets.only(
                          left: 5
                        ),
                        child: charts.TimeSeriesChart(
                          series,
                          animate: false,
                          domainAxis: charts.DateTimeAxisSpec(
                            tickFormatterSpec: charts.AutoDateTimeTickFormatterSpec(
                              day: charts.TimeFormatterSpec(
                                format: 'dd',
                                transitionFormat: 'dd MMM',
                              ),
                            ),
                          ),
                          primaryMeasureAxis: charts.NumericAxisSpec(
                            tickFormatterSpec: numericFormatter,
                            renderSpec: charts.GridlineRendererSpec(
                              // Tick and Label styling here.
                              labelStyle: charts.TextStyleSpec(
                                fontSize: 10, // size in Pts.
                                color: charts.MaterialPalette.black
                              ),
                            )
                          ),
                          defaultRenderer: charts.LineRendererConfig(
                            includeArea: true,
                            includeLine: true,
                            includePoints: true,
                            strokeWidthPx: 0.5,
                            radiusPx: 1.5
                          ),
                          dateTimeFactory: const charts.LocalDateTimeFactory(),
                          behaviors: [
                            charts.SlidingViewport(),
                            charts.PanAndZoomBehavior(),
                            charts.SeriesLegend(
                              position: charts.BehaviorPosition.top,
                              horizontalFirst: false,
                              cellPadding: EdgeInsets.only(
                                left: MediaQuery.of(context).size.width * 0.27, 
                                top: 15
                              ),
                            ),
                            charts.SelectNearest(
                              eventTrigger: charts.SelectionTrigger.tap
                            ),
                            charts.LinePointHighlighter(
                              symbolRenderer: CustomCircleSymbolRenderer(size: size),
                            ),
                          ],
                          selectionModels: [
                            charts.SelectionModelConfig(
                            type: charts.SelectionModelType.info,
                            changedListener: (charts.SelectionModel model) {
                              if(model.hasDatumSelection) {
                                final tankVolumeValue = model.selectedSeries[0].measureFn(model.selectedDatum[0].index).round();
                                final dateValue = model.selectedSeries[0].domainFn(model.selectedDatum[0].index);
                                CustomCircleSymbolRenderer.value = '$dateValue \n $tankVolumeValue L';
                              }
                            })
                          ]),
                      ),
                      ),
                    ],
                  ),
              ),
            );
          }
        });
  }
}

Upvotes: 1

Aiman_Irfan
Aiman_Irfan

Reputation: 375

The answer is use the min and max value to determine how long the data will be. And then just use the flSpot to enter your data.

Upvotes: 0

Related Questions