Gilbert Aligoey
Gilbert Aligoey

Reputation: 135

Flutter - Horizontal Bar Chart stacked in one bar chart

I'm trying to achieve this type of horizontal bar chart stacked within one Bar chart. I came across the fl_chart package, but none of it seems to have the type that I'm looking for. If any champ can support me in giving me steps to how to achieve this or an exemplary code will be so much helpful. Thank you so much in advance.

Example

Upvotes: 4

Views: 4428

Answers (5)

Fractale
Fractale

Reputation: 1654

There is a simple flutter package for that: https://pub.dev/packages/staked_horizontal_bar_chart

Upvotes: 0

Aakash Ghimire
Aakash Ghimire

Reputation: 154

I assume, the best and simplest way to do this without any packages is using row and expanded containers with flex of respective category percentages.

Like this

  Widget buildSteppedProgressBar(List<Category> categories) {
  double totalProgress = categories.fold(0, (sum, item) => sum + item.progress);
  return Padding(
    padding: const EdgeInsets.symmetric(vertical: Dimens.DIMEN_TWELVE),
    child: ClipRRect(
      borderRadius: BorderRadius.circular(8.0),
      child: Row(
        children: categories.map((category) {
          return Flexible(
            flex: (100 * category.progress / totalProgress).ceil(),
            child: Container(
              margin: EdgeInsets.symmetric(horizontal: 1),
              height: 20,
              color: category.color,
            ),
          );
        }).toList(),
      ),
    ),
  );
}

Here I expect your model to be a category model with progress and color as a property for this stepped bar.

Upvotes: 0

Thierry
Thierry

Reputation: 8393

You could also achieve this with a LinearGradient.

A LinearGradient takes a List<Color> colors and List<double> stops.

In order to have clear color boundaries, you duplicate the colors and stops at the boundaries.

Example:

colors: [red, red, transparent, transparent, green, green]
stops: [0.0, 0.45, 0.45, 0.55, 0.55, 1]

Full code sample

enter image description here

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData.light(),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final chartData = [
      Data(units: 15, color: const Color(0xFF8A5426)),
      Data(units: 20, color: const Color(0xFF00BCD5)),
      Data(units: 12, color: const Color(0xFF7B8700)),
      Data(units: 10, color: const Color(0xFFDD8B11)),
      Data(units: 50, color: const Color(0xFF673BB7)),
    ];
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: SizedBox(
            height: 20,
            child: HorizontalBarChart(
              data: chartData,
            ),
          ),
        ),
      ),
    );
  }
}

class HorizontalBarChart extends StatelessWidget {
  final List<Data> data;
  final double gap;

  const HorizontalBarChart({
    Key? key,
    required this.data,
    this.gap = .02,
  }) : super(key: key);

  List<double> get processedStops {
    double totalGapsWith = gap * (data.length - 1);
    double totalData = data.fold(0, (a, b) => a + b.units);
    return data.fold(<double>[0.0], (List<double> l, d) {
      l.add(l.last + d.units * (1 - totalGapsWith) / totalData);
      l.add(l.last);
      l.add(l.last + gap);
      l.add(l.last);
      return l;
    })
      ..removeLast()
      ..removeLast()
      ..removeLast();
  }

  List<Color> get processedColors {
    return data.fold(
        <Color>[],
        (List<Color> l, d) => [
              ...l,
              d.color,
              d.color,
              Colors.transparent,
              Colors.transparent,
            ])
      ..removeLast()
      ..removeLast();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        borderRadius: const BorderRadius.all(
          Radius.circular(500),
        ),
        gradient: LinearGradient(
          begin: Alignment.centerLeft,
          end: Alignment.centerRight,
          stops: processedStops,
          colors: processedColors,
        ),
      ),
    );
  }
}

class Data {
  final double units;
  final Color color;

  Data({required this.units, required this.color});
}

Upvotes: 3

A. Sang
A. Sang

Reputation: 401

List<int> acc = [500, 300, 400, 900, 800];
List<Color> col = [
Colors.red,
Colors.blue,
Colors.orange,
Colors.green,
Colors.pink
];

getSum() {
return acc.reduce((a, b) => a + b);
}

getAccAver(int index) {
return (acc[index] / getSum() * 100).toInt();
}

Padding(
    padding: const EdgeInsets.all(5.0),
    child: SizedBox(
      height: 20,
      width: MediaQuery.of(context).size.width,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          for (var i = 0; i < acc.length; i++)
            CardAccAve(
              percentage: getAccAver(i),
              leftBorder: i == 0 ? 10 : 0,
              rightBorder: i == acc.length - 1 ? 10 : 0,
              color: col[i],
            ),
        ],
      ),
    ),
),

class CardAccAve extends StatelessWidget {
  CardAccAve({
    Key? key,
    required this.leftBorder,
    required this.rightBorder,
    required this.percentage,
    required this.color,
  }) : super(key: key);
  double leftBorder;
  double rightBorder;
  final int percentage;
  Color color;
  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: percentage,
      child: SizedBox(
        height: 20,
        child: Card(
          margin: const EdgeInsets.symmetric(horizontal: 1, vertical: 2),
          color: color,
          elevation: 5,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
              bottomLeft: Radius.circular(leftBorder),
              topLeft: Radius.circular(leftBorder),
              bottomRight: Radius.circular(rightBorder),
              topRight: Radius.circular(rightBorder),
            ),
          ),
        ),
      ),
    );
  }
}

Result

enter image description here

Upvotes: 0

Gilbert Aligoey
Gilbert Aligoey

Reputation: 135

Thanks for the code @ChiragBargoojar, I just added bits of customization and the graph works as how I designed it.

Outcome

If anyone else wondering, here's the code:

class HorizontalBarChart extends StatelessWidget {
  const HorizontalBarChart({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    List<Map<String, dynamic>> chartData = [
      {
        "units": 50,
        "color": cCoffee,
      },
      {
        "units": 10,
        "color": cCyan,
      },
      {
        "units": 70,
        "color": cGreen,
      },
      {
        "units": 100,
        "color": cOrange,
      },
    ];
    double maxWidth = MediaQuery.of(context).size.width - 36;
    var totalUnitNum = 0;
    for (int i = 0; i < chartData.length; i++) {
      totalUnitNum = totalUnitNum + int.parse(chartData[i]["units"].toString());
    }

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 18.0),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(90),
        child: Row(
          children: [
            for (int i = 0; i < chartData.length; i++)
              i == chartData.length - 1
                  ? Expanded(
                      child: SizedBox(
                        height: 16,
                        child: ColoredBox(
                          color: chartData[i]["color"],
                        ),
                      ),
                    )
                  : Row(
                      children: [
                        SizedBox(
                          width:
                              chartData[i]["units"] / totalUnitNum * maxWidth,
                          height: 16,
                          child: ColoredBox(
                            color: chartData[i]["color"],
                          ),
                        ),
                        const SizedBox(width: 6),
                      ],
                    )
          ],
        ),
      ),
    );
  }
}

Upvotes: 2

Related Questions