|
1 | 1 | import 'dart:math'; |
2 | 2 |
|
| 3 | +import 'package:flutter/gestures.dart'; |
3 | 4 | import 'package:flutter/widgets.dart'; |
4 | 5 | import 'package:intl/intl.dart' as intl; |
5 | 6 |
|
@@ -145,80 +146,98 @@ class _InteractiveChartState extends State<InteractiveChart> { |
145 | 146 | final minVol = |
146 | 147 | candlesInRange.map((c) => c.volume).whereType<double>().reduce(min); |
147 | 148 |
|
148 | | - return GestureDetector( |
149 | | - // Tap and hold to view candle details |
150 | | - onTapDown: (details) { |
151 | | - setState(() => _tapPosition = details.localPosition); |
152 | | - }, |
153 | | - onTapCancel: () => setState(() => _tapPosition = null), |
154 | | - onTapUp: (_) { |
155 | | - if (widget.onTap != null) { |
156 | | - _fireOnTapEvent(); // Fire callback event (if needed) |
157 | | - } |
158 | | - setState(() => _tapPosition = null); |
| 149 | + final child = TweenAnimationBuilder( |
| 150 | + tween: PainterParamsTween( |
| 151 | + end: PainterParams( |
| 152 | + candles: candlesInRange, |
| 153 | + style: widget.style, |
| 154 | + size: size, |
| 155 | + candleWidth: _candleWidth, |
| 156 | + startOffset: _startOffset, |
| 157 | + maxPrice: maxPrice, |
| 158 | + minPrice: minPrice, |
| 159 | + maxVol: maxVol, |
| 160 | + minVol: minVol, |
| 161 | + xShift: xShift, |
| 162 | + tapPosition: _tapPosition, |
| 163 | + leadingTrends: leadingTrends, |
| 164 | + trailingTrends: trailingTrends, |
| 165 | + ), |
| 166 | + ), |
| 167 | + duration: Duration(milliseconds: 300), |
| 168 | + curve: Curves.easeOut, |
| 169 | + builder: (_, PainterParams params, __) { |
| 170 | + _prevParams = params; |
| 171 | + return RepaintBoundary( |
| 172 | + child: CustomPaint( |
| 173 | + size: size, |
| 174 | + painter: ChartPainter( |
| 175 | + params: params, |
| 176 | + getTimeLabel: widget.timeLabel ?? defaultTimeLabel, |
| 177 | + getPriceLabel: widget.priceLabel ?? defaultPriceLabel, |
| 178 | + getOverlayInfo: widget.overlayInfo ?? defaultOverlayInfo, |
| 179 | + ), |
| 180 | + ), |
| 181 | + ); |
159 | 182 | }, |
| 183 | + ); |
160 | 184 |
|
161 | | - // Pan and zoom |
162 | | - onScaleStart: (details) { |
163 | | - _prevCandleWidth = _candleWidth; |
164 | | - _prevStartOffset = _startOffset; |
165 | | - _initialFocalPoint = details.localFocalPoint; |
| 185 | + return Listener( |
| 186 | + onPointerSignal: (signal) { |
| 187 | + if (signal is PointerScrollEvent) { |
| 188 | + final dy = signal.scrollDelta.dy; |
| 189 | + if (dy.abs() > 0) { |
| 190 | + _onScaleStart(signal.position); |
| 191 | + _onScaleUpdate( |
| 192 | + dy > 0 ? 0.9 : 1.1, |
| 193 | + signal.position, |
| 194 | + w, |
| 195 | + ); |
| 196 | + } |
| 197 | + } |
166 | 198 | }, |
167 | | - onScaleUpdate: (details) => _onScaleUpdate(details, w), |
168 | | - child: TweenAnimationBuilder( |
169 | | - tween: PainterParamsTween( |
170 | | - end: PainterParams( |
171 | | - candles: candlesInRange, |
172 | | - style: widget.style, |
173 | | - size: size, |
174 | | - candleWidth: _candleWidth, |
175 | | - startOffset: _startOffset, |
176 | | - maxPrice: maxPrice, |
177 | | - minPrice: minPrice, |
178 | | - maxVol: maxVol, |
179 | | - minVol: minVol, |
180 | | - xShift: xShift, |
181 | | - tapPosition: _tapPosition, |
182 | | - leadingTrends: leadingTrends, |
183 | | - trailingTrends: trailingTrends, |
184 | | - ), |
185 | | - ), |
186 | | - duration: Duration(milliseconds: 300), |
187 | | - curve: Curves.easeOut, |
188 | | - builder: (_, PainterParams params, __) { |
189 | | - _prevParams = params; |
190 | | - return RepaintBoundary( |
191 | | - child: CustomPaint( |
192 | | - size: size, |
193 | | - painter: ChartPainter( |
194 | | - params: params, |
195 | | - getTimeLabel: widget.timeLabel ?? defaultTimeLabel, |
196 | | - getPriceLabel: widget.priceLabel ?? defaultPriceLabel, |
197 | | - getOverlayInfo: widget.overlayInfo ?? defaultOverlayInfo, |
198 | | - ), |
199 | | - ), |
200 | | - ); |
| 199 | + child: GestureDetector( |
| 200 | + // Tap and hold to view candle details |
| 201 | + onTapDown: (details) => setState(() { |
| 202 | + _tapPosition = details.localPosition; |
| 203 | + }), |
| 204 | + onTapCancel: () => setState(() => _tapPosition = null), |
| 205 | + onTapUp: (_) { |
| 206 | + setState(() => _tapPosition = null); |
| 207 | + // Fire callback event (if needed) |
| 208 | + if (widget.onTap != null) _fireOnTapEvent(); |
201 | 209 | }, |
| 210 | + // Pan and zoom |
| 211 | + onScaleStart: (details) => _onScaleStart(details.localFocalPoint), |
| 212 | + onScaleUpdate: (details) => |
| 213 | + _onScaleUpdate(details.scale, details.localFocalPoint, w), |
| 214 | + child: child, |
202 | 215 | ), |
203 | 216 | ); |
204 | 217 | }, |
205 | 218 | ); |
206 | 219 | } |
207 | 220 |
|
208 | | - _onScaleUpdate(details, double w) { |
| 221 | + _onScaleStart(Offset focalPoint) { |
| 222 | + _prevCandleWidth = _candleWidth; |
| 223 | + _prevStartOffset = _startOffset; |
| 224 | + _initialFocalPoint = focalPoint; |
| 225 | + } |
| 226 | + |
| 227 | + _onScaleUpdate(double scale, Offset focalPoint, double w) { |
209 | 228 | // Handle zoom |
210 | | - final candleWidth = (_prevCandleWidth * details.scale) |
| 229 | + final candleWidth = (_prevCandleWidth * scale) |
211 | 230 | .clamp(_getMinCandleWidth(w), _getMaxCandleWidth(w)); |
212 | 231 | final clampedScale = candleWidth / _prevCandleWidth; |
213 | 232 | var startOffset = _prevStartOffset * clampedScale; |
214 | 233 | // Handle pan |
215 | | - final dx = (details.localFocalPoint - _initialFocalPoint).dx * -1; |
| 234 | + final dx = (focalPoint - _initialFocalPoint).dx * -1; |
216 | 235 | startOffset += dx; |
217 | 236 | // Adjust pan when zooming |
218 | 237 | final double prevCount = w / _prevCandleWidth; |
219 | 238 | final double currCount = w / candleWidth; |
220 | 239 | final zoomAdjustment = (currCount - prevCount) * candleWidth; |
221 | | - final focalPointFactor = details.localFocalPoint.dx / w; |
| 240 | + final focalPointFactor = focalPoint.dx / w; |
222 | 241 | startOffset -= zoomAdjustment * focalPointFactor; |
223 | 242 | startOffset = startOffset.clamp(0, _getMaxStartOffset(w, candleWidth)); |
224 | 243 | // Fire candle width resize event |
|
0 commit comments