Hi,
When using a `ColumnSeries`in a `SfCartesionChart`with a `primaryXAxis: DateTimeAxis()`, is there a way to fix the start and end of the columns, or their width?
They seem to center by default on the provided date value from `xValueMapper`, and be as wide as the smallest `x` distance between data points, which is fine, until I try to add another Serie to stack on the same graph. At that point, the second serie messes up the columns width.
Example code:
In this example code, if you uncomment one or several lines from the `pointsData`, you will see the columns width changing. Why is this happening and how can we solve this?
Thank you,
Louis
Just the Column Serie:
Same Column Serie with a BubbleSerie added:
Hi Louis Deveseleer,
We have analyzed your query and to achieve your requirement, you can customize the rendering of the column series with the fixed width using the ColumnSeriesRenderer and ColumnSegment.
In the ColumnSeries, the width of the column will be calculated
based on the available size and axis range and it is the current behavior of
the SfCartesianChart. However, To render all the column with equal width, you
can render the column with the fixed width in the ColumnSeriesRenderer by
creating a renderer using the onCreateRenderer
callback.
Disclaimer: However, while rendering the column with the fixed width, the column segment might overlap with the other column segment.
Kindly refer the code snippet below:
|
class GrowthGraphTest extends StatelessWidget { const GrowthGraphTest({super.key});
@override Widget build(BuildContext context) { final now = DateTime.now(); final columnData = <GraphDataPoint>[ GraphDataPoint(now, 7), GraphDataPoint(now.subtract(const Duration(days: 7)), 12), GraphDataPoint(now.subtract(const Duration(days: 14)), 5), GraphDataPoint(now.subtract(const Duration(days: 21)), 7), ]; final pointsData = <GraphDataPoint>[ GraphDataPoint(now.subtract(const Duration(days: 10)), 10), GraphDataPoint(now.subtract(const Duration(days: 9)), 8), GraphDataPoint(now.subtract(const Duration(days: 11)), 6), ]; return MaterialApp( home: Scaffold( body: SizedBox( height: 300, child: SfCartesianChart( series: <ChartSeries>[ ColumnSeries( dataSource: columnData, xValueMapper: (datum, _) => datum.date, yValueMapper: (datum, _) => datum.value, onCreateRenderer: (series) { return CustomColumnSeriesRenderer(); }, ), BubbleSeries<GraphDataPoint, DateTime>( dataSource: pointsData, xValueMapper: (datum, _) => datum.date, yValueMapper: (datum, _) => datum.value, ), ], primaryXAxis: DateTimeAxis(), primaryYAxis: NumericAxis(), ), ), ), ); } }
class CustomColumnSeriesRenderer extends ColumnSeriesRenderer { @override ChartSegment createSegment() { return CustomColumnSegment(); } }
class CustomColumnSegment extends ColumnSegment { final double width = 50; @override void onPaint(Canvas canvas) { final double centerX = segmentRect.center.dx; final double left = centerX - (width / 2); final double right = centerX + (width / 2); final Paint paint = Paint();
paint ..color = Colors.blue ..style = PaintingStyle.fill;
final Rect rect = Rect.fromLTRB(left, segmentRect.top, right, segmentRect.bottom);
final RRect customRect = RRect.fromRectAndRadius(rect, Radius.zero);
canvas.drawRRect(customRect, paint); } } |
Snapshot:
Column Series:
Column Series with Bubble Series:
Also attached the sample below for your reference.
Regards,
Hari Hara Sudhan. K
Hello Hari Hara Sudhan. K,
Thank you for your answer! This should work as a workaround but the Custom renderer is overriding all the styling that we would otherwise put inside the `ColumnSeries`, and the calculation of the column width in pixels will be complicated, even with fixed `visibleMinimum` and `visibleMaximum` for the `XAxis`, since `SfCartesianChart` determines itself the width taken by the axis labels...
Is there no way to force the graph to respect the `ColumnSeries.width` parameter with respect to the `ColumnSeries.dataSource`, instead of letting data from other series mess things up? This would make things a lot easier. Even an extra parameter to use a fixed width with respect to the `XAxis` (in my case a DateTime range) would be a lot easier than override everything at once.
Please let me know if there is a solution like this available!
Thank you,
Louis
Another question related to your answer:
I am calculating the column width in the widget containing the graph, and passing that value down to the `ColumnSeriesRender`. But if the width value gets updated (and the widget rebuilds), the `ColumnSeriesRender` does not update itself: it keeps the first width value that was passed to it.
Is there access to some sort of `shouldRepaint` method, like we have in the `CustomPainter`, to tell the Renderer to refresh when some parameter changes? I could not find any but maybe I missed it.
Alternatively, would there be a way to overlay widgets on the graph, at specified (x,y) positions ? This would also solve my original issue.
Hi Louis Deveseleer,
To achieve your requirement, you can render the column series in the primaryXAxis and primaryYAxis, and you can render another series in the secondary x and y axes using the axes property. You can also render the secondary x and y axis in the opposite direction using the opposedPosition property.
While rendering the column series in the primaryXAxis and primaryYAxis, the actual range of the primary x and y axes will be calculated based on the data source of the column series only. Hence, the axis range of the column series will not be changed while adding another respective series and thus the width of the column will be static.
You can overlay the widgets on the graph, at the specified x and y position using the annotations property in the SfCartesianChart.
If you have further queries, please get back to us.
Regards,
Hari Hara Sudhan. K
Thank you!
Hi Louis Deveseleer,
Most Welcome. Kindly get back to us if you have further queries. We are always happy to assist you.
Regards,
Hari Hara Sudhan. K