3

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.

2 Answers 2

1

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';
                              }
                            })
                          ]),
                      ),
                      ),
                    ],
                  ),
              ),
            );
          }
        });
  }
}
Sign up to request clarification or add additional context in comments.

Comments

0

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.

10 Comments

How do you get the titles you want in your chart ?
Sorry I forgot about that. Now I have change my chart from FL Chart to chart_flutter
@Aiman_Irfan can you provide your sample code of how you implemented the data using chart_flutter... i have to show linear graph and data is coming from api and even i used FL Chart but i'm not able to plot the graph so i you don't mind can you share your code?
Hi Rahul, sorry this is an old project that I was working on. And I have change my code to use charts_flutter
@Aiman_Irfan which one was better ? Fl_charts or chart_Flutter?which one should i go for?can you share charts_flutter code for reference if possible?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.