diff --git a/CHANGELOG.md b/CHANGELOG.md index e20f3c45f..64a31656e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## newVersion +* **FEATURE** Add `extraLinesData` support to [CandlestickChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/candlestick_chart.md). You can now draw horizontal and vertical lines on candlestick charts, similar to LineChart. Useful for showing support/resistance levels, moving averages, or other reference lines. * **BUGFIX** (by @imaNNeo) Consider the `enabled` property in [LineTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchdata-read-about-touch-handling), [BarTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchdata-read-about-touch-handling), [PieTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#pietouchdata-read-about-touch-handling), [ScatterTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertouchdata-read-about-touch-handling), [RadarTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md#radartouchdata-read-about-touch-handling) and [CandlestickTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/candlestick_chart.md#candlesticktouchdata-read-about-touch-handling), #1676 ## 1.1.1 diff --git a/example/lib/presentation/samples/candlestick/candlestick_chart_sample1.dart b/example/lib/presentation/samples/candlestick/candlestick_chart_sample1.dart index f78d50431..2e56f2036 100644 --- a/example/lib/presentation/samples/candlestick/candlestick_chart_sample1.dart +++ b/example/lib/presentation/samples/candlestick/candlestick_chart_sample1.dart @@ -166,6 +166,29 @@ class CandlestickChartSample1State extends State { getDrawingHorizontalLine: (_) => _gridLine, getDrawingVerticalLine: (_) => _gridLine, ), + extraLinesData: ExtraLinesData( + horizontalLines: [ + HorizontalLine( + y: _monthMedian, + color: AppColors.contentColorYellow, + strokeWidth: 2, + dashArray: [10, 5], + label: HorizontalLineLabel( + show: true, + alignment: Alignment.topRight, + padding: + const EdgeInsets.only(bottom: 8, right: 8), + style: const TextStyle( + color: AppColors.contentColorYellow, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + labelResolver: (line) => + 'Median: \$${line.y.toStringAsFixed(0)}', + ), + ), + ], + ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( @@ -258,6 +281,22 @@ class CandlestickChartSample1State extends State { bool get _canGoPrevious => _currentMonthIndex > 0; + double get _monthMedian { + if (_btcMonthlyData == null) return 0; + final monthData = _btcMonthlyData![_currentMonthIndex]; + if (monthData.isEmpty) return 0; + + // Calculate median of closing prices + final closingPrices = monthData.map((e) => e.close).toList()..sort(); + final middle = closingPrices.length ~/ 2; + + if (closingPrices.length % 2 == 0) { + return (closingPrices[middle - 1] + closingPrices[middle]) / 2; + } else { + return closingPrices[middle]; + } + } + void _previousMonth() { if (!_canGoPrevious) { return; diff --git a/lib/src/chart/candlestick_chart/candlestick_chart_data.dart b/lib/src/chart/candlestick_chart/candlestick_chart_data.dart index b05b642f2..901d0b5d4 100644 --- a/lib/src/chart/candlestick_chart/candlestick_chart_data.dart +++ b/lib/src/chart/candlestick_chart/candlestick_chart_data.dart @@ -30,11 +30,16 @@ class CandlestickChartData extends AxisChartData with EquatableMixin { /// on top of each [CandlestickChartData.candleSpots] using [showingTooltipIndicators], /// just put spot indices you want to show it on top of them. /// + /// [CandlestickChart] draws some horizontal or vertical lines on above or below of everything, + /// they are useful in some scenarios, for example you can show average line, you can fill + /// [extraLinesData] property to have your extra lines. + /// /// [clipData] forces the [CandlestickChart] to draw lines inside the chart bounding box. CandlestickChartData({ List? candlestickSpots, FlCandlestickPainter? candlestickPainter, FlTitlesData? titlesData, + ExtraLinesData? extraLinesData, CandlestickTouchData? candlestickTouchData, List? showingTooltipIndicators, FlGridData? gridData, @@ -57,6 +62,7 @@ class CandlestickChartData extends AxisChartData with EquatableMixin { super( gridData: gridData ?? const FlGridData(), titlesData: titlesData ?? const FlTitlesData(), + extraLinesData: extraLinesData ?? const ExtraLinesData(), clipData: clipData ?? const FlClipData.none(), minX: minX ?? CandlestickChartHelper.calculateMaxAxisValues( @@ -121,6 +127,8 @@ class CandlestickChartData extends AxisChartData with EquatableMixin { t, ), titlesData: FlTitlesData.lerp(a.titlesData, b.titlesData, t), + extraLinesData: + ExtraLinesData.lerp(a.extraLinesData, b.extraLinesData, t), candlestickTouchData: b.candlestickTouchData, showingTooltipIndicators: lerpIntList( a.showingTooltipIndicators, @@ -156,6 +164,7 @@ class CandlestickChartData extends AxisChartData with EquatableMixin { List? candlestickSpots, FlCandlestickPainter? candlestickPainter, FlTitlesData? titlesData, + ExtraLinesData? extraLinesData, CandlestickTouchData? candlestickTouchData, List? showingTooltipIndicators, FlGridData? gridData, @@ -176,6 +185,7 @@ class CandlestickChartData extends AxisChartData with EquatableMixin { candlestickSpots: candlestickSpots ?? this.candlestickSpots, candlestickPainter: candlestickPainter ?? this.candlestickPainter, titlesData: titlesData ?? this.titlesData, + extraLinesData: extraLinesData ?? this.extraLinesData, candlestickTouchData: candlestickTouchData ?? this.candlestickTouchData, showingTooltipIndicators: showingTooltipIndicators ?? this.showingTooltipIndicators, @@ -204,6 +214,7 @@ class CandlestickChartData extends AxisChartData with EquatableMixin { showingTooltipIndicators, gridData, titlesData, + extraLinesData, minX, maxX, baselineX, diff --git a/lib/src/chart/candlestick_chart/candlestick_chart_painter.dart b/lib/src/chart/candlestick_chart/candlestick_chart_painter.dart index 109e4a893..3b99857e0 100644 --- a/lib/src/chart/candlestick_chart/candlestick_chart_painter.dart +++ b/lib/src/chart/candlestick_chart/candlestick_chart_painter.dart @@ -41,9 +41,18 @@ class CandlestickChartPainter extends AxisChartPainter { ..clipRect(Offset.zero & canvasWrapper.size); } super.paint(context, canvasWrapper, holder); + + if (!holder.data.extraLinesData.extraLinesOnTop) { + super.drawExtraLines(context, canvasWrapper, holder); + } + drawAxisSpotIndicator(context, canvasWrapper, holder); drawCandlesticks(context, canvasWrapper, holder); + if (holder.data.extraLinesData.extraLinesOnTop) { + super.drawExtraLines(context, canvasWrapper, holder); + } + if (holder.chartVirtualRect != null) { canvasWrapper.restore(); } diff --git a/test/chart/candlestick_chart/candlestick_chart_data_test.dart b/test/chart/candlestick_chart/candlestick_chart_data_test.dart index 8ad006d52..5d8927481 100644 --- a/test/chart/candlestick_chart/candlestick_chart_data_test.dart +++ b/test/chart/candlestick_chart/candlestick_chart_data_test.dart @@ -273,6 +273,38 @@ void main() { ), false, ); + + expect( + candleStickChartData1 == + candleStickChartData1Clone.copyWith( + extraLinesData: ExtraLinesData( + horizontalLines: [ + HorizontalLine(y: 10), + ], + ), + ), + false, + ); + + expect( + candleStickChartData1 == + candleStickChartData1Clone.copyWith( + extraLinesData: ExtraLinesData( + verticalLines: [ + VerticalLine(x: 5), + ], + ), + ), + false, + ); + + expect( + candleStickChartData1 == + candleStickChartData1Clone.copyWith( + extraLinesData: const ExtraLinesData(), + ), + true, + ); }); test('CandlestickSpot equality test', () {