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

Posted

技术标签:

【中文标题】如何使用 chart_flutter 库创建自定义 LegendContentBuilder 以根据选择显示和隐藏系列?【英文标题】:How to create custom LegendContentBuilder with charts_flutter library to show and hide series depond on selection? 【发布时间】:2020-10-27 16:57:47 【问题描述】:

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

这里我想做以下选项:

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

请看我的代码:

pubspec.yaml

dev_dependencies:
  flutter_test:
    sdk: flutter
  charts_flutter: ^0.9.0

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);

ma​​in.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(),
        ),
      ),
    );
  

最后是我的结果:

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

【问题讨论】:

【参考方案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),
         );
       
     
    

    关于您希望在点击时显示的值,也许this 是您正在寻找的。我使它与 CustomCircleSymbolRenderer 一起工作。您将看到 TextElement 被硬编码为“1”。要实现它,您可以在图表类中保存一个变量并调用其内容:

     class MyChart extends StatelessWidget 
       final List data;
       static String pointerValue;
       MyChart(this.data);
    

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

   selectionModels: [
                SelectionModelConfig(changedListener: (SelectionModel model) 
                  if (model.hasDatumSelection)
                    pointerValue = model.selectedSeries[0]
                      .measureFn(model.selectedDatum[0].index)
                      .toString();
                )
              ],

然后在自定义渲染器上:

    canvas.drawText(
          TextElement(MyChart.pointerValue, style: textStyle),
          (bounds.left).round(),
          (bounds.top - 28).round());

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

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

【讨论】:

非常感谢,有用的回答

以上是关于如何使用 chart_flutter 库创建自定义 LegendContentBuilder 以根据选择显示和隐藏系列?的主要内容,如果未能解决你的问题,请参考以下文章

chart_flutter 包中的图中的 domainFn 和 measureFn 是啥?

如何使用 gcc 创建和使用自定义共享库?

我将如何引用使用 C++ 中的另一个库创建的自定义库?

如何在没有第三方库的情况下创建自定义幻灯片菜单。?

RF自定义库

RF引入自定义库