fl_chart icon indicating copy to clipboard operation
fl_chart copied to clipboard

x-Axis based on DateTime object and dots

Open xOldeVx opened this issue 3 years ago • 18 comments

Firstly i must to say that a worked with a lot of Flutter's chart library, and this is the best i found!

I have a problem with two lines on the same graph, and after a lot of checks i found that the x-Axis is based only on indexs, and can't be based on DateTime! in my case it's really a big problem, because i have 2 lines to show on the graph, one based on quarter of an hour, and the second on minute, in this case there is no match between the 2 lines and the x-Axis. As you can see on the orange text, the km/h and the date is right, but the location on the graph is not bug

Google charts is support DateTime graph, in Time Series Charts https://google.github.io/charts/flutter/gallery.html You can also see, for example, the Weekly Chart the x-Axis is based on DateTime object https://pub.dev/packages/bezier_chart

One more important thing is the barWidth, if is set to 0 the line is still appear, it could be good and right that 0 value make the line transparent, and stay only the Dots to appear. In my case it's help to see a dangerous wind that more than 50km/h (in the image attached i just drawed the red points in photoshop for the example, that why it's on 20kmh~) 50kmh

Hope you will fix and support this because it's really critical, and add values to your awesome graph.

xOldeVx avatar Sep 04 '20 12:09 xOldeVx

I'm not sure if my issue is the same or not, but definitely related. I currently have a lot of points that want plotted, but the bottom labels are getting on top of one another. It would be nice to get the labels to automatically adapt based on the values given.

This is my current chart:

image

Even though I want all the dots, I don't want all the labels at the bottom.

jlubeck avatar Sep 21 '20 21:09 jlubeck

@xOldeVx I didn't understand the first problem, and I think you can explain it simpler, And about the second problem (lines with 0 barWidth), you are right but It is better to make a separate issue to follow it up separately.

And a reproducible code helps me a lot.

Thanks!

imaNNeo avatar Sep 25 '20 18:09 imaNNeo

@jlubeck You can handle it using the interval property of your SideTitle. Or alternatively you can override getTitles and return empty string wherever you don't want to show a specific title.

imaNNeo avatar Sep 25 '20 18:09 imaNNeo

Just as a note this issue relates to #444 and #462 and also +1 for this 👍

enwi avatar Jan 28 '21 18:01 enwi

As you can see in the image, in the same point of time (7:30), on the red line is on 21:55 and on the blue is 7:30, if the x-axis was based according to DateTime object, (e.g. FlSpot(DateTime(2017, 9, 19), 25.0) || FlSpot(1612416600000, 25.0) instead of FlSpot(3, 25.0)) it was not happened. 92237965-f339bb80-eec0-11ea-8ea3-e3fa7e4693eb

As you can see in google charts, 2 kinds of line chart, one of time: https://google.github.io/charts/flutter/example/time_series_charts/simple) and one of index, like yours https://google.github.io/charts/flutter/example/line_charts/simple

xOldeVx avatar Feb 04 '21 12:02 xOldeVx

@jlubeck You can handle it using the interval property of your SideTitle. Or alternatively you can override getTitles and return empty string wherever you don't want to show a specific title.

Can you please detail how to use interval property. I have tried to set a value (15 by example) and the application is looping ans stop the UI My objective is to display a time serie with by example 3 points (9h32, 12:47, 18:29) but display bottom title every 30 minutes

fvisticot avatar Mar 29 '21 18:03 fvisticot

As you can see in the image, in the same point of time (7:30), on the red line is on 21:55 and on the blue is 7:30, if the x-axis was based according to DateTime object, (e.g. FlSpot(DateTime(2017, 9, 19), 25.0) || FlSpot(1612416600000, 25.0) instead of FlSpot(3, 25.0)) it was not happened. 92237965-f339bb80-eec0-11ea-8ea3-e3fa7e4693eb

As you can see in google charts, 2 kinds of line chart, one of time: https://google.github.io/charts/flutter/example/time_series_charts/simple) and one of index, like yours https://google.github.io/charts/flutter/example/line_charts/simple

Any news? that very important

xOldeVx avatar May 09 '21 07:05 xOldeVx

I'm looking to do timeseries charts as well and FL Charts seems to be by far and away the best charting library, but it's impossible to have x-axis and hover labels with timestamp data (although the data charts just fine). The Google chart does support timestamps, but it's pretty ugly and you can't change that. I would love to see more thoughts on timeseries data in fl_chart.

Cheers!

emerysilb avatar Dec 05 '21 19:12 emerysilb

I converted my DateTime to miliseconds epoch and works fine.

//Convert to FLSpot
List<charts.FlSpot> toTimeSeriesFlSpots(TimeSeriesChartData chartData) {
    if (chartData.serie?.isEmpty ?? true) return <charts.FlSpot>[];

    final spots =
        chartData.serie!.map((e) => charts.FlSpot(e.label.millisecondsSinceEpoch.toDouble(), e.value ?? 0)).toList();

    return spots;
  }

// Bulid titles
charts.FlTitlesData getDefaultTimeSeriesTitlesData(double? domainInterval,
      {TextStyle? titlesTheme, double? codomainInterval}) {
return charts.FlTitlesData(
        show: true,
        bottomTitles: charts.SideTitles(
            showTitles: true,
            getTextStyles: (context, value) => titlesTheme,
            rotateAngle: 270,
            reservedSize: 50,
            getTitles: getDomainTimeSeriesTitles,
            interval: domainInterval));

Monolite avatar Mar 01 '22 23:03 Monolite

@Monolite How did you calculate the domain intervals on your getDefaultTimeSeriesTitlesData function?

Foluwa avatar Mar 24 '22 16:03 Foluwa

At the bottomTitles interval, I set the domainInterval value as below:

domainInterval = DaysCount * Duration.millisecondsPerDay;

where DaysCount is the number of days between each title

Monolite avatar Mar 25 '22 03:03 Monolite

Thank you @Monolite

Foluwa avatar Mar 31 '22 12:03 Foluwa

I converted my DateTime to miliseconds epoch and works fine.

//Convert to FLSpot
List<charts.FlSpot> toTimeSeriesFlSpots(TimeSeriesChartData chartData) {
    if (chartData.serie?.isEmpty ?? true) return <charts.FlSpot>[];

    final spots =
        chartData.serie!.map((e) => charts.FlSpot(e.label.millisecondsSinceEpoch.toDouble(), e.value ?? 0)).toList();

    return spots;
  }

// Bulid titles
charts.FlTitlesData getDefaultTimeSeriesTitlesData(double? domainInterval,
      {TextStyle? titlesTheme, double? codomainInterval}) {
return charts.FlTitlesData(
        show: true,
        bottomTitles: charts.SideTitles(
            showTitles: true,
            getTextStyles: (context, value) => titlesTheme,
            rotateAngle: 270,
            reservedSize: 50,
            getTitles: getDomainTimeSeriesTitles,
            interval: domainInterval));

Could you show the full code to use dateTime as x coordinate? I have problems implementing it, especially to use the getTitles like you did with getDomainTimeSeriesTitles

Bonsoh avatar Nov 26 '22 10:11 Bonsoh

In my case, I converted DateTime to milliseconds epoch with a list of 64 spots, and it became Out Of Memory.

shitake4 avatar Jul 31 '23 13:07 shitake4

In my case, I converted DateTime to milliseconds epoch with a list of 64 spots, and it became Out Of Memory.

I am also facing the same issue. Getting out of memory whenover tried to pass date in miliseconds epoch

abuzarbasit avatar Aug 23 '23 12:08 abuzarbasit

@shitake4 @abuzarbasit you getting OOM crash because the interval run on a lot of lines, you not really need all of these lines, you can use an accuracy of one minute, for that convert firstly the date time from milliseconds to seconds, here's a full example.

import 'dart:math';

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;

class NewGraphWidget extends StatefulWidget {
  @override
  State<NewGraphWidget> createState() => _NewGraphWidgetState();
}

class _NewGraphWidgetState extends State<NewGraphWidget> {
  intl.DateFormat hmFormat = intl.DateFormat('Hm');
  double _maxX = 0;
  double _minX = 0;
  double middle = 0;

// convert to second by / 1000 / 60
  final data = [
    FlSpot(1693811005000 / 1000 / 60, 7.59659),
    FlSpot(1693810765000 / 1000 / 60, 8.74324),
    FlSpot(1693810704000 / 1000 / 60, 10.8216),
    FlSpot(1693810645000 / 1000 / 60, 40.8216),
    FlSpot(1693810285000 / 1000 / 60, 6.87993),
    FlSpot(1693810105000 / 1000 / 60, 5.94827),
    FlSpot(1693809804000 / 1000 / 60, 7.02326),
    FlSpot(1693809744000 / 1000 / 60, 7.09493),
    FlSpot(1693809685000 / 1000 / 60, 6.0916),
    FlSpot(1693809625000 / 1000 / 60, 0.15662),
  ];

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    _maxX = double.parse(data.map<double>((e) => (e.x)).reduce(max).toStringAsFixed(0));
    _minX = double.parse(data.map<double>((e) => e.x).reduce(min).toStringAsFixed(0));
    middle = double.parse(data[data.length ~/ 2].x.toStringAsFixed(0)) + 4;
    return LineChart(
      LineChartData(
        maxY: 50,
        minY: 0,
        gridData: FlGridData(show: false),
        borderData: FlBorderData(
          show: true,
          border: Border(
            bottom: BorderSide(color: Colors.white.withOpacity(0.8), width: 2),
            left: BorderSide(color: Colors.white.withOpacity(0.8), width: 2),
            right: const BorderSide(color: Colors.transparent),
            top: const BorderSide(color: Colors.transparent),
          ),
        ),
        titlesData: FlTitlesData(
          bottomTitles: getBottomTitles(),
          topTitles: noTitlesWidget(),
          leftTitles: getLeftTitles(),
          rightTitles: noTitlesWidget(),
        ),
        lineBarsData: [
          LineChartBarData(
            isCurved: true,
            color: Colors.cyan,
            barWidth: 2,
            isStrokeCapRound: true,
            dotData: const FlDotData(show: false),
            spots: data,
          ),
        ],
      ),
    );
  }

  AxisTitles getBottomTitles() {
    return AxisTitles(
      sideTitles: SideTitles(
        interval: 1,
        showTitles: true,
        getTitlesWidget: (value, meta) {
          String text = '';
          if (value == _maxX) {
            text = getDate(data.first.x);
          } else if (double.parse(value.toStringAsFixed(0)) == _minX) {
            text = getDate(data.last.x);
          } else if (value == middle) {
            text = getDate(middle);
          }
          return Text(text);
        },
      ),
    );
  }

  AxisTitles getLeftTitles() {
    return AxisTitles(
      sideTitles: SideTitles(
        showTitles: true,
        interval: 25,
        getTitlesWidget: (value, meta) {
          String text = '';
          switch (value.toString()) {
            case '50.0':
              text = '50';
              break;
            case '25.0':
              text = '25';
              break;
            case '0.0':
              text = '0';
              break;
          }
          return Text(text);
        },
      ),
    );
  }

  String getDate(double value) {
    return hmFormat.format(DateTime.fromMillisecondsSinceEpoch((value * 1000 * 60).toInt()));
  }

  AxisTitles noTitlesWidget() {
    return const AxisTitles(sideTitles: SideTitles());
  }
}

xOldeVx avatar Sep 05 '23 09:09 xOldeVx

any update or sample? i still don't know how to implement this image

Trung15010802 avatar Feb 22 '24 06:02 Trung15010802