diff --git a/lib/src/candle_data.dart b/lib/src/candle_data.dart index 692f5d2..3a5de72 100644 --- a/lib/src/candle_data.dart +++ b/lib/src/candle_data.dart @@ -71,6 +71,29 @@ class CandleData { return result; } + @override + bool operator ==(Object other) => + identical(this, other) || + other is CandleData && + runtimeType == other.runtimeType && + timestamp == other.timestamp && + open == other.open && + high == other.high && + low == other.low && + close == other.close && + volume == other.volume && + trends == other.trends; + + @override + int get hashCode => + timestamp.hashCode ^ + open.hashCode ^ + high.hashCode ^ + low.hashCode ^ + close.hashCode ^ + volume.hashCode ^ + trends.hashCode; + @override String toString() => ""; } diff --git a/lib/src/chart_painter.dart b/lib/src/chart_painter.dart index ecf16da..ca1e621 100644 --- a/lib/src/chart_painter.dart +++ b/lib/src/chart_painter.dart @@ -27,6 +27,8 @@ class ChartPainter extends CustomPainter { // Draw time labels (dates) & price labels _drawTimeLabels(canvas, params); _drawPriceGridAndLabels(canvas, params); + _drawCurrentPriceLabel(canvas, params); + _drawCurrentPriceLine(canvas, params); // Draw prices, volumes & trend line canvas.save(); @@ -107,6 +109,67 @@ class ChartPainter extends CustomPainter { }); } + void _drawCurrentPriceLabel( + Canvas canvas, + PainterParams params, + ) { + final currentPrice = params.currentPrice; + if (currentPrice == null) { + return; + } + final priceTp = TextPainter( + text: TextSpan( + text: getPriceLabel(currentPrice), + style: params.style.currentPriceStyle + .labelStyle, + ), + ) + ..textDirection = TextDirection.ltr + ..layout(); + + final dx = params.chartWidth + 4; + final dy = + params.fitPrice(currentPrice).clamp(0, params.chartHeight).toDouble() - + priceTp.height / 2; + + + final padding = params.style.currentPriceStyle.rectPadding; + final radius = params.style.currentPriceStyle.rectRadius; + final rectColor = params.style.currentPriceStyle.rectColor; + + final rect = Rect.fromLTWH( + dx, dy, priceTp.width + 2 * padding, priceTp.height + 2 * padding); + final rrect = RRect.fromRectAndRadius(rect, Radius.circular(radius)); + canvas.drawRRect(rrect, Paint()..color = rectColor); + + priceTp.paint(canvas, Offset(dx + padding, dy + padding)); + } + + void _drawCurrentPriceLine(Canvas canvas, PainterParams params) { + final currentPrice = params.currentPrice; + if (currentPrice == null) { + return; + } + final paint = Paint() + ..color = Colors.red + ..strokeWidth = 1 + ..style = PaintingStyle.stroke; + + final dashWidth = 4.0; + final dashSpace = 2.0; + double startX = 0; + final clampedPrice = + params.fitPrice(currentPrice).clamp(0, params.chartHeight).toDouble(); + while (startX < params.chartWidth) { + canvas.drawLine( + Offset(startX, clampedPrice), + Offset(startX + dashWidth, clampedPrice), + paint, + ); + startX += dashWidth + dashSpace; + } + } + void _drawSingleDay(canvas, PainterParams params, int i) { final candle = params.candles[i]; final x = i * params.candleWidth; diff --git a/lib/src/chart_style.dart b/lib/src/chart_style.dart index 8e6b3ec..eaaadfa 100644 --- a/lib/src/chart_style.dart +++ b/lib/src/chart_style.dart @@ -54,6 +54,9 @@ class ChartStyle { /// This appears when user clicks on the chart. final Color overlayBackgroundColor; + /// The style of current price labels (on the right of the chart). + final CurrentPriceStyle currentPriceStyle; + const ChartStyle({ this.volumeHeightFactor = 0.2, this.priceLabelWidth = 48.0, @@ -70,6 +73,15 @@ class ChartStyle { fontSize: 16, color: Colors.white, ), + this.currentPriceStyle = const CurrentPriceStyle( + labelStyle: TextStyle( + fontSize: 12, + color: Colors.white, + ), + rectPadding: 4.0, + rectRadius: 2.0, + rectColor: Colors.red + ), this.priceGainColor = Colors.green, this.priceLossColor = Colors.red, this.volumeColor = Colors.grey, @@ -79,3 +91,24 @@ class ChartStyle { this.overlayBackgroundColor = const Color(0xEE757575), }); } + +class CurrentPriceStyle { + const CurrentPriceStyle({ + required this.labelStyle, + required this.rectPadding, + required this.rectRadius, + required this.rectColor, + }); + + /// The style of current price labels (on the right of the chart). + final TextStyle labelStyle; + + /// The padding around the current price rect. + final double rectPadding; + + /// The radius of the current price rect. + final double rectRadius; + + /// The color of the current price rect. + final Color rectColor; +} diff --git a/lib/src/interactive_chart.dart b/lib/src/interactive_chart.dart index e48c70b..62c4c40 100644 --- a/lib/src/interactive_chart.dart +++ b/lib/src/interactive_chart.dart @@ -60,6 +60,9 @@ class InteractiveChart extends StatefulWidget { /// This provides the width of a candlestick at the current zoom level. final ValueChanged? onCandleResize; + /// The current price to be displayed on the right side of the chart. + final double? currentPrice; + const InteractiveChart({ Key? key, required this.candles, @@ -70,6 +73,7 @@ class InteractiveChart extends StatefulWidget { this.overlayInfo, this.onTap, this.onCandleResize, + this.currentPrice, }) : this.style = style ?? const ChartStyle(), assert(candles.length >= 3, "InteractiveChart requires 3 or more CandleData"), @@ -175,6 +179,7 @@ class _InteractiveChartState extends State { tapPosition: _tapPosition, leadingTrends: leadingTrends, trailingTrends: trailingTrends, + currentPrice: widget.currentPrice, ), ), duration: Duration(milliseconds: 300), diff --git a/lib/src/painter_params.dart b/lib/src/painter_params.dart index 228ac7e..a32ee48 100644 --- a/lib/src/painter_params.dart +++ b/lib/src/painter_params.dart @@ -6,6 +6,7 @@ import 'candle_data.dart'; class PainterParams { final List candles; + final double? currentPrice; final ChartStyle style; final Size size; final double candleWidth; @@ -35,6 +36,7 @@ class PainterParams { required this.tapPosition, required this.leadingTrends, required this.trailingTrends, + this.currentPrice, }); double get chartWidth => // width without price labels @@ -84,25 +86,28 @@ class PainterParams { double lerpField(double getField(PainterParams p)) => lerpDouble(getField(a), getField(b), t)!; return PainterParams( - candles: b.candles, - style: b.style, - size: b.size, - candleWidth: b.candleWidth, - startOffset: b.startOffset, - maxPrice: lerpField((p) => p.maxPrice), - minPrice: lerpField((p) => p.minPrice), - maxVol: lerpField((p) => p.maxVol), - minVol: lerpField((p) => p.minVol), - xShift: b.xShift, - tapPosition: b.tapPosition, - leadingTrends: b.leadingTrends, - trailingTrends: b.trailingTrends, - ); + candles: b.candles, + style: b.style, + size: b.size, + candleWidth: b.candleWidth, + startOffset: b.startOffset, + maxPrice: lerpField((p) => p.maxPrice), + minPrice: lerpField((p) => p.minPrice), + maxVol: lerpField((p) => p.maxVol), + minVol: lerpField((p) => p.minVol), + xShift: b.xShift, + tapPosition: b.tapPosition, + leadingTrends: b.leadingTrends, + trailingTrends: b.trailingTrends, + currentPrice: + b.currentPrice != null ? lerpField((p) => p.currentPrice!) : null); } bool shouldRepaint(PainterParams other) { if (candles.length != other.candles.length) return true; + if (other.currentPrice != currentPrice) return true; + if (size != other.size || candleWidth != other.candleWidth || startOffset != other.startOffset ||