google / charts

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

Pie chart breaks when utilizing charts.ArcLabelPosition.auto and charts.BehaviorPosition.bottom #334

Open thoffart opened 5 years ago

thoffart commented 5 years ago

The pie chart breaks when the legend position is set to the bottom and the arc position is set to automatic. This seems to be related to the outside arc overlapping the legend at the bottom. Causing this bug to depend on the bounds and chart values.

Environment

  1. Flutter version: 1.7.8+hotfix.4
  2. charts_flutter: ^0.8.1
  3. Emulator: Pixel_XL

Screenshot: image

Error:

════════ Exception Caught By rendering library ═════════════════════════════════
The following ArgumentError was thrown during paint():
Invalid argument(s): 0.0
When the exception was thrown, this was the stack
#0      double.clamp  (dart:core-patch/double.dart:174:7)
#1      TextPainter.layout 
package:flutter/…/painting/text_painter.dart:549
#2      TextElement._refreshPainter 
package:charts_flutter/src/text_element.dart:167
#3      TextElement.measurement 
package:charts_flutter/src/text_element.dart:111
#4      ChartCanvas.drawText 
package:charts_flutter/src/chart_canvas.dart:284
...
The following RenderObject was being processed when the exception was fired: ChartContainerRenderObject<dynamic>#12994
RenderObject: ChartContainerRenderObject<dynamic>#12994
    parentData: <none> (can use size)
    constraints: BoxConstraints(w=411.4, h=340.0)
    semantic boundary
    size: Size(411.4, 340.0)
════════════════════════════════════════════════════════════════════════════════

════════ Exception Caught By rendering library ═════════════════════════════════
The following ArgumentError was thrown during paint():
Invalid argument(s): 0.0
When the exception was thrown, this was the stack
#0      double.clamp  (dart:core-patch/double.dart:174:7)
#1      TextPainter.layout 
package:flutter/…/painting/text_painter.dart:549
#2      TextElement._refreshPainter 
package:charts_flutter/src/text_element.dart:167
#3      TextElement.measurement 
package:charts_flutter/src/text_element.dart:111
#4      ChartCanvas.drawText 
package:charts_flutter/src/chart_canvas.dart:284
...
The following RenderObject was being processed when the exception was fired: ChartContainerRenderObject<dynamic>#12994
RenderObject: ChartContainerRenderObject<dynamic>#12994
    parentData: <none> (can use size)
    constraints: BoxConstraints(w=411.4, h=340.0)
    semantic boundary
    size: Size(411.4, 340.0)
════════════════════════════════════════════════════════════════════════════════

main.dart:

import 'pie-chart-widget/pie-chart-widget.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: Center(
        child: Container(
          width: 500,
          height: 500,
          child: PieChartWidget.withSampleData(),
        )
      ),
    );
  }
}

pie-chart-widget.dart:

/// Simple pie chart with outside labels example.
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';

class PieChartWidget extends StatelessWidget {
  final dynamic seriesList;
  final String seriesId;

  PieChartWidget(this.seriesList, this.seriesId);

  factory PieChartWidget.withSampleData() {
    return new PieChartWidget(
      _createSampleData(),
      'sample-data'
    );
  }

  List<charts.Series<dynamic, dynamic>> createSeries(dynamic data, String seriesId) {
    return [
      new charts.Series(
        id: seriesId,
        data: data,
        domainFn: (dynamic data, _) => data['name'],
        measureFn: (dynamic data, _) => data['value'],
        labelAccessorFn: (dynamic row, _) => '${row['value']}',
      )
    ];
  }

  @override
  Widget build(BuildContext context) {
    return charts.PieChart(
      createSeries(seriesList, seriesId),
      animate: false,
      behaviors: [new charts.DatumLegend(
        position: charts.BehaviorPosition.bottom,
        outsideJustification: charts.OutsideJustification.middleDrawArea,
        desiredMaxColumns: 2,
        desiredMaxRows: 10,
      )],
      defaultRenderer: new charts.ArcRendererConfig(arcRendererDecorators: [
        new charts.ArcLabelDecorator(
          labelPosition: charts.ArcLabelPosition.auto,
          insideLabelStyleSpec: charts.TextStyleSpec(
            fontSize: 16, // size in Pts.
            color: charts.MaterialPalette.black
          )
        )
      ]),
    );
  }

  static List<dynamic> _createSampleData() {
    return [
      {
        "name": "data 1",
        "value": 6
      },
      {
        "name": "data 2",
        "value": 36
      },
      {
        "name": "data 3",
        "value": 2
      },
      {
        "name": "data 4",
        "value": 9
      },
      {
        "name": "data 5",
        "value": 79
      },
      {
        "name": "data 6",
        "value": 16
      },
      {
        "name": "data 7",
        "value": 79
      },
      {
        "name": "data 8",
        "value": 3
      },
      {
        "name": "data 9",
        "value": 2
      },
    ];
  }

}
drafting-dreams commented 4 years ago

This is caused by invoking _textPainter.layout() in the text_element.dart file. I don't know somehow, the maxWidth passed to it is negative. And inside of this function, clamp() is called with clamp(minWidth, maxWidth), of which minWidth is set to 0.0 as default. caused this problem. So, I just simply added a condition to get around this. Hope this can help and the team fix this in the future. if (maxWidth == null || maxWidth >= 0) { _textPainter.layout(maxWidth: maxWidth?.toDouble() ?? double.infinity); } else { _textPainter.layout(maxWidth: double.infinity); }

thoffart commented 4 years ago

This is caused by invoking _textPainter.layout() in the text_element.dart file. I don't know somehow, the maxWidth passed to it is negative. And inside of this function, clamp() is called with clamp(minWidth, maxWidth), of which minWidth is set to 0.0 as default. caused this problem. So, I just simply added a condition to get around this. Hope this can help and the team fix this in the future. if (maxWidth == null || maxWidth >= 0) { _textPainter.layout(maxWidth: maxWidth?.toDouble() ?? double.infinity); } else { _textPainter.layout(maxWidth: double.infinity); }

I came around with the same solution a time ago, but instead of setting the maxWidth to infinity when it's value is negative, I convert it to his absolute value. I am having no issues with this solution in my project, that is in production a couple of months.

cuong292 commented 4 years ago

still no work arround or fix with this?