Skip to content

Commit f3a5758

Browse files
fix: Clip only the enabled FlClipData sides in scatter and candlestick charts (#2108)
# Description Same bug as #1262 (fixed for `LineChart` in #2107), but in `ScatterChart` and `CandlestickChart`. The clip blocks built a single rect and passed it to `clipRect`, which always clips all four sides. Disabled sides were left at the canvas edge, so they still clipped overflow. As a result, enabling any single side in `FlClipData` clipped all four sides. Disabled sides now extend to infinity, leaving them unbounded in `clipRect`; only the sides explicitly enabled in `FlClipData` get a real boundary. Mirrors the line chart fix in #2107. ## Checklist - [x] I have followed the [Contributor Guide] when preparing my PR. - [x] I have updated/added tests for ALL new/updated/fixed functionality. - [-] I have updated/added relevant documentation and added dartdoc comments with `///`. - [-] I have updated/added relevant examples in `example`. ## Breaking Change? - [ ] Yes, this PR is a breaking change. - [x] No, this PR is not a breaking change. ## Related Issues Related to #1262 and #2107 (same root cause, other chart types). [Contributor Guide]: https://github.com/imaNNeo/fl_chart/blob/main/CONTRIBUTING.md Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a9147f0 commit f3a5758

4 files changed

Lines changed: 117 additions & 8 deletions

File tree

lib/src/chart/candlestick_chart/candlestick_chart_painter.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,13 @@ class CandlestickChartPainter extends AxisChartPainter<CandlestickChartData> {
7373
_clipPaint,
7474
);
7575

76-
var left = 0.0;
77-
var top = 0.0;
78-
var right = viewSize.width;
79-
var bottom = viewSize.height;
76+
// Sides that aren't enabled in [clip] must stay unclipped. We extend
77+
// their boundary to infinity so [Canvas.clipRect] only clips the enabled
78+
// sides (otherwise enabling any single side would clip all four).
79+
var left = double.negativeInfinity;
80+
var top = double.negativeInfinity;
81+
var right = double.infinity;
82+
var bottom = double.infinity;
8083

8184
if (clip.left) {
8285
final borderWidth = border?.left.width ?? 0;

lib/src/chart/scatter_chart/scatter_chart_painter.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@ class ScatterChartPainter extends AxisChartPainter<ScatterChartData> {
7979
_clipPaint,
8080
);
8181

82-
var left = 0.0;
83-
var top = 0.0;
84-
var right = viewSize.width;
85-
var bottom = viewSize.height;
82+
// Sides that aren't enabled in [clip] must stay unclipped. We extend
83+
// their boundary to infinity so [Canvas.clipRect] only clips the enabled
84+
// sides (otherwise enabling any single side would clip all four).
85+
var left = double.negativeInfinity;
86+
var top = double.negativeInfinity;
87+
var right = double.infinity;
88+
var bottom = double.infinity;
8689

8790
if (clip.left) {
8891
final borderWidth = border?.left.width ?? 0;

test/chart/candlestick_chart/candlestick_chart_painter_test.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,63 @@ void main() {
5454
verify(mockCanvas.drawLine(any, any, any)).called(6);
5555
Utils.changeInstance(utilsMainInstance);
5656
});
57+
58+
test('enabling one clip side does not clip the others (#1262)', () {
59+
final utilsMainInstance = Utils();
60+
const viewSize = Size(400, 400);
61+
final data = CandlestickChartData(
62+
candlestickSpots: [
63+
candlestickSpot1,
64+
candlestickSpot2,
65+
candlestickSpot3,
66+
],
67+
borderData: FlBorderData(show: true, border: Border.all(width: 8)),
68+
clipData: const FlClipData(
69+
top: false,
70+
bottom: false,
71+
left: true,
72+
right: false,
73+
),
74+
);
75+
76+
final candlestickPainter = CandlestickChartPainter();
77+
final holder = PaintHolder<CandlestickChartData>(
78+
data,
79+
data,
80+
TextScaler.noScaling,
81+
);
82+
83+
final mockUtils = MockUtils();
84+
Utils.changeInstance(mockUtils);
85+
when(mockUtils.getEfficientInterval(any, any))
86+
.thenAnswer((realInvocation) => 1.0);
87+
when(mockUtils.getBestInitialIntervalValue(any, any, any))
88+
.thenAnswer((realInvocation) => 1.0);
89+
final mockBuildContext = MockBuildContext();
90+
final mockCanvas = MockCanvas();
91+
final canvasWrapper = CanvasWrapper(mockCanvas, viewSize);
92+
candlestickPainter.paint(
93+
mockBuildContext,
94+
canvasWrapper,
95+
holder,
96+
);
97+
98+
final verifyResult = verify(
99+
mockCanvas.clipRect(
100+
captureAny,
101+
clipOp: anyNamed('clipOp'),
102+
doAntiAlias: anyNamed('doAntiAlias'),
103+
),
104+
);
105+
final rect = verifyResult.captured.single as Rect;
106+
verifyResult.called(1);
107+
// Only the left side is clipped; the other three stay unbounded.
108+
expect(rect.left, 4);
109+
expect(rect.top, double.negativeInfinity);
110+
expect(rect.right, double.infinity);
111+
expect(rect.bottom, double.infinity);
112+
Utils.changeInstance(utilsMainInstance);
113+
});
57114
});
58115

59116
group('drawAxisSpotIndicator()', () {

test/chart/scatter_chart/scatter_chart_painter_test.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,52 @@ void main() {
268268

269269
verify(mockCanvasWrapper.clipRect(any)).called(1);
270270
});
271+
272+
test('enabling one clip side does not clip the others (#1262)', () {
273+
const viewSize = Size(100, 100);
274+
275+
final data = ScatterChartData(
276+
minY: 0,
277+
maxY: 10,
278+
minX: 0,
279+
maxX: 10,
280+
scatterSpots: [ScatterSpot(1, 1)],
281+
titlesData: const FlTitlesData(show: false),
282+
borderData: FlBorderData(show: true, border: Border.all(width: 8)),
283+
clipData: const FlClipData(
284+
top: false,
285+
bottom: false,
286+
left: true,
287+
right: false,
288+
),
289+
);
290+
291+
final scatterChartPainter = ScatterChartPainter();
292+
final holder = PaintHolder<ScatterChartData>(
293+
data,
294+
data,
295+
TextScaler.noScaling,
296+
);
297+
298+
final mockBuildContext = MockBuildContext();
299+
final mockCanvasWrapper = MockCanvasWrapper();
300+
when(mockCanvasWrapper.size).thenReturn(viewSize);
301+
when(mockCanvasWrapper.canvas).thenReturn(MockCanvas());
302+
scatterChartPainter.drawSpots(
303+
mockBuildContext,
304+
mockCanvasWrapper,
305+
holder,
306+
);
307+
308+
final verifyResult = verify(mockCanvasWrapper.clipRect(captureAny));
309+
final rect = verifyResult.captured.single as Rect;
310+
verifyResult.called(1);
311+
// Only the left side is clipped; the other three stay unbounded.
312+
expect(rect.left, 4);
313+
expect(rect.top, double.negativeInfinity);
314+
expect(rect.right, double.infinity);
315+
expect(rect.bottom, double.infinity);
316+
});
271317
});
272318

273319
group('drawTooltips()', () {

0 commit comments

Comments
 (0)