Skip to content

Conversation

@Caffo17
Copy link

@Caffo17 Caffo17 commented Jul 16, 2025

This PR introduces support for custom pattern painters in the BarChart component, allowing each bar to be rendered with a configurable pattern. The following features and improvements are included:

  • Pattern Painters: Added three new CustomPainter classes for bar patterns:
    • StripesPatternPainter: Draws diagonal, vertical, or horizontal stripes at a configurable angle.
    • CirclePoisPatternPainter: Draws a polka dot (circle) pattern.
    • SquarePoisPatternPainter: Draws a polka dot (square) pattern.
  • BarChart Integration: The BarChartRodData now accepts a patternPainter property. If set, the bar will be rendered using the provided pattern.
  • Sample Update: Updated the example (bar_chart_sample1.dart) to allow users to select and preview different bar patterns via a dropdown.
  • Unit Tests: Added unit tests for all new pattern painters, ensuring correct default values, equality, and rendering.
  • Documentation: Updated and translated comments to English for better clarity and maintainability.

Motivation

These changes make it easy to add visual variety to bar charts, supporting use cases such as accessibility, branding, and improved data distinction. The new pattern painters are fully customizable and integrate seamlessly with the existing chart API. Related to this issue.

Showcase

showcase_compressed.mp4

@imaNNeo
Copy link
Owner

imaNNeo commented Aug 1, 2025

Hi, thanks for your contribution. I really like the idea.
It has performance cost (because it iterates over every single part of the pattern).
So I would like to suggest using FragmentShader as they're perfect and performant for patterns.
Can you put some time and investigation into it please?

@Caffo17
Copy link
Author

Caffo17 commented Aug 3, 2025

Hi @imaNNeo, I managed to get the same result using FragmentShaders but I'm not sure how to test them. I'm working on it.

@Caffo17
Copy link
Author

Caffo17 commented Aug 3, 2025

Showcase with shaders

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-08-03.at.19.29.06.mp4

@imaNNeo
Copy link
Owner

imaNNeo commented Aug 21, 2025

That looks amazing! 🤩
I see that now you're initializing all the Shaders inside the main(). So suppose that someone doesn't need to have all of them, or someone doesn't want it at all. I also see that we have FlShaderManager is Singleton.
So we can improve them all using the below suggestion:

  • We can create a parent class called FLSurfacePainter, and it has an abstract initialize() method that the child classes should implement it (it also has the paint() method)
  • Then you can set StripesPatternPainter's parent to FLSurfacePainter. This way, it has to implement the initialize() method (as well as the paint() method)
  • So it loads the shader fragment in it's initialize method, and in our bar_chart_example1 (where we're using these shaders), we can initialize those we want to use in our init method (and implement a loading state for that one until all the shaders are loaded)
  • This way, the user/developer is responsible for loading the pattern/shader painters that they want. (And we're also doing that in our bar_chart_example1)
  • Inside the BarChartRodData, instead of patternPainter, we can have FLSurfacePainter surfacePainter (that is a more generic name)
  • This way, we have 3 built-in shader pattern painters that users can use (and initialize them when they want to use them), they're also able to implement new types of surface painters. So maybe they just want to add a black overlay on top of what has been drawn.
  • So this way, we don't need to have the FlShaderManager anymore.

Please let me know what you think.
I know these are a lot of changes and takes much time to do, but I'm pretty sure they're beneficial for all the community.

Removed FlShaderManager and replaced patternPainter with surfacePainter in BarChartRodData. Refactored pattern painter classes to extend FlSurfacePainter, added initialization logic, and updated related usages and tests. Renamed fl_shader_painter.dart to fl_surface_painter.dart and updated exports. Added dedicated shader tests and removed obsolete manager tests.
@Caffo17
Copy link
Author

Caffo17 commented Aug 29, 2025

Hi @imaNNeo, thanks for your feedback. I've made the changes you asked for.

Let me know what you think about them.

@Caffo17
Copy link
Author

Caffo17 commented Sep 16, 2025

Hi @imaNNeo , sorry for the ping. Could you check the last changes? Thanks 🙏

FlSurfacePainter({required this.flShader});

/// {@macro fl_shader}
final FlShader flShader;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use a generic abstraction for this part, like:

abstract class FlSurfacePainter<S extends FlShader> extends CustomPainter {
  FlSurfacePainter({required this.flShader});

  /// {@macro fl_shader}
  final S flShader;

  /// Initializes the painter by setting up the shader.
  Future<void> initialize() async {
    await flShader.init();
  }

  /// Returns whether the shader is initialized.
  bool get isInitialized => flShader.isInitialized;
}

And also, for the Painters, I think we can remove the shader parameter, we can just instantiate it to make it simpler, like:

class StripesPatternPainter extends FlSurfacePainter<StripesShader> {
  StripesPatternPainter({
    this.color = Colors.black,
    this.width = 2,
    this.gap = 4,
    this.angle = 45,
  }) : super(flShader: StripesShader());

And from the user's perspective, it can be like:

final stripesPainter = StripesPatternPainter(
width: 2,
gap: 8,
angle: 45,
);

@override
void initState() {
super.initState();
initializeShaders();
}

Future<void> initializeShaders() async {
await stripesPainter.initialize();
}

So I think this change improves the developer experience and makes it more expandable

Copy link
Author

@Caffo17 Caffo17 Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach makes the flShader initialization untestable.
We can inject a mock shader via an optional parameter, falling back to the default implementation when not provided:

StripesPatternPainter({
    @visibleForTesting StripesShader? mockShader,
    this.color = Colors.black,
    this.width = 2,
    this.gap = 4,
    this.angle = 45,
  }) : super(flShader: mockShader ?? StripesShader());

What do you think?

@imaNNeo
Copy link
Owner

imaNNeo commented Oct 26, 2025

And lastly, I think we can also add the surfacePainter property in the BarChartRodStackItem as well. So this way pepole can have different patterns for different parts of the bar.
Just like what you see here: #2009

@Caffo17
Copy link
Author

Caffo17 commented Oct 28, 2025

Hi @imaNNeo, You can check the changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants