Skip to content

Commit 0ecba93

Browse files
feat: draggable barrier - relative and absolute
1 parent 0296765 commit 0ecba93

File tree

13 files changed

+587
-94
lines changed

13 files changed

+587
-94
lines changed

lib/src/deriv_chart/chart/basic_chart.dart

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class BasicChartState<T extends BasicChart> extends State<T>
107107
}
108108
}
109109

110+
bool _canScaleVertically = true;
111+
110112
double get _topPadding => verticalPadding;
111113

112114
double get _bottomPadding => verticalPadding;
@@ -260,6 +262,26 @@ class BasicChartState<T extends BasicChart> extends State<T>
260262
bottomPadding: _bottomPadding,
261263
);
262264

265+
/// Converts the chart quote to y axis value inside the canvas.
266+
double chartQuoteFromCanvasY(double y) => quoteFromCanvasY(
267+
y: y,
268+
topBoundQuote: _topBoundQuote,
269+
bottomBoundQuote: _bottomBoundQuote,
270+
canvasHeight: canvasSize?.height ?? 200,
271+
topPadding: _topPadding,
272+
bottomPadding: _bottomPadding,
273+
);
274+
275+
/// Enables vertical scroll.
276+
void enableVerticalScale() {
277+
_canScaleVertically = true;
278+
}
279+
280+
/// Disables vertical scroll.
281+
void disableVerticalScale() {
282+
_canScaleVertically = false;
283+
}
284+
263285
@override
264286
Widget build(BuildContext context) => LayoutBuilder(
265287
key: _key,
@@ -373,7 +395,8 @@ class BasicChartState<T extends BasicChart> extends State<T>
373395

374396
void _onPanUpdate(DragUpdateDetails details) {
375397
if (_panStartedOnQuoteLabelsArea &&
376-
_onQuoteLabelsTouchArea(details.globalPosition)) {
398+
_onQuoteLabelsTouchArea(details.globalPosition) &&
399+
_canScaleVertically) {
377400
_scaleVertically(details.delta.dy);
378401
}
379402
}

lib/src/deriv_chart/chart/chart.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Chart extends StatefulWidget {
2929
this.dataFitEnabled = false,
3030
this.opacity = 1.0,
3131
this.annotations,
32+
this.barriers,
3233
Key? key,
3334
}) : super(key: key);
3435

@@ -45,6 +46,9 @@ class Chart extends StatefulWidget {
4546
/// Open position marker series.
4647
final MarkerSeries? markerSeries;
4748

49+
/// The list that holds the stateful barriers.
50+
final List<Barrier>? barriers;
51+
4852
/// Chart's controller
4953
final ChartController? controller;
5054

@@ -152,6 +156,7 @@ class _ChartState extends State<Chart> with WidgetsBindingObserver {
152156
mainSeries: widget.mainSeries,
153157
overlaySeries: widget.overlaySeries,
154158
annotations: widget.annotations,
159+
barriers: widget.barriers,
155160
markerSeries: widget.markerSeries,
156161
pipSize: widget.pipSize,
157162
onCrosshairAppeared: widget.onCrosshairAppeared,

lib/src/deriv_chart/chart/data_visualization/annotations/barriers/barrier.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/barrier_objects.dart';
22
import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart';
3+
import 'package:flutter/material.dart';
34

45
import '../chart_annotation.dart';
56

7+
/// Drag callback
8+
typedef OnDragCallback = Function(double, bool);
9+
610
/// Base class of barrier
711
abstract class Barrier extends ChartAnnotation<BarrierObject> {
812
/// Initializes a base class of barrier.
@@ -13,6 +17,8 @@ abstract class Barrier extends ChartAnnotation<BarrierObject> {
1317
this.title,
1418
this.longLine = true,
1519
BarrierStyle? style,
20+
this.label,
21+
this.onDrag,
1622
}) : super(id ?? '$title$style$longLine', style: style);
1723

1824
/// Title of the barrier.
@@ -29,6 +35,15 @@ abstract class Barrier extends ChartAnnotation<BarrierObject> {
2935
/// The value that this barrier points to.
3036
final double? value;
3137

38+
/// Called when the barrier is dragged.
39+
final OnDragCallback? onDrag;
40+
41+
/// Used to store label tap area on the chart.
42+
late Rect labelTapArea;
43+
44+
/// Label of the barrier.
45+
final String? label;
46+
3247
@override
3348
int? getMaxEpoch() => epoch;
3449

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import 'package:deriv_chart/deriv_chart.dart';
2+
import 'package:deriv_chart/src/deriv_chart/chart/custom_painters/chart_painter.dart';
3+
import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart';
4+
import 'package:deriv_chart/src/deriv_chart/chart/gestures/gesture_manager.dart';
5+
import 'package:deriv_chart/src/deriv_chart/chart/multiple_animated_builder.dart';
6+
import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart';
7+
import 'package:deriv_chart/src/models/chart_config.dart';
8+
import 'package:flutter/material.dart';
9+
import 'package:flutter/services.dart';
10+
import 'package:provider/provider.dart';
11+
12+
/// Layer with barriers.
13+
class BarrierArea extends StatefulWidget {
14+
/// Initializes marker area.
15+
const BarrierArea({
16+
required this.barriers,
17+
required this.quoteToCanvasY,
18+
required this.quoteFromCanvasY,
19+
required this.enableVerticalScale,
20+
required this.disableVerticalScale,
21+
Key? key,
22+
}) : super(key: key);
23+
24+
/// The list of barriers.
25+
final List<Barrier> barriers;
26+
27+
/// Conversion function for converting quote to chart's canvas' Y position.
28+
final double Function(double) quoteToCanvasY;
29+
30+
/// Conversion function for converting quote from chart's canvas' Y position.
31+
final double Function(double) quoteFromCanvasY;
32+
33+
/// Enables vertical scroll.
34+
final VoidCallback enableVerticalScale;
35+
36+
/// Disables vertical scroll.
37+
final VoidCallback disableVerticalScale;
38+
39+
@override
40+
_BarrierAreaState createState() => _BarrierAreaState();
41+
}
42+
43+
class _BarrierAreaState extends State<BarrierArea>
44+
with TickerProviderStateMixin {
45+
late GestureManagerState gestureManager;
46+
String? _barrierDragId;
47+
double? _lastDragQuote;
48+
49+
SystemMouseCursor _cursor = SystemMouseCursors.basic;
50+
51+
late AnimationController _currentTickAnimationController;
52+
53+
/// The animation of the current tick.
54+
late Animation<double> currentTickAnimation;
55+
56+
@override
57+
void initState() {
58+
super.initState();
59+
gestureManager = context.read<GestureManagerState>()
60+
..registerCallback(_onDragStart)
61+
..registerCallback(_onDragUpdate)
62+
..registerCallback(_onDragEnd);
63+
_setupCurrentTickAnimation();
64+
}
65+
66+
@override
67+
void didUpdateWidget(BarrierArea barrierArea) {
68+
super.didUpdateWidget(barrierArea);
69+
_playNewTickAnimation();
70+
}
71+
72+
@override
73+
void dispose() {
74+
super.dispose();
75+
gestureManager
76+
..removeCallback(_onDragStart)
77+
..removeCallback(_onDragUpdate)
78+
..removeCallback(_onDragEnd);
79+
_currentTickAnimationController.dispose();
80+
}
81+
82+
void _playNewTickAnimation() {
83+
if (!_currentTickAnimationController.isAnimating) {
84+
_currentTickAnimationController
85+
..reset()
86+
..forward();
87+
}
88+
}
89+
90+
void _setupCurrentTickAnimation() {
91+
_currentTickAnimationController = AnimationController(
92+
vsync: this,
93+
duration: const Duration(milliseconds: 300),
94+
);
95+
currentTickAnimation = CurvedAnimation(
96+
parent: _currentTickAnimationController,
97+
curve: Curves.easeOut,
98+
);
99+
}
100+
101+
void _onDragStart(ScaleStartDetails details) {
102+
for (final Barrier barrier in widget.barriers) {
103+
if (barrier.labelTapArea.contains(details.focalPoint)) {
104+
_barrierDragId = barrier.id;
105+
_cursor = SystemMouseCursors.grabbing;
106+
return;
107+
}
108+
}
109+
}
110+
111+
void _onDragUpdate(DragUpdateDetails details) {
112+
if (_barrierDragId != null) {
113+
for (final Barrier barrier in widget.barriers) {
114+
if (barrier.id == _barrierDragId) {
115+
widget.disableVerticalScale();
116+
final double newQuote = widget
117+
.quoteFromCanvasY(details.localPosition.dy + details.delta.dy);
118+
_lastDragQuote = newQuote;
119+
120+
barrier.onDrag?.call(newQuote, false);
121+
return;
122+
}
123+
}
124+
}
125+
}
126+
127+
void _onDragEnd(ScaleEndDetails details) {
128+
if (_barrierDragId != null) {
129+
widget.enableVerticalScale();
130+
131+
for (final Barrier barrier in widget.barriers) {
132+
if (barrier.id == _barrierDragId) {
133+
barrier.onDrag?.call(_lastDragQuote!, true);
134+
_cursor = SystemMouseCursors.grab;
135+
continue;
136+
}
137+
}
138+
}
139+
_barrierDragId = null;
140+
}
141+
142+
void _onHover(PointerEvent details) {
143+
_cursor = SystemMouseCursors.basic;
144+
145+
for (final Barrier barrier in widget.barriers) {
146+
if (barrier.labelTapArea.contains(details.position)) {
147+
_cursor = SystemMouseCursors.grab;
148+
return;
149+
}
150+
}
151+
}
152+
153+
@override
154+
Widget build(BuildContext context) {
155+
final XAxisModel xAxis = context.watch<XAxisModel>();
156+
157+
return MultipleAnimatedBuilder(
158+
animations: <Animation<double>>[
159+
currentTickAnimation,
160+
],
161+
builder: (BuildContext context, _) => MouseRegion(
162+
cursor: _cursor,
163+
onHover: _onHover,
164+
child: Stack(fit: StackFit.expand, children: <Widget>[
165+
...widget.barriers.map((Barrier annotation) {
166+
annotation.update(xAxis.leftBoundEpoch, xAxis.rightBoundEpoch);
167+
168+
return CustomPaint(
169+
key: ValueKey<String>(annotation.id),
170+
painter: ChartPainter(
171+
animationInfo: AnimationInfo(
172+
currentTickPercent: currentTickAnimation.value,
173+
),
174+
chartData: annotation,
175+
chartConfig: context.watch<ChartConfig>(),
176+
theme: context.watch<ChartTheme>(),
177+
epochToCanvasX: xAxis.xFromEpoch,
178+
quoteToCanvasY: widget.quoteToCanvasY,
179+
),
180+
);
181+
}).toList()
182+
]),
183+
),
184+
);
185+
}
186+
}

lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@ class HorizontalBarrier extends Barrier {
1515
bool longLine = true,
1616
HorizontalBarrierStyle? style,
1717
this.visibility = HorizontalBarrierVisibility.keepBarrierLabelVisible,
18+
OnDragCallback? onDrag,
19+
String? label,
1820
}) : super(
1921
id: id,
2022
title: title,
2123
epoch: epoch,
2224
value: value,
2325
style: style,
2426
longLine: longLine,
27+
label: label,
28+
onDrag: onDrag,
2529
);
2630

2731
/// Barrier visibility behavior.

lib/src/deriv_chart/chart/data_visualization/annotations/barriers/horizontal_barrier/horizontal_barrier_painter.dart

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,34 @@ class HorizontalBarrierPainter<T extends HorizontalBarrier>
112112
_paintBlinkingDot(canvas, dotX, y, animationInfo, style.blinkingDotColor);
113113
}
114114

115+
final String label =
116+
series.label ?? animatedValue.toStringAsFixed(chartConfig.pipSize);
117+
115118
final TextPainter valuePainter = makeTextPainter(
116-
animatedValue.toStringAsFixed(chartConfig.pipSize),
119+
label,
117120
style.textStyle,
118121
);
122+
123+
final TextPainter? iconPainter = style.isDraggable
124+
? makeTextPainter(
125+
String.fromCharCode(Icons.drag_handle.codePoint),
126+
TextStyle(
127+
fontSize: 15,
128+
fontFamily: Icons.drag_handle.fontFamily,
129+
color: Colors.white),
130+
)
131+
: null;
132+
119133
final Rect labelArea = Rect.fromCenter(
120134
center: Offset(
121135
size.width - rightMargin - padding - valuePainter.width / 2, y),
122-
width: valuePainter.width + padding * 2,
136+
width: (iconPainter?.width ?? 0) + valuePainter.width + padding * 2,
123137
height: style.labelHeight,
124138
);
139+
series.labelTapArea = labelArea;
125140

126141
// Line.
127-
if (arrowType == BarrierArrowType.none) {
142+
if (arrowType == BarrierArrowType.none || style.hasArrow == false) {
128143
final double lineStartX = series.longLine ? 0 : (dotX ?? 0);
129144
final double lineEndX = labelArea.left;
130145

@@ -178,6 +193,14 @@ class HorizontalBarrierPainter<T extends HorizontalBarrier>
178193
anchor: labelArea.center,
179194
);
180195

196+
if (iconPainter != null) {
197+
paintWithTextPainter(
198+
canvas,
199+
painter: iconPainter,
200+
anchor: labelArea.centerLeft + const Offset(6, 0),
201+
);
202+
}
203+
181204
// Arrows.
182205
if (style.hasArrow) {
183206
final double arrowMidX = labelArea.left - style.arrowSize - 6;

lib/src/deriv_chart/chart/helpers/functions/conversion.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,28 @@ double quoteToCanvasY({
9494

9595
return topPadding + pxFromTopBound;
9696
}
97+
98+
/// Returns quote based on the y-coordinate.
99+
double quoteFromCanvasY({
100+
required double y,
101+
required double topBoundQuote,
102+
required double bottomBoundQuote,
103+
required double canvasHeight,
104+
required double topPadding,
105+
required double bottomPadding,
106+
}) {
107+
final double drawingRange = canvasHeight - topPadding - bottomPadding;
108+
final double quoteRange = topBoundQuote - bottomBoundQuote;
109+
110+
final double yTopBound = topPadding;
111+
112+
if (quoteRange == 0) {
113+
return topBoundQuote;
114+
}
115+
116+
final double yToTopBoundFraction = (y - yTopBound) / drawingRange;
117+
118+
final double quoteFromTopBound = yToTopBoundFraction * quoteRange;
119+
120+
return topBoundQuote - quoteFromTopBound;
121+
}

0 commit comments

Comments
 (0)