Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create custom LegendContentBuilder with charts_flutter library to show and hide series depond on selection?

I used charts_flutter to create BarChart, at current time i want to customise that to build a barChart like below chart:

Here i want to do below options:

  1. show/hide series when i click on legend which i created in CustomLegend (i create that in my code with class name -> CustomLegendBuilder)
  2. set custom color to text of secondaryMeasureAxis and primaryMeasureAxis (i have two axis in right and left)
  3. Show value of column when tap on that column (like a bubble message to show maximum data value of that column)
  4. don't expand width of column when i hide one of series(i want to set specific width to column)

Please see my Code:

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

and 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(),
        ),
      ),
    );
  }
}

finally here is my result :

Please help me if you worked with charts_flutter library , Thank you very much :)

like image 447
Taleb Avatar asked Jul 07 '20 12:07

Taleb


People also ask

How to create a developer series model in flutter?

Our model, which defines the format of our data, consists of the year we are looking at, the number of developers that joined the Flutter chart community that year, and the color of the corresponding bar. Inside the lib folder, create a file named developer_series.dart. Below, implement the code for our model:

How to create charts in flutter?

When building Flutter apps, developers can use the official charts_flutter library, maintained by Google, to create these types of charts. In this tutorial, we’ll learn how to create some of the most common charts — a line chart, pie chart, and bar chart — using charts_flutter.

When to use custom widgets in flutter?

Flutter – Custom Widgets Last Updated : 12 Mar, 2021 We create Custom Widgets when we want a custom look and feel to our app, and we know that there will be a repetition of a particular widget. We can create the custom widget in a new dart file with all the codes and defining the parameters that we need in the constructor.

How to display world of Warcraft subscriber numbers in flutter?

You can use this library in your Flutter application to display data as bar charts, line charts, scatter plot charts, pie charts, and more. In this article, you will use data for World of Warcraft subscriber numbers between the years of 2008 to 2015 and display it as a bar chart using charts_flutter. To download and install Flutter.


1 Answers

  1. I am pretty sure there must be a better way (cleaner, more organized) to do it, but I merged your code with flutter_charts open source code and got the colors to be set correctly and the labels to be reactive to the clicks on it to hide or show variables. Here is the example:

     /// 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),
         );
       }
     }
    
  2. About the value you want to be shown on click, maybe this is what you are looking for. I made it work with the CustomCircleSymbolRenderer. You will see that the TextElement is hardcoded to "1". To actualize it you can save a variable on your chart class and call its content:

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

Inside the chart, below "behaviours: [],":

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

Then on the custom renderer:

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

I found an example on internet some time ago, but I am not sure where.

  1. / 4. About the axis and width of column I have no suggestions.
like image 160
Iris Avatar answered Oct 12 '22 18:10

Iris