diff --git a/lib/deriv_chart.dart b/lib/deriv_chart.dart index b542ae1b3..eae8ad1dc 100644 --- a/lib/deriv_chart.dart +++ b/lib/deriv_chart.dart @@ -3,6 +3,7 @@ library deriv_chart; export 'generated/l10n.dart'; export 'src/deriv_chart/chart/chart.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_indicator.dart'; +export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_active_contract.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/barrier.dart'; diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator.dart new file mode 100644 index 000000000..be64c155c --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator.dart @@ -0,0 +1,86 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/accumulator_object.dart'; + +import 'accumulators_closed_indicator_painter.dart'; + +/// Accumulator Closed Contract Barriers. +class AccumulatorsClosedIndicator extends ChartAnnotation { + /// Initializes a tick indicator. + AccumulatorsClosedIndicator( + this.exitTick, { + required this.lowBarrier, + required this.highBarrier, + required this.highBarrierDisplay, + required this.lowBarrierDisplay, + required this.barrierSpotDistance, + required this.barrierEpoch, + required this.activeContract, + this.barrierEndEpoch, + super.style, + String? id, + }) : super(id ?? 'AccumulatorsClosedIndicator'); + + /// The price difference between the barrier and the barrier Tick quote. + final String barrierSpotDistance; + + /// The which this tick indicator will be pointing to. + final Tick exitTick; + + /// The low barrier value. + final double lowBarrier; + + /// The high barrier value. + final double highBarrier; + + /// The low barrier display value. + final String highBarrierDisplay; + + /// The high barrier display value. + final String lowBarrierDisplay; + + /// [Optional] Active contract information. + final AccumulatorsActiveContract? activeContract; + + /// The [epoch] of the tick that the barriers belong to. + final int barrierEpoch; + + /// The End if the barrier, if [null] the barriers will go to the end of the screen. + final int? barrierEndEpoch; + + @override + SeriesPainter createPainter() => + AccumulatorsClosedIndicatorPainter(this); + + @override + AccumulatorObject createObject() => AccumulatorObject( + tick: exitTick, + barrierEpoch: barrierEpoch, + lowBarrier: lowBarrier, + highBarrier: highBarrier, + profit: activeContract?.profit, + barrierEndEpoch: barrierEndEpoch, + ); + + @override + int? getMaxEpoch() => barrierEpoch; + + @override + int? getMinEpoch() => barrierEpoch; + + @override + List recalculateMinMax() { + if (annotationObject.bottomValue == null || + annotationObject.topValue == null || + !isOnRange) { + return [double.nan, double.nan]; + } + final double halfOfBarriersDelta = + (annotationObject.highBarrier - annotationObject.lowBarrier) / 2; + + return [ + annotationObject.bottomValue! - halfOfBarriersDelta, + annotationObject.topValue! + halfOfBarriersDelta, + ]; + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator_painter.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator_painter.dart new file mode 100644 index 000000000..2b5dbaccd --- /dev/null +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator_painter.dart @@ -0,0 +1,256 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_data.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/animation_info.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_dot.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/helpers/paint_functions/paint_text.dart'; +import 'package:flutter/material.dart'; + +/// Accumulator barriers painter. +class AccumulatorsClosedIndicatorPainter + extends SeriesPainter { + /// Initializes [AccumulatorsClosedIndicatorPainter]. + AccumulatorsClosedIndicatorPainter(super.series); + + final Paint _linePaint = Paint() + ..strokeWidth = 1 + ..style = PaintingStyle.stroke; + + final Paint _linePaintFill = Paint() + ..strokeWidth = 1 + ..style = PaintingStyle.fill; + + final Paint _rectPaint = Paint()..style = PaintingStyle.fill; + + /// Padding between lines. + static const double padding = 4; + + /// Padding between tick and text. + static const double tickTextPadding = 12; + + /// Right margin. + static const double rightMargin = 4; + + @override + void onPaint({ + required Canvas canvas, + required Size size, + required EpochToX epochToX, + required QuoteToY quoteToY, + required AnimationInfo animationInfo, + }) { + final HorizontalBarrierStyle style = + series.style as HorizontalBarrierStyle? ?? theme.horizontalBarrierStyle; + + // Change the barrier color based on the contract status and tick quote. + Color color = theme.base03Color; + if (series.activeContract?.profit != null) { + if (series.activeContract!.profit! > 0) { + color = theme.accentGreenColor; + } else if (series.activeContract!.profit! < 0) { + color = theme.accentRedColor; + } + } + + if (series.exitTick.quote > series.highBarrier || + series.exitTick.quote < series.lowBarrier) { + color = theme.accentRedColor; + } + _linePaint.color = color; + _linePaintFill.color = color; + _rectPaint.color = color.withOpacity(0.08); + + final AccumulatorsClosedIndicator indicator = series; + + final double barrierX = epochToX(indicator.barrierEpoch); + final double hBarrierQuote = indicator.highBarrier; + final double lBarrierQuote = indicator.lowBarrier; + + final Offset highBarrierPosition = Offset( + barrierX, + quoteToY(hBarrierQuote), + ); + + final Offset lowBarrierPosition = Offset( + barrierX, + quoteToY(lBarrierQuote), + ); + + final Offset exitTickPosition = Offset( + epochToX(indicator.exitTick.epoch), + quoteToY(indicator.exitTick.quote), + ); + + // draw the transparent color. + final Rect rect = Rect.fromPoints( + highBarrierPosition, + Offset( + series.barrierEndEpoch != null + ? epochToX(series.barrierEndEpoch!) + : size.width, + lowBarrierPosition.dy), + ); + canvas.drawRect(rect, _rectPaint); + + const int triangleEdge = 4; + const int triangleHeight = 5; + + final Path upperTrianglePath = Path() + ..moveTo( + highBarrierPosition.dx, + highBarrierPosition.dy, + ) + ..lineTo( + highBarrierPosition.dx + triangleEdge, + highBarrierPosition.dy, + ) + ..lineTo( + highBarrierPosition.dx, + highBarrierPosition.dy + triangleHeight, + ) + ..lineTo( + highBarrierPosition.dx + -triangleEdge, + highBarrierPosition.dy, + ) + ..close(); + + final Path lowerTrianglePath = Path() + ..moveTo( + lowBarrierPosition.dx, + lowBarrierPosition.dy, + ) + ..lineTo( + lowBarrierPosition.dx + triangleEdge, + lowBarrierPosition.dy, + ) + ..lineTo( + lowBarrierPosition.dx, + lowBarrierPosition.dy - triangleHeight, + ) + ..lineTo( + lowBarrierPosition.dx + -triangleEdge, + lowBarrierPosition.dy, + ) + ..close(); + + canvas + ..drawLine( + lowBarrierPosition, + Offset( + series.barrierEndEpoch != null + ? epochToX(series.barrierEndEpoch!) + : size.width, + lowBarrierPosition.dy), + _linePaint, + ) + ..drawLine( + highBarrierPosition, + Offset( + series.barrierEndEpoch != null + ? epochToX(series.barrierEndEpoch!) + : size.width, + highBarrierPosition.dy), + _linePaint, + ) + ..drawPath(upperTrianglePath, _linePaint) + ..drawPath(lowerTrianglePath, _linePaint) + ..drawPath(upperTrianglePath, _linePaintFill) + ..drawPath(lowerTrianglePath, _linePaintFill); + + // Draw exit tick position. + paintDot(canvas, exitTickPosition, color); + + // draw dialog + const double dotPadding = 5; + const int dialogTriangleEdge = 6; + const int dialogTriangleHeight = 4; + + final Path dialogTrianglePath = Path() + ..moveTo( + exitTickPosition.dx - dotPadding, + exitTickPosition.dy, + ) + ..lineTo( + exitTickPosition.dx - dotPadding - dialogTriangleHeight, + exitTickPosition.dy - dialogTriangleEdge / 2, + ) + ..lineTo( + exitTickPosition.dx - dotPadding - dialogTriangleHeight, + exitTickPosition.dy + dialogTriangleEdge / 2, + ) + ..lineTo( + exitTickPosition.dx - dotPadding, + exitTickPosition.dy, + ) + ..close(); + + canvas + ..drawPath(dialogTrianglePath, _linePaintFill) + ..drawPath(dialogTrianglePath, _linePaint); + + if (indicator.activeContract?.profit != null) { + final double profit = indicator.activeContract!.profit!; + final String profitText = + '${profit < 0 ? '' : '+'}${profit.toStringAsFixed(2)}'; + final String currencyText = '${indicator.activeContract?.currency ?? ''}'; + final TextPainter profitPainter = makeTextPainter( + '$profitText $currencyText', + style.textStyle.copyWith( + color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), + ); + + final TextPainter winLossPainter = makeTextPainter( + indicator.activeContract!.profit! > 0 ? 'Won:' : 'Loss:', + style.textStyle.copyWith( + color: Colors.white, fontSize: 10, fontWeight: FontWeight.w400), + ); + + final double textHeight = profitPainter.height + winLossPainter.height; + final double textWidth = profitPainter.width; + + final double dialogRightSide = + exitTickPosition.dx - dotPadding - dialogTriangleHeight - padding; + final double dialogLeftSide = dialogRightSide - textWidth; + + final double dialogDownSide = exitTickPosition.dy + textHeight / 2; + final double dialogUpSide = exitTickPosition.dy - textHeight / 2; + + final Rect dialogRect = Rect.fromLTRB( + dialogLeftSide - padding, + dialogUpSide - padding, + dialogRightSide + padding, + dialogDownSide + padding, + ); + final RRect rRect = + RRect.fromRectAndRadius(dialogRect, const Radius.circular(4)); + + _rectPaint.color = color.withOpacity(1); + canvas.drawRRect(rRect, _rectPaint); + + final Offset winLossPosition = Offset( + dialogLeftSide + winLossPainter.width / 2, + exitTickPosition.dy - textHeight / 2 + winLossPainter.height / 2, + ); + + paintWithTextPainter( + canvas, + painter: winLossPainter, + anchor: winLossPosition, + ); + + final Offset profitPosition = Offset( + dialogLeftSide + profitPainter.width / 2, + exitTickPosition.dy - + textHeight / 2 + + profitPainter.height / 2 + + winLossPainter.height, + ); + + paintWithTextPainter( + canvas, + painter: profitPainter, + anchor: profitPosition, + ); + } + } +} diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_indicator.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_indicator.dart index 05875b1ab..d5677a419 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_indicator.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_indicator.dart @@ -1,5 +1,4 @@ import 'package:deriv_chart/deriv_chart.dart'; -import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_active_contract.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/series_painter.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/accumulator_object.dart'; @@ -70,4 +69,20 @@ class AccumulatorIndicator extends ChartAnnotation { @override int? getMinEpoch() => barrierEpoch; + + @override + List recalculateMinMax() { + if (annotationObject.bottomValue == null || + annotationObject.topValue == null || + !isOnRange) { + return [double.nan, double.nan]; + } + final double halfOfBarriersDelta = + (annotationObject.highBarrier - annotationObject.lowBarrier) / 2; + + return [ + annotationObject.bottomValue! - halfOfBarriersDelta, + annotationObject.topValue! + halfOfBarriersDelta, + ]; + } } diff --git a/lib/src/deriv_chart/chart/data_visualization/annotations/chart_annotation.dart b/lib/src/deriv_chart/chart/data_visualization/annotations/chart_annotation.dart index 6b2e02611..8bc8e4772 100644 --- a/lib/src/deriv_chart/chart/data_visualization/annotations/chart_annotation.dart +++ b/lib/src/deriv_chart/chart/data_visualization/annotations/chart_annotation.dart @@ -77,10 +77,12 @@ abstract class ChartAnnotation extends Series { isOnRange = annotationObject.isOnEpochRange(leftEpoch, rightEpoch); @override - List recalculateMinMax() => [ - annotationObject.bottomValue ?? double.nan, - annotationObject.topValue ?? double.nan - ]; + List recalculateMinMax() => isOnRange + ? [ + annotationObject.bottomValue ?? double.nan, + annotationObject.topValue ?? double.nan + ] + : [double.nan, double.nan]; /// Prepares the [annotationObject] of this [ChartAnnotation]. T createObject(); diff --git a/lib/src/deriv_chart/chart/data_visualization/models/accumulator_object.dart b/lib/src/deriv_chart/chart/data_visualization/models/accumulator_object.dart index 022e1c77f..a4c3757b4 100644 --- a/lib/src/deriv_chart/chart/data_visualization/models/accumulator_object.dart +++ b/lib/src/deriv_chart/chart/data_visualization/models/accumulator_object.dart @@ -9,7 +9,13 @@ class AccumulatorObject extends ChartObject { required this.lowBarrier, required this.highBarrier, required this.profit, - }) : super(barrierEpoch, null, lowBarrier, highBarrier); + this.barrierEndEpoch, + }) : super( + barrierEpoch, + barrierEndEpoch ?? barrierEpoch, + lowBarrier, + highBarrier, + ); /// The which this tick indicator will be pointing to. final Tick tick; @@ -23,6 +29,8 @@ class AccumulatorObject extends ChartObject { /// The [epoch] of the tick that the barriers belong to. final int barrierEpoch; + final int? barrierEndEpoch; + /// [Optional] The profit value which is being shown in the /// middle of the tick indicator. final double? profit; diff --git a/lib/src/deriv_chart/chart/main_chart.dart b/lib/src/deriv_chart/chart/main_chart.dart index 359290e8c..9e8239713 100644 --- a/lib/src/deriv_chart/chart/main_chart.dart +++ b/lib/src/deriv_chart/chart/main_chart.dart @@ -253,9 +253,16 @@ class _ChartImplementationState extends BasicChartState { _buildSeries(), _buildAnnotations(), if (widget.markerSeries != null) - MarkerArea( - markerSeries: widget.markerSeries!, - quoteToCanvasY: chartQuoteToCanvasY, + MultipleAnimatedBuilder( + animations: [ + currentTickAnimation, + topBoundQuoteAnimationController, + bottomBoundQuoteAnimationController, + ], + builder: (_, __) => MarkerArea( + markerSeries: widget.markerSeries!, + quoteToCanvasY: chartQuoteToCanvasY, + ), ), _buildCrosshairArea(), if (_isScrollToLastTickAvailable)