Repository: imaNNeo/fl_chart (dart)

Issue:
Include gradient field to BarChartRodStackItem


I can see I can give a gradient color to bar chart. But, if there are multiple stacked bar charts, there is no way to give a gradient for each **BarChartRodStackItem**.  It would be nice to have this feature!

Pull request:
Resolves #919 

This PR adds a `gradient` property on `BarChartRodStackItem`, which displays a stacked chart rod with a gradient if provided.

Saw there was an attempt at this already, but no activity on that for a few weeks now and I'm needing this feature and impatient 😛 

Unfortunately [dart doesn't support both optional positional arguments and named arguments](https://github.com/dart-lang/language/issues/1076), so the constructor signature is not ideal, however this avoids a breaking change as opposed to updating `color` to a named argument. If there's a better way, or we make that breaking change, let me know.

I've also updated a sample bar chart to show the effect.

<img width="389" height="508" alt="Screenshot 2025-08-26 at 11 09 26 pm" src="https://github.com/user-attachments/assets/95f2d26b-fdd3-4740-99da-734121105438" />

Patch:
diff --git a/example/lib/presentation/samples/bar/bar_chart_sample5.dart b/example/lib/presentation/samples/bar/bar_chart_sample5.dart
index a0f73df81..c32566590 100644
--- a/example/lib/presentation/samples/bar/bar_chart_sample5.dart
+++ b/example/lib/presentation/samples/bar/bar_chart_sample5.dart
@@ -169,7 +169,16 @@ class BarChartSample5State extends State<BarChartSample5> {
             BarChartRodStackItem(
               0,
               value1,
-              AppColors.contentColorGreen,
+              null,
+              gradient: LinearGradient(colors: [
+                AppColors.contentColorWhite.withValues(alpha: 0.8),
+                AppColors.contentColorGreen.withValues(alpha: 0.5),
+                AppColors.contentColorCyan.withValues(alpha: 0.2),
+              ], stops: const [
+                0.1,
+                0.4,
+                0.9
+              ], begin: Alignment.topLeft, end: Alignment.bottomRight),
               label: isTouched ? 'A' : null,
               borderSide: BorderSide(
                 color: Colors.white,
@@ -189,7 +198,17 @@ class BarChartSample5State extends State<BarChartSample5> {
             BarChartRodStackItem(
               value1 + value2,
               value1 + value2 + value3,
-              AppColors.contentColorPink,
+              null,
+              gradient: LinearGradient(
+                colors: [
+                  AppColors.contentColorPurple,
+                  AppColors.contentColorRed.withValues(alpha: 0.9),
+                  AppColors.contentColorOrange.withValues(alpha: 0.8),
+                ],
+                stops: const [0, 0.5, 1],
+                begin: Alignment.topLeft,
+                end: Alignment.bottomRight,
+              ),
               label: isTouched ? 'C' : null,
               borderSide: BorderSide(
                 color: Colors.white,
@@ -225,8 +244,19 @@ class BarChartSample5State extends State<BarChartSample5> {
             BarChartRodStackItem(
               0,
               -value1,
-              AppColors.contentColorGreen.withValues(
-                  alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
+              null,
+              gradient: LinearGradient(colors: [
+                AppColors.contentColorWhite.withValues(
+                    alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
+                AppColors.contentColorGreen.withValues(
+                    alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
+                AppColors.contentColorCyan.withValues(
+                    alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
+              ], stops: const [
+                0.1,
+                0.4,
+                0.9
+              ], begin: Alignment.topLeft, end: Alignment.bottomRight),
               label: isTouched ? 'A' : null,
               borderSide: const BorderSide(color: Colors.transparent),
             ),
@@ -241,8 +271,24 @@ class BarChartSample5State extends State<BarChartSample5> {
             BarChartRodStackItem(
               -(value1 + value2),
               -(value1 + value2 + value3),
-              AppColors.contentColorPink.withValues(
-                  alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
+              null,
+              gradient: LinearGradient(
+                colors: [
+                  AppColors.contentColorPurple.withValues(
+                      alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
+                  AppColors.contentColorRed.withValues(
+                      alpha: isTouched
+                          ? (shadowOpacity * 2) - 0.1
+                          : shadowOpacity),
+                  AppColors.contentColorOrange.withValues(
+                      alpha: isTouched
+                          ? (shadowOpacity * 2) - 0.2
+                          : shadowOpacity),
+                ],
+                stops: const [0, 0.5, 1],
+                begin: Alignment.topLeft,
+                end: Alignment.bottomRight,
+              ),
               label: isTouched ? 'C' : null,
               borderSide: const BorderSide(color: Colors.transparent),
             ),
diff --git a/lib/src/chart/bar_chart/bar_chart_data.dart b/lib/src/chart/bar_chart/bar_chart_data.dart
index 78b711b21..6a9990b2d 100644
--- a/lib/src/chart/bar_chart/bar_chart_data.dart
+++ b/lib/src/chart/bar_chart/bar_chart_data.dart
@@ -471,7 +471,7 @@ class BarChartRodData with EquatableMixin {
 /// Each [BarChartRodData] can have a list of [BarChartRodStackItem] (with different colors
 /// and position) to represent a Stacked Chart rod,
 class BarChartRodStackItem with EquatableMixin {
-  /// Renders a section of Stacked Chart from [fromY] to [toY] with [color]
+  /// Renders a section of Stacked Chart from [fromY] to [toY] with [color] or [gradient]
   /// for example if you want to have a Stacked Chart with three colors:
   /// ```dart
   /// BarChartRodData(
@@ -484,14 +484,19 @@ class BarChartRodStackItem with EquatableMixin {
   ///   ]
   /// )
   /// ```
+  /// To use the [gradient], set [color] to null
   BarChartRodStackItem(
     this.fromY,
     this.toY,
     this.color, {
+    this.gradient,
     this.label,
     this.labelStyle,
     this.borderSide = Utils.defaultBorderSide,
-  });
+  }) : assert(
+          color != null || gradient != null,
+          'You must provide either a color or gradient',
+        );
   final String? label;
   final TextStyle? labelStyle;
 
@@ -502,7 +507,10 @@ class BarChartRodStackItem with EquatableMixin {
   final double toY;
 
   /// Renders a Stacked Chart section with [color]
-  final Color color;
+  final Color? color;
+
+  /// Renders a Stacked Chart section with [gradient]
+  final Gradient? gradient;
 
   /// Renders border stroke for a Stacked Chart section
   final BorderSide borderSide;
@@ -513,6 +521,7 @@ class BarChartRodStackItem with EquatableMixin {
     double? fromY,
     double? toY,
     Color? color,
+    Gradient? gradient,
     String? label,
     TextStyle? labelStyle,
     BorderSide? borderSide,
@@ -521,6 +530,7 @@ class BarChartRodStackItem with EquatableMixin {
         fromY ?? this.fromY,
         toY ?? this.toY,
         color ?? this.color,
+        gradient: gradient ?? this.gradient,
         label: label ?? this.label,
         labelStyle: labelStyle ?? this.labelStyle,
         borderSide: borderSide ?? this.borderSide,
@@ -535,7 +545,8 @@ class BarChartRodStackItem with EquatableMixin {
       BarChartRodStackItem(
         lerpDouble(a.fromY, b.fromY, t)!,
         lerpDouble(a.toY, b.toY, t)!,
-        Color.lerp(a.color, b.color, t)!,
+        Color.lerp(a.color, b.color, t),
+        gradient: Gradient.lerp(a.gradient, b.gradient, t),
         label: b.label,
         labelStyle: b.labelStyle,
         borderSide: BorderSide.lerp(a.borderSide, b.borderSide, t),
@@ -543,7 +554,8 @@ class BarChartRodStackItem with EquatableMixin {
 
   /// Used for equality check, see [EquatableMixin].
   @override
-  List<Object?> get props => [fromY, toY, color, label, labelStyle, borderSide];
+  List<Object?> get props =>
+      [fromY, toY, color, gradient, label, labelStyle, borderSide];
 }
 
 /// Holds values to draw a rod in rear of the main rod.
diff --git a/lib/src/chart/bar_chart/bar_chart_painter.dart b/lib/src/chart/bar_chart/bar_chart_painter.dart
index 1e7b91e66..46f34de40 100644
--- a/lib/src/chart/bar_chart/bar_chart_painter.dart
+++ b/lib/src/chart/bar_chart/bar_chart_painter.dart
@@ -311,10 +311,14 @@ class BarChartPainter extends AxisChartPainter<BarChartData> {
               final stackToY = getPixelY(stackItem.toY, viewSize, holder);
 
               final isNegative = stackItem.toY < stackItem.fromY;
-              _barPaint.color = stackItem.color;
               final rect = isNegative
                   ? Rect.fromLTRB(left, stackFromY, right, stackToY)
                   : Rect.fromLTRB(left, stackToY, right, stackFromY);
+              _barPaint.setColorOrGradient(
+                stackItem.color,
+                stackItem.gradient,
+                rect,
+              );
               canvasWrapper
                 ..save()
                 ..clipRect(rect)
diff --git a/repo_files/documentations/bar_chart.md b/repo_files/documentations/bar_chart.md
index 2902833ec..fc4781a8b 100644
--- a/repo_files/documentations/bar_chart.md
+++ b/repo_files/documentations/bar_chart.md
@@ -77,6 +77,7 @@ enum values {`start`, `end`, `center`, `spaceEvenly`, `spaceAround`, `spaceBetwe
 |fromY|draw stack item from this value|null|
 |toY|draw stack item to this value|null|
 |color|color of the stack item|null|
+|gradient|gradient of the stack item|null|
 |label|optional text label for the stack item|null|
 |labelStyle|optional TextStyle for the label|null|
 |borderSide|draw border stroke for each stack item|null|


Test patch:
diff --git a/test/chart/bar_chart/bar_chart_painter_test.dart b/test/chart/bar_chart/bar_chart_painter_test.dart
index f645ad4ac..131751f83 100644
--- a/test/chart/bar_chart/bar_chart_painter_test.dart
+++ b/test/chart/bar_chart/bar_chart_painter_test.dart
@@ -1,3 +1,5 @@
+import 'dart:ui' as ui show Gradient;
+
 import 'package:fl_chart/fl_chart.dart';
 import 'package:fl_chart/src/chart/bar_chart/bar_chart_helper.dart';
 import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart';
@@ -1277,7 +1279,10 @@ void main() {
                 BarChartRodStackItem(
                   5,
                   10,
-                  const Color(0x44444444),
+                  null,
+                  gradient: const LinearGradient(
+                    colors: [Color(0xFFFF0000), Color(0xFF00FF00)],
+                  ),
                   label: '5',
                 ),
               ],
@@ -1286,7 +1291,13 @@ void main() {
         ),
       ];
 
-      final data = BarChartData(barGroups: barGroups);
+      final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups);
+
+      final data = BarChartData(
+        barGroups: barGroups,
+        minY: minY,
+        maxY: maxY,
+      );
 
       final barChartPainter = BarChartPainter();
       final holder =
@@ -1309,6 +1320,7 @@ void main() {
         final paint = inv.positionalArguments[1] as Paint;
         results.add({
           'paint_color': paint.color,
+          'gradient': paint.shader is ui.Gradient,
         });
       });
 
@@ -1326,9 +1338,17 @@ void main() {
         results[3]['paint_color'],
         isSameColorAs(const Color(0x33333333)),
       );
+      expect(
+        results[3]['gradient'],
+        false,
+      );
       expect(
         results[4]['paint_color'],
-        isSameColorAs(const Color(0x44444444)),
+        isSameColorAs(const Color(0xFF000000)),
+      );
+      expect(
+        results[4]['gradient'],
+        true,
       );
     });
 
@@ -3418,4 +3438,10 @@ void main() {
       ).called(2);
     });
   });
+
+  group('BarChartRodStackItem()', () {
+    test('throws an exception if color and gradient is null', () {
+      expect(() => BarChartRodStackItem(0, 10, null), throwsAssertionError);
+    });
+  });
 }