-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Draft: add FlDotImagePainter for custom image dot markers #2049
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import 'package:fl_chart/fl_chart.dart'; | ||
| import 'package:flutter/material.dart'; | ||
|
|
||
| /// LineChartSample14 demonstrates how to use FlDotImagePainter | ||
| /// to display custom images as dot markers on a line chart. | ||
| /// | ||
| /// This sample shows: | ||
| /// - Loading images asynchronously before creating the chart | ||
| /// - Using FlDotImagePainter with custom image assets | ||
| /// - Handling loading states while images are being loaded | ||
| class LineChartSample14 extends StatefulWidget { | ||
| const LineChartSample14({super.key}); | ||
|
|
||
| @override | ||
| State<LineChartSample14> createState() => _LineChartSample14State(); | ||
| } | ||
|
|
||
| class _LineChartSample14State extends State<LineChartSample14> { | ||
| /// The custom dot painter that will render images at each data point. | ||
| /// Null until the image is loaded asynchronously. | ||
| FlDotImagePainter? _dotPainter; | ||
|
|
||
| @override | ||
| void initState() { | ||
| super.initState(); | ||
| _loadImage(); | ||
| } | ||
|
|
||
| /// Loads the image asset and creates a FlDotImagePainter. | ||
| /// | ||
| /// This must be done asynchronously before the chart can be rendered | ||
| /// because the draw() method is synchronous and cannot load images on-demand. | ||
| Future<void> _loadImage() async { | ||
| final image = await FlDotImagePainter.loadImageFromAsset( | ||
| 'assets/icons/image_annotation.png', | ||
| ); | ||
| setState(() { | ||
| _dotPainter = FlDotImagePainter(image: image, size: 20.0); | ||
| }); | ||
| } | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| // Show a loading indicator while the image is being loaded | ||
| if (_dotPainter == null) { | ||
| return const Center(child: CircularProgressIndicator()); | ||
| } | ||
|
|
||
| return AspectRatio( | ||
| aspectRatio: 1.5, | ||
| child: Padding( | ||
| padding: const EdgeInsets.all(16), | ||
| child: LineChart( | ||
| LineChartData( | ||
| // Define the chart boundaries | ||
| minX: 0, | ||
| maxX: 6, | ||
| minY: 0, | ||
| maxY: 6, | ||
| // Configure axis titles | ||
| titlesData: FlTitlesData( | ||
| show: true, | ||
| bottomTitles: AxisTitles( | ||
| sideTitles: SideTitles( | ||
| showTitles: true, | ||
| reservedSize: 30, | ||
| interval: 1, | ||
| ), | ||
| ), | ||
| leftTitles: AxisTitles( | ||
| sideTitles: SideTitles( | ||
| showTitles: true, | ||
| reservedSize: 40, | ||
| interval: 1, | ||
| ), | ||
| ), | ||
| topTitles: const AxisTitles( | ||
| sideTitles: SideTitles(showTitles: false), | ||
| ), | ||
| rightTitles: const AxisTitles( | ||
| sideTitles: SideTitles(showTitles: false), | ||
| ), | ||
| ), | ||
| gridData: const FlGridData(show: true), | ||
| borderData: FlBorderData( | ||
| show: true, | ||
| border: Border.all(color: Colors.grey), | ||
| ), | ||
| lineBarsData: [ | ||
| LineChartBarData( | ||
| spots: const [ | ||
| FlSpot(0, 3), | ||
| FlSpot(1, 1), | ||
| FlSpot(2, 4), | ||
| FlSpot(3, 2), | ||
| FlSpot(4, 5), | ||
| FlSpot(5, 3), | ||
| FlSpot(6, 4), | ||
| ], | ||
| isCurved: true, | ||
| color: Colors.blue, | ||
| barWidth: 3, | ||
| // Use FlDotImagePainter to display custom images at each data point | ||
| dotData: FlDotData( | ||
| show: true, | ||
| getDotPainter: (spot, percent, barData, index) { | ||
| // Return the pre-loaded image painter for each dot | ||
| return _dotPainter!; | ||
| }, | ||
| ), | ||
| belowBarData: BarAreaData( | ||
| show: true, | ||
| color: Colors.blue.withValues(alpha: 0.3), | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,6 +9,7 @@ import 'package:fl_chart/src/utils/canvas_wrapper.dart'; | |||||||||||||||||||||
| import 'package:fl_chart/src/utils/lerp.dart'; | ||||||||||||||||||||||
| import 'package:fl_chart/src/utils/utils.dart'; | ||||||||||||||||||||||
| import 'package:flutter/material.dart' hide Image; | ||||||||||||||||||||||
| import 'package:flutter/services.dart'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// This is the base class for axis base charts data | ||||||||||||||||||||||
| /// that contains a [FlGridData] that holds data for showing grid lines, | ||||||||||||||||||||||
|
|
@@ -1466,6 +1467,73 @@ abstract class FlDotPainter with EquatableMixin { | |||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// This class is an implementation of a [FlDotPainter] that draws | ||||||||||||||||||||||
| /// an image as the dot marker | ||||||||||||||||||||||
| class FlDotImagePainter extends FlDotPainter { | ||||||||||||||||||||||
| /// Creates an image dot painter. | ||||||||||||||||||||||
| /// | ||||||||||||||||||||||
| /// [image] must be loaded before creating this painter. | ||||||||||||||||||||||
| /// Use [loadImageFromAsset] to load images from assets. | ||||||||||||||||||||||
| FlDotImagePainter({ | ||||||||||||||||||||||
| required this.image, | ||||||||||||||||||||||
| this.size = 24.0, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// The image to draw as the dot marker | ||||||||||||||||||||||
| final Image image; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// The size of the dot (width and height) | ||||||||||||||||||||||
| final double size; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// Loads an image from asset path and returns a [Image] object. | ||||||||||||||||||||||
| /// | ||||||||||||||||||||||
| /// Example: | ||||||||||||||||||||||
| /// ```dart | ||||||||||||||||||||||
| /// final image = await FlDotImagePainter.loadImageFromAsset('assets/dot.png'); | ||||||||||||||||||||||
| /// final painter = FlDotImagePainter(image: image, size: 20.0); | ||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||
| static Future<Image> loadImageFromAsset(String assetPath) async { | ||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. loadImageFromAsset doesn't belong in the painter.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw, it seems instantiateImageCodec() is deprecated in Flutter 3.x. So please make sure you're using the new approach to load the image |
||||||||||||||||||||||
| final byteData = await rootBundle.load(assetPath); | ||||||||||||||||||||||
| final codec = await instantiateImageCodec(byteData.buffer.asUint8List()); | ||||||||||||||||||||||
| final frame = await codec.getNextFrame(); | ||||||||||||||||||||||
| return frame.image; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @override | ||||||||||||||||||||||
| void draw(Canvas canvas, FlSpot spot, Offset offsetInCanvas) { | ||||||||||||||||||||||
| // Center the image at the offset | ||||||||||||||||||||||
| final drawOffset = offsetInCanvas - Offset(size / 2, size / 2); | ||||||||||||||||||||||
| final rect = Rect.fromLTWH(drawOffset.dx, drawOffset.dy, size, size); | ||||||||||||||||||||||
| paintImage( | ||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use canvasWrapper.drawImage() instead of paintImage. This way, we keep the consistency and testability. Just like here: fl_chart/lib/src/chart/base/axis_chart/axis_chart_painter.dart Lines 296 to 305 in c83aa90
|
||||||||||||||||||||||
| canvas: canvas, | ||||||||||||||||||||||
| rect: rect, | ||||||||||||||||||||||
| image: image, | ||||||||||||||||||||||
| fit: BoxFit.contain, | ||||||||||||||||||||||
| filterQuality: FilterQuality.high, | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @override | ||||||||||||||||||||||
| Color get mainColor => Colors.transparent; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @override | ||||||||||||||||||||||
| Size getSize(FlSpot spot) => Size(size, size); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @override | ||||||||||||||||||||||
| FlDotPainter lerp(FlDotPainter a, FlDotPainter b, double t) { | ||||||||||||||||||||||
| if (a is! FlDotImagePainter || b is! FlDotImagePainter) { | ||||||||||||||||||||||
| return b; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return FlDotImagePainter( | ||||||||||||||||||||||
| image: b.image, | ||||||||||||||||||||||
| size: lerpDouble(a.size, b.size, t) ?? b.size, | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @override | ||||||||||||||||||||||
| List<Object?> get props => [image, size]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// This class is an implementation of a [FlDotPainter] that draws | ||||||||||||||||||||||
| /// a circled shape | ||||||||||||||||||||||
| class FlDotCirclePainter extends FlDotPainter { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need these kinds of comments. Please remove the unnecessary comments.