google / charts

https://pub.dev/packages/charts_flutter
Apache License 2.0
2.8k stars 1.2k forks source link

Exception thrown when using desiredMaxColumns in SeriesLegend in certain cases #116

Open Zil85 opened 6 years ago

Zil85 commented 6 years ago

There seems to be an issue with the SeriesLegend of Charts when using desiredMaxColumns.

If the 'desiredMaxColumns' parameter is set to a a value lower then the number of series in the chart and the number of series isn't exactly divisible by the 'desiredMaxColumns' an exception is thrown. Basically if the final row of the legend holds fewer entries then the rest the error occurs.

For example if there are 3 series in the chart. Setting desiredMaxColumns = 1 works fine and produces 3 rows of 1. Label1 Label2 Label3

Setting desiredMaxColumns >= 3 works fine and produces 1 row of 3. Label1 Label2 Label3

Setting desiredMaxColumns = 2, throws an exception. Expected result Label1 Label2 Label3

The exception given

flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building LineChart(dirty, state:
flutter: BaseChartState<num>#e3f81(tickers: tracking 1 ticker)):
flutter: type '_GeneratorIterable<Widget>' is not a subtype of type 'Iterable<Padding>' of 'iterable'
flutter:
flutter: Either the assertion indicates an error in the framework itself, or we should provide substantially
flutter: more information in this error message to help you determine and fix the underlying cause.
flutter: In either case, please report this assertion by filing a bug on GitHub:
flutter:   https://github.com/flutter/flutter/issues/new
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0      List.addAll (dart:core/runtime/libgrowable_array.dart)
flutter: #1      TabularLegendLayout._buildTableFromRows (package:charts_flutter/src/behaviors/legend/legend_layout.dart:147:21)
flutter: #2      TabularLegendLayout._buildHorizontalFirst (package:charts_flutter/src/behaviors/legend/legend_layout.dart:117:12)
flutter: #3      TabularLegendLayout.build (package:charts_flutter/src/behaviors/legend/legend_layout.dart:88:11)
flutter: #4      BaseLegendContentBuilder.build (package:charts_flutter/src/behaviors/legend/legend_content_builder.dart:64:25)
flutter: #5      _FlutterSeriesLegend.build (package:charts_flutter/src/behaviors/legend/series_legend.dart:288:8)
flutter: #6      BaseChartState.build.<anonymous closure> (package:charts_flutter/src/base_chart_state.dart:119:42)
flutter: #7      __InternalLinkedHashMap&_HashVMBase&MapMixin&_LinkedHashMapMixin.forEach (dart:collection/runtime/libcompact_hash.dart:363:8)
flutter: #8      BaseChartState.build (package:charts_flutter/src/base_chart_state.dart:112:32)
flutter: #9      StatefulElement.build (package:flutter/src/widgets/framework.dart:3730:27)
flutter: #10     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3642:15)
flutter: #11     Element.rebuild (package:flutter/src/widgets/framework.dart:3495:5)
flutter: #12     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2242:33)
flutter: #13     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:626:20)
flutter: #14     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208:5)
flutter: #15     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
flutter: #16     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
flutter: #17     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842:5)
flutter: #18     _invoke (dart:ui/hooks.dart:120:13)
flutter: #19     _drawFrame (dart:ui/hooks.dart:109:3)

Full code to reproduce the issue:

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

void main() {
  runApp(
    MaterialApp(home: NumericInitialViewport()),
  );
}

class NumericInitialViewport extends StatelessWidget {
  List<charts.Series> seriesList;
  bool animate = false;

  @override
  Widget build(BuildContext context) {
    seriesList = _createSampleData();

    return charts.LineChart(
      seriesList,
      animate: animate,
      behaviors: [
      charts.SeriesLegend(
        desiredMaxColumns: 2, // <-- This is the problem
      )],
    );
  }

  /// Create 3 series with sample hard coded data.
  static List<charts.Series<LinearSales, int>> _createSampleData() {
    final data1 = [
      LinearSales(0, 5),
      LinearSales(1, 25),
      LinearSales(2, 100),
      LinearSales(3, 75),
      LinearSales(4, 55),
      LinearSales(5, 66),
    ];
    final data2 = [
      LinearSales(0, 25),
      LinearSales(1, 45),
      LinearSales(2, 60),
      LinearSales(3, 45),
      LinearSales(4, 75),
      LinearSales(5, 68),
    ];
    final data3 = [
      LinearSales(0, 51),
      LinearSales(1, 24),
      LinearSales(2, 10),
      LinearSales(3, 71),
      LinearSales(4, 53),
      LinearSales(5, 64),
    ];

    return [
      charts.Series<LinearSales, int>(
        id: 'Sales1',
        colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
        domainFn: (LinearSales sales, _) => sales.year,
        measureFn: (LinearSales sales, _) => sales.sales,
        data: data1,
      ),
      charts.Series<LinearSales, int>(
        id: 'Sales2',
        colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
        domainFn: (LinearSales sales, _) => sales.year,
        measureFn: (LinearSales sales, _) => sales.sales,
        data: data2,
      ),
      charts.Series<LinearSales, int>(
        id: 'Sales3',
        colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
        domainFn: (LinearSales sales, _) => sales.year,
        measureFn: (LinearSales sales, _) => sales.sales,
        data: data3,
      )
    ];
  }
}

/// Sample linear data type.
class LinearSales {
  final int year;
  final int sales;

  LinearSales(this.year, this.sales);
}

Result from flutter doctor:

[✓] Flutter (Channel unknown, v0.5.1, on Mac OS X 10.13.6 17G65, locale en-DK)
    • Flutter version 0.5.1 at /Users/user/Applications/flutter
    • Framework revision c7ea3ca377 (3 months ago), 2018-05-29 21:07:33 +0200
    • Engine revision 1ed25ca7b7
    • Dart version 2.0.0-dev.58.0.flutter-f981f09760

[✓] Android toolchain - develop for Android devices (Android SDK 28.0.1)
    • Android SDK at /Users/user/Library/Android/sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-28, build-tools 28.0.1
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)
    • All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 9.4.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 9.4.1, Build version 9F2000
    • ios-deploy 1.9.2
    • CocoaPods version 1.5.3

[✓] Android Studio (version 3.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 27.1.1
    • Dart plugin version 173.4700
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)
Schwusch commented 5 years ago

My workaround for this problem is to set desiredMaxRows to the number of legend entries divided with 2 rounded up, as follows:

charts.SeriesLegend(
        horizontalFirst: false,
        desiredMaxRows: (numberOfLegendEntries / 2).ceil()
)