Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to show gaps in data? #26

Closed
stx opened this issue Jun 15, 2019 · 12 comments
Closed

Ability to show gaps in data? #26

stx opened this issue Jun 15, 2019 · 12 comments
Labels
enhancement New feature or request Line Chart

Comments

@stx
Copy link

stx commented Jun 15, 2019

Again, thanks for this plugin. You're doing an awesome job!

Is it possible to show gaps or null data? I've played around and haven't found how one might do that.

Visually, something along these lines:

image

Implementation wise, this might look like:

                    FlSpot(1, 2),
                    FlSpot(2, 3),
                    FlSpot(3, 3),
                    FlSpot(4, null),
                    FlSpot(5, null),
                    FlSpot(6, 2),
                    FlSpot(7, 4),

Thanks again!

@imaNNeo
Copy link
Owner

imaNNeo commented Jun 15, 2019

You can have multiple bars on the LineChart, then make two separted and combine them in your chart,

@imaNNeo
Copy link
Owner

imaNNeo commented Jun 15, 2019

check LineBarsData , you can have multiple LineChartBarData

@stx
Copy link
Author

stx commented Jun 15, 2019

Got it! Thank you!

@stx stx closed this as completed Jun 15, 2019
@shamilovtim
Copy link

shamilovtim commented Jan 10, 2020

Hello @imaNNeoFighT,

Thanks for the library! It's great so far. Is it possible to get real nullable coordinates added without having to create multiple instances of lines? This is a standard feature of a lot of different charting libraries.

In almost all of them the best practice is:

{ x: 5, y: null },
{ x: 6, y: null }

See:

  1. https://google.github.io/charts/flutter/example/line_charts/simple_nulls
  2. https://formidable.com/open-source/victory/gallery/victory-line-with-null-data/

@imaNNeo
Copy link
Owner

imaNNeo commented Jan 10, 2020

Okay fine, we need this in the line chart, but It is weird when we have rounded line chart

@shamilovtim
Copy link

Definitely but I think it conveys the proper feeling from the business logic side. The users didn't chart something that they were supposed to do, which caused the problem.

We could offer settings to interpolate with a dashed line and/or a pure null option:

Pure null
Screen Shot 2020-01-10 at 2 29 42 PM

Interpolated with a dashed line:
Screen Shot 2020-01-10 at 2 30 14 PM

I would be happy to help with this feature if we go with flutter for our project

@imaNNeo
Copy link
Owner

imaNNeo commented Jan 10, 2020

Nice, we will consider it.
Thanks for reporting!

@imaNNeo imaNNeo reopened this Jan 10, 2020
@imaNNeo imaNNeo added enhancement New feature or request Line Chart labels Jan 10, 2020
@yongjhih
Copy link

yongjhih commented Jan 31, 2020

That's what I did for null spots:

Skip null spots:

() {
    return LineChart(LineChartData(
        lineBarsData: LineChartBarData(/* ... */).listWith(spots.splitByNull()),
        minX: spots.map((it) => it.x).min(), // optional: Avoid trimming to keep the original range of x-axis
        maxX: spots.map((it) => it.x).max(), // optional: Avoid trimming to keep the original range of x-axis
        // ...
    ));
}

extension LineChartBarDataX<T extends LineChartBarData> on T {
  /// Skip spots while [spots] contain null y
  List<T> listWith(Iterable<List<FlSpot>> lists) {
    final data = this;
    return lists.map((spots) => data.copyWith(spots: spots)).toList();
  }
}

extension IterableFlSpotX<T extends FlSpot> on Iterable<T> {
  Iterable<List<T>> splitByNull() => splitBy((it) => it.y == null);
}

/// ref. https://github.com/yongjhih/dartx/blob/77ef87a/lib/src/iterable.dart#L1012
extension IterableX<T> on Iterable<T> {
  Iterable<List<E>> splitBy(bool test(E it)) {
    final lists = fold<List<List<E>>>([[]], (that, it) {
      if (!test(it)) {
        that.last.add(it);
      } else {
        if (that.last.isNotEmpty) {
          that.add([]);
        }
      }
      return that;
    });

    return lists.where((it) => it.isNotEmpty);
  }
}

Interpolate with the dashes for null spots:

() {
    return LineChart(LineChartData(
        lineBarsData: LineChartBarData(/* ... */).listDashableWith(spots),
        minX: spots.map((it) => it.x).min(), // optional: Avoid trimming to keep the original range of x-axis
        maxX: spots.map((it) => it.x).max(), // optional: Avoid trimming to keep the original range of x-axis
        // ...
    ));
}

extension LineChartBarDataX<T extends LineChartBarData> on T {
  /// Skip spots while [spots] contain null y
  List<T> listWith(Iterable<List<FlSpot>> lists) {
    final data = this;
    return lists.map((spots) => data.copyWith(spots: spots)).toList();
  }

  /// Return simple dashed [T]
  T dashed({
    List<int> dashArray = const [1, 2],
    double opacity = 0.5,
  }) {
    return copyWith(
        dashArray: dashArray,
        belowBarData: belowBarData?.copyWith(
          colors: belowBarData?.colors?.map((it) => it. opacityFactor(opacity))?.toList(),
        ));
  }

  /// Dashed [spots] while which contain null y
  List<T> listDashableWith(Iterable<FlSpot> spots, {
    List<int> dashArray = const [1, 2],
    double opacity = 0.5,
  }) {
    final repeatedSpots = spots.repeatByNull();
    final _dashed = dashed(dashArray: dashArray, opacity: opacity);
    if (repeatedSpots.isNotEmpty) {
      return repeatedSpots.map((it) => it.first.dashed ? _dashed.copyWith(spots: it) : copyWith(spots: it)).toList();
    } else {
      final values = spots.map((it) => it.x);
      return <T>[
        _dashed.copyWith(spots: <FlSpot>[
          FlSpot(values.min() ?? 0, 0),
          FlSpot(values.max() ?? 0, 0),
        ])
      ];
    }
  }
}

extension IterableFlSpotX<T extends FlSpot> on Iterable<T> {
  Iterable<List<T>> splitByNull() => splitBy((it) => it.y == null);
}

extension IterableFlSpotsX on Iterable<FlSpot> {
  Iterable<List<FlSpotDashable>> repeatByNull() =>
      map((it) => FlSpotDashable(it.x, it.y))
          .repeatBy((it) => it.y == null, (that, it) => FlSpotDashable(that.x, that.y, dashed: true))
          .where((it) => it.isNotEmpty);
}

class FlSpotDashable extends FlSpot {
  const FlSpotDashable(double x, double y, {this.dashed = false}) : super(x, y);

  final bool dashed;

  @override
  FlSpotDashable copyWith({
    double x,
    double y,
    bool dashed = false
  }) {
    return FlSpotDashable(
      x ?? this.x,
      y ?? this.y,
      dashed: dashed ?? this.dashed,
    );
  }

  @override
  String toString() {
    return "{x: ${x}, y: ${y}, dashed: $dashed}";
  }
}

extension BarAreaDataX<T extends BarAreaData> on T {
  BarAreaData copyWith({
    bool show,
    List<Color> colors,
    Offset gradientFrom,
    Offset gradientTo,
    List<double> gradientColorStops,
    BarAreaSpotsLine spotsLine,
    double cutOffY,
    bool applyCutOffY,
  }) => BarAreaData(
      show: show ?? this.show ?? false,
      colors: colors ?? this.colors ?? const [Colors.blueGrey],
      gradientFrom: gradientFrom ?? this.gradientFrom ?? const Offset(0, 0),
      gradientTo: gradientTo ?? this.gradientTo ?? const Offset(1, 0),
      gradientColorStops: gradientColorStops ?? this.gradientColorStops,
      spotsLine: spotsLine ?? this.spotsLine ?? const BarAreaSpotsLine(),
      cutOffY: cutOffY ?? this.cutOffY,
      applyCutOffY: applyCutOffY ?? this.applyCutOffY ?? false,
    );
}

/// ref. https://github.com/yongjhih/dartx/blob/77ef87a/lib/src/iterable.dart#L1012
extension IterableX<T> on Iterable<T> {
  Iterable<List<E>> splitBy(bool test(E it)) {
    final lists = fold<List<List<E>>>([[]], (that, it) {
      if (!test(it)) {
        that.last.add(it);
      } else {
        if (that.last.isNotEmpty) {
          that.add([]);
        }
      }
      return that;
    });

    return lists.where((it) => it.isNotEmpty);
  }

  Iterable<List<T>> repeatBy(bool test(T it), T repeat(T repeater, T element)) {
    if (isEmpty) {
      return [[]];
    }

    final lists = skip(1).fold<List<List<T>>>([[first]], (that, it) {
      if (test(it)) { // it == null
        // repeating for current
        if (test(that.last.last)) { // it == null && last == null
          // skip
        } else { // it == null && last != null
          // repeat by repeater
          that.add([repeat(that.last.last, it), it]);
        }
      } else { // it != null
        if (test(that.last.last)) { // it != null && last == null
          that.last.last = repeat(it, that.last.last);
          that.add([it]);
        } else { // it != null && last != null
          that.last.add(it);
        }
      }

      return that;
    });

    if (test(lists.last.last)) { // it != null && last == null
      final repeater = lists.last.lastOrNullWhere((it) => !test(it));
      if (repeater != null) {
        lists.last.last = repeat(repeater, lists.last.last);
      }
    }

    return lists.where((it) => it.every((that) => !test(that)));
  }
}

extension ColorX<T extends Color> on T {
  Color opacityFactor(double factor) =>
      withOpacity(opacity * factor);
}

I'm still looking for this project will support the nullable spots by itself.

@shamilovtim
Copy link

@yongjhih nulls should be in the next release

@imaNNeo
Copy link
Owner

imaNNeo commented Mar 21, 2020

Implemented in 0.8.6, check it out!

@Vingtoft
Copy link

How can I use null values?
If null is parsed as Y value, the following exception is thrown:

Unhandled Exception: type 'Null' is not a subtype of type 'double'

My use case requires valid X values with null Y values

@cryosx
Copy link

cryosx commented Jan 7, 2024

How can I use null values? If null is parsed as Y value, the following exception is thrown:

Unhandled Exception: type 'Null' is not a subtype of type 'double'

My use case requires valid X values with null Y values

FlSpot.nullSpot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Line Chart
Projects
None yet
Development

No branches or pull requests

6 participants