如何使用 charts_flutter 库创建自定义 LegendContentBuilder 以根据选择显示和隐藏系列?

Tal*_*leb 9 charts legend flutter dynamic-chart-series google-barchart

我使用charts_flutter创建BarChart,目前我想自定义它以构建如下图所示的 barChart:

在这里,我想做以下选项:

  1. 当我点击我在CustomLegend 中创建的图例时显示/隐藏系列(我在我的代码中用类名创建 -> CustomLegendBuilder
  2. 将自定义颜色设置为secondaryMeasureAxisprimaryMeasureAxis 的文本(我有左右两个轴)
  3. 点击该列时显示列的值(如气泡消息以显示该列的最大数据值)
  4. 当我隐藏系列之一时不要扩展列的宽度(我想为列设置特定的宽度)

请看我的代码:

pubspec.yaml :

dev_dependencies:
  flutter_test:
    sdk: flutter
  charts_flutter: ^0.9.0
Run Code Online (Sandbox Code Playgroud)

series_legend_options.dart :

import 'dart:math';

import 'package:charts_common/src/chart/common/behavior/legend/legend.dart';
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;

const Color blueColor = Color(0xff1565C0);
const Color orangeColor = Color(0xffFFA000);

class LegendOptions extends StatelessWidget {
  final List<charts.Series> seriesList;
  static const secondaryMeasureAxisId = 'secondaryMeasureAxisId';
  final bool animate;

  LegendOptions(this.seriesList, {this.animate});

  factory LegendOptions.withSampleData() {
    return LegendOptions(
      _createSampleData(),
      // Disable animations for image tests.
      animate: false,
    );
  }

  @override
  Widget build(BuildContext context) {
    return charts.BarChart(
      seriesList,
      animate: animate,
      barGroupingType: charts.BarGroupingType.grouped,
      defaultRenderer: charts.BarRendererConfig(
          cornerStrategy: const charts.ConstCornerStrategy(50)),

      primaryMeasureAxis: charts.NumericAxisSpec(
        tickProviderSpec: charts.BasicNumericTickProviderSpec(
          desiredMinTickCount: 6,
          desiredMaxTickCount: 10,
        ),
      ),
      secondaryMeasureAxis: charts.NumericAxisSpec(
          tickProviderSpec: charts.BasicNumericTickProviderSpec(
              desiredTickCount: 6, desiredMaxTickCount: 10)),
      selectionModels: [
        charts.SelectionModelConfig(
            changedListener: (charts.SelectionModel model) {
          if (model.hasDatumSelection)
            print(model.selectedSeries[0]
                .measureFn(model.selectedDatum[0].index));
        })
      ],
      behaviors: [
        charts.SeriesLegend.customLayout(
          CustomLegendBuilder(),
          position: charts.BehaviorPosition.top,
          outsideJustification: charts.OutsideJustification.start,
        ),
      ],
    );
  }

  static List<charts.Series<OrdinalSales, String>> _createSampleData() {
    final desktopSalesData = [
      OrdinalSales('2/14', 29),
      OrdinalSales('2/15', 25),
      OrdinalSales('2/16', 100),
      OrdinalSales('2/17', 75),
      OrdinalSales('2/18', 70),
      OrdinalSales('2/19', 70),
    ];

    final tabletSalesData = [
      OrdinalSales('2/14', 10),
      OrdinalSales('2/15', 25),
      OrdinalSales('2/16', 8),
      OrdinalSales('2/17', 20),
      OrdinalSales('2/18', 38),
      OrdinalSales('2/19', 70),
    ];

    return [
      charts.Series<OrdinalSales, String>(
          id: 'expense',
          domainFn: (OrdinalSales sales, _) => sales.year,
          measureFn: (OrdinalSales sales, _) => sales.sales,
          data: desktopSalesData,
          colorFn: (_, __) => charts.ColorUtil.fromDartColor(orangeColor),
          labelAccessorFn: (OrdinalSales sales, _) =>
              'expense: ${sales.sales.toString()}',
          displayName: "Expense"),
      charts.Series<OrdinalSales, String>(
        id: 'income',
        domainFn: (OrdinalSales sales, _) => sales.year,
        measureFn: (OrdinalSales sales, _) => sales.sales,
        data: tabletSalesData,
        colorFn: (_, __) => charts.ColorUtil.fromDartColor(blueColor),
        displayName: "Income",
      )..setAttribute(charts.measureAxisIdKey, secondaryMeasureAxisId),
    ];
  }
}


//Here is my CustomLegendBuilder

class CustomLegendBuilder extends charts.LegendContentBuilder {
  @override
  Widget build(BuildContext context, LegendState<dynamic> legendState,
      Legend<dynamic> legend,
      {bool showMeasures}) {
    return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisSize: MainAxisSize.min,
        children: legend.chart.currentSeriesList
            .map<Widget>((e) => InkWell(
                  //Here i Want to show and hide series when click on the legend
                  onTap: () {},
                  child: Container(
                      padding:
                          EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                      margin: EdgeInsets.all(5),
                      decoration: BoxDecoration(
                        color: Colors.grey,
                        borderRadius: BorderRadius.all(Radius.circular(100)),
                      ),
                      child: Text(
                        e.displayName,
                        style: TextStyle(color: Colors.white),
                      )),
                ))
            .toList()
              ..add(Spacer())
              ..add(Transform.rotate(
                angle: 90 * (pi / 180),
                child: Icon(
                  Icons.tune,
                  size: 30,
                  color: Colors.red,
                ),
              )));
  }
}

////////////////////////////////

class OrdinalSales {
  final String year;
  final int sales;

  OrdinalSales(this.year, this.sales);
}
Run Code Online (Sandbox Code Playgroud)

main.dart

import 'package:flutter/material.dart';

import 'series_legend_options.dart';

void main(){
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeWidget(),
    );
  }
}

class HomeWidget extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          margin: EdgeInsets.all(20),
          height: 300,
          child: LegendOptions.withSampleData(),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

最后这是我的结果:

如果您使用过 charts_flutter 库,请帮助我,非常感谢 :)

小智 5

  1. 我很确定一定有更好的方法(更干净、更有组织)来做到这一点,但我将您的代码与 flutter_charts 开源代码合并,并正确设置了颜色,并且标签对点击它做出反应隐藏或显示变量。这是示例:

     /// Custom legend builder
     class CustomLegendBuilder extends charts.LegendContentBuilder {
       /// Convert the charts common TextStlyeSpec into a standard TextStyle.
       TextStyle _convertTextStyle(
         bool isHidden, BuildContext context, common.TextStyleSpec textStyle) {
         return new TextStyle(
           inherit: true,
           fontFamily: textStyle?.fontFamily,
           fontSize:
           textStyle?.fontSize != null ? textStyle.fontSize.toDouble() : null,
           color: Colors.white);
       }
    
       Widget createLabel(BuildContext context, common.LegendEntry legendEntry,
         common.SeriesLegend legend, bool isHidden) {
         TextStyle style =
         _convertTextStyle(isHidden, context, legendEntry.textStyle);
         Color color = charts.ColorUtil.toDartColor(legendEntry.color) ?? Colors.blue;
    
         return new GestureDetector(
           child: Container(
             height: 30,
             width: 90,
             decoration: BoxDecoration(
               borderRadius: BorderRadius.circular(20),
               color: isHidden ? (color).withOpacity(0.26): color,
             ),
             child: Center(child: Text(legendEntry.label, style: style))),
           onTapUp: makeTapUpCallback(context, legendEntry, legend));
       }
    
       GestureTapUpCallback makeTapUpCallback(BuildContext context,
         common.LegendEntry legendEntry, common.SeriesLegend legend) {
         return (TapUpDetails d) {
           switch (legend.legendTapHandling) {
             case common.LegendTapHandling.hide:
               final seriesId = legendEntry.series.id;
               if (legend.isSeriesHidden(seriesId)) {
               // This will not be recomended since it suposed to be accessible only from inside the legend class, but it worked fine on my code.
                 legend.showSeries(seriesId);
               } else {
                 legend.hideSeries(seriesId);
               }
               legend.chart.redraw(skipLayout: true, skipAnimation: false);
               break;
             case common.LegendTapHandling.none:
             default:
               break;
           }
         };
    
       }
    
       @override
       Widget build(BuildContext context, common.LegendState legendState,
         common.Legend legend,
         {bool showMeasures}) {
         final entryWidgets = legendState.legendEntries.map((legendEntry) {
           var isHidden = false;
           if (legend is common.SeriesLegend) {
             isHidden = legend.isSeriesHidden(legendEntry.series.id);
           }
           return createLabel(context, legendEntry, legend as common.SeriesLegend, isHidden);
         }).toList();
    
         return Padding(
           padding: const EdgeInsets.only(right: 40.0, top: 10),
           child: Row(
             mainAxisAlignment: MainAxisAlignment.spaceBetween,
             crossAxisAlignment: CrossAxisAlignment.center,
             children: entryWidgets),
         );
       }
     }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 关于您希望在点击时显示的值,也许就是您正在寻找的。我让它与 CustomCircleSymbolRenderer 一起使用。您将看到 TextElement 被硬编码为“1”。要实现它,您可以在图表类上保存一个变量并调用其内容:

     class MyChart extends StatelessWidget {
       final List data;
       static String pointerValue;
       MyChart(this.data);
    
    Run Code Online (Sandbox Code Playgroud)

在图表内,“行为:[]”下方:

   selectionModels: [
                SelectionModelConfig(changedListener: (SelectionModel model) {
                  if (model.hasDatumSelection)
                    pointerValue = model.selectedSeries[0]
                      .measureFn(model.selectedDatum[0].index)
                      .toString();
                })
              ],
Run Code Online (Sandbox Code Playgroud)

然后在自定义渲染器上:

    canvas.drawText(
          TextElement(MyChart.pointerValue, style: textStyle),
          (bounds.left).round(),
          (bounds.top - 28).round());
Run Code Online (Sandbox Code Playgroud)

我前段时间在网上找到了一个例子,但我不确定在哪里。

  1. / 4.关于柱的轴和宽度我没有建议。