imaNNeo / fl_chart

FL Chart is a highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart.
https://flchart.dev
MIT License
6.78k stars 1.75k forks source link

x-Axis based on DateTime object and dots #438

Open xOldeVx opened 4 years ago

xOldeVx commented 4 years ago

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.

jlubeck commented 4 years ago

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.

imaNNeo commented 4 years ago

@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 commented 4 years ago

@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.

enwi commented 3 years ago

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

xOldeVx commented 3 years ago

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

fvisticot commented 3 years ago

@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

xOldeVx commented 3 years ago

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

emerysilb commented 2 years ago

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!

Monolite commented 2 years ago

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));
Foluwa commented 2 years ago

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

Monolite commented 2 years ago

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

Foluwa commented 2 years ago

Thank you @Monolite

Bonsoh commented 1 year ago

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

shitake4 commented 1 year ago

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

abuzarbasit commented 1 year ago

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

xOldeVx commented 1 year ago

@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());
  }
}
Trung15010802 commented 7 months ago

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

kevinsullivan commented 4 months ago

Perhaps maintainer community could indicate what the blockers are for this one? Seems a particularly discouraging gap in critical functionality. Is there a roadmap? Is there any way forward, or is this just a dead end due to some pervasive architectural assumption, or some such thing? Thank you for any information that the mainatiner community can provide. I wouldn't know where to start.

felipecosta-dev commented 1 month ago

are there some guidelines to achieve a good looking timeseries graph?