如何使用 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)
为 secondaryMeasureAxis 和 primaryMeasureAxis 的文本设置自定义颜色(我在左右两个轴)
点击该列时显示该列的值(如显示该列最大数据值的气泡消息)
当我隐藏一个系列时不要扩大列的宽度(我想为列设置特定的宽度)
请看我的代码:
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);
和 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(),
),
),
);
最后是我的结果:
如果您使用过 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 以根据选择显示和隐藏系列?的主要内容,如果未能解决你的问题,请参考以下文章