From df12b6314a8ad2992ff4f91b1cbe76f7bd40c5a6 Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 29 Sep 2026 22:23:44 +0330 Subject: [PATCH 01/12] feat: add GridColumnMode to spread grid lines evenly across the chart With few candles in view, the grid's vertical lines only rule where a candle crosses a time bucket, so a sparse intraday window leaves them clustered instead of spanning the chart. ChartStyle.gridColumnMode lets that grid fall back to dividing the chart width into gridColumns equal bands instead, independent of candle count. --- doc/date-axis.md | 15 +++++++++++++++ example/lib/src/controls.dart | 6 ++++++ example/lib/src/demo_state.dart | 7 +++++++ lib/src/chart_style.dart | 25 +++++++++++++++++++++++++ lib/src/renderer/chart_painter.dart | 11 ++++++++--- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/doc/date-axis.md b/doc/date-axis.md index 78256d0..4369aaf 100644 --- a/doc/date-axis.md +++ b/doc/date-axis.md @@ -16,6 +16,21 @@ Like the price axis, the date axis selects round values before placing labels. `ChartStyle.gridColumns` controls label density, in the same way as `gridRows` on the price axis. +By default the grid's vertical lines rule wherever a candle crosses a time +bucket (`GridColumnMode.dateTicks`), so each line lines up with a round time. +With only a handful of candles in view — a sparse intraday window, say — +there may be only one or two such crossings, leaving most of the chart +without a line. Set `ChartStyle.gridColumnMode` to +`GridColumnMode.evenlySpaced` to divide the chart width into `gridColumns` +equal bands instead, so the grid always spans the chart regardless of candle +count — at the cost of the lines no longer landing on round times: + +```dart +ChartStyle( + gridColumnMode: GridColumnMode.evenlySpaced, +); +``` + To control formatting, use `ChartStyle.dateTimeFormat` for a fixed pattern, or `dateFormatter` for full control. `dateFormatter` receives each candle and a flag indicating whether the long form (used by the crosshair) is required: diff --git a/example/lib/src/controls.dart b/example/lib/src/controls.dart index 94918fb..ac12611 100644 --- a/example/lib/src/controls.dart +++ b/example/lib/src/controls.dart @@ -458,6 +458,12 @@ class Controls extends StatelessWidget { value: state.fitContent, onChanged: (v) => state.update(() => state.fitContent = v), ), + _Toggle( + label: 'Even grid columns', + subtitle: 'Grid lines spread over the chart width, not candles', + value: state.evenGridColumns, + onChanged: (v) => state.update(() => state.evenGridColumns = v), + ), _Toggle( label: 'Prices as currency', subtitle: r'priceFormatter writes them as $1234.50', diff --git a/example/lib/src/demo_state.dart b/example/lib/src/demo_state.dart index 056ba14..898754c 100644 --- a/example/lib/src/demo_state.dart +++ b/example/lib/src/demo_state.dart @@ -272,6 +272,10 @@ class DemoState extends ChangeNotifier { /// the left at the fixed spacing. bool fitContent = false; + /// Spreads the grid's vertical lines evenly across the chart width rather + /// than ruling them at candle timestamps. + bool evenGridColumns = false; + /// Writes the prices as currency rather than as plain decimals. bool currencyPrices = false; @@ -323,6 +327,9 @@ class DemoState extends ChangeNotifier { showSessionDividers: sessionDividers, priceAxisWidth: fixedPriceAxis ? 56.0 : 0.0, fitContent: fitContent, + gridColumnMode: evenGridColumns + ? GridColumnMode.evenlySpaced + : GridColumnMode.dateTicks, ); } diff --git a/lib/src/chart_style.dart b/lib/src/chart_style.dart index 2aedd7b..520c0ac 100644 --- a/lib/src/chart_style.dart +++ b/lib/src/chart_style.dart @@ -436,6 +436,23 @@ class ChartColors { } } +/// How the grid's vertical lines are placed. +enum GridColumnMode { + /// Rules a line wherever a candle crosses a time-bucket boundary — see + /// [ChartStyle.gridColumns]. Reads well once there are enough candles in + /// view to cross several boundaries, since each line then lines up with a + /// round time. With only a handful of candles on screen — a sparse + /// intraday window, say — there may be only one or two such crossings, so + /// the lines cluster together instead of spanning the chart. + dateTicks, + + /// Divides the chart width into [ChartStyle.gridColumns] equal bands, + /// ignoring candle timestamps entirely. The lines always span the full + /// chart, regardless of how many candles are in view, but they no longer + /// line up with round times the way [dateTicks] does. + evenlySpaced, +} + /// Geometry of the candlestick chart: paddings, stroke widths, gaps and /// text sizes. class ChartStyle { @@ -461,6 +478,7 @@ class ChartStyle { this.nowPriceDashed = true, this.gridRows = 8, this.gridColumns = 4, + this.gridColumnMode = GridColumnMode.dateTicks, this.gridStrokeWidth = 0.5, this.separatorWidth = 1.0, this.hollowUpCandles = false, @@ -553,6 +571,11 @@ class ChartStyle { /// vertical line at each one that survives. final int gridColumns; + /// Whether the grid's vertical lines follow candle timestamps or are + /// spread evenly across the chart width. Defaults to + /// [GridColumnMode.dateTicks], matching the chart's previous behavior. + final GridColumnMode gridColumnMode; + /// Stroke width of the grid's hairlines. final double gridStrokeWidth; @@ -684,6 +707,7 @@ class ChartStyle { bool? nowPriceDashed, int? gridRows, int? gridColumns, + GridColumnMode? gridColumnMode, double? gridStrokeWidth, double? separatorWidth, bool? hollowUpCandles, @@ -730,6 +754,7 @@ class ChartStyle { nowPriceDashed: nowPriceDashed ?? this.nowPriceDashed, gridRows: gridRows ?? this.gridRows, gridColumns: gridColumns ?? this.gridColumns, + gridColumnMode: gridColumnMode ?? this.gridColumnMode, gridStrokeWidth: gridStrokeWidth ?? this.gridStrokeWidth, separatorWidth: separatorWidth ?? this.separatorWidth, hollowUpCandles: hollowUpCandles ?? this.hollowUpCandles, diff --git a/lib/src/renderer/chart_painter.dart b/lib/src/renderer/chart_painter.dart index 27960d5..cfc12e6 100644 --- a/lib/src/renderer/chart_painter.dart +++ b/lib/src/renderer/chart_painter.dart @@ -509,9 +509,14 @@ class ChartPainter extends BaseChartPainter { if (!hideGrid) { // Every pane rules itself on the same columns, so they line up down the // stack and each one meets its own date label at the bottom. - final columnXs = [ - for (final index in dateTickIndices()) translateXtoX(getX(index)), - ]; + // + // `evenlySpaced` skips candle timestamps entirely — passing columnXs: + // null tells each renderer to divide the chart width into gridColumns + // equal bands itself, so the grid always spans the chart no matter how + // few candles are in view. + final columnXs = chartStyle.gridColumnMode == GridColumnMode.dateTicks + ? [for (final index in dateTickIndices()) translateXtoX(getX(index))] + : null; mMainRenderer.drawGrid( canvas, mGridRows, From 949540001868072debfc36033dfedc5dfade1d60 Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 01:27:13 +0330 Subject: [PATCH 02/12] feat: toggle grid rows and columns, dash the grid, release as 3.1.0 ChartStyle gains showGridRows, showGridColumns and gridDashPattern next to gridColumnMode, so the grid's horizontal and vertical lines switch on and off independently and can be dashed. Invalid dash patterns read as solid. The pane borders are not part of the grid and stay. The example app gets switches for all three, the date-axis doc covers them, and the 3.1.0 changelog entry folds in gridColumnMode. --- CHANGELOG.md | 18 ++ doc/date-axis.md | 14 ++ example/lib/src/controls.dart | 18 ++ example/lib/src/demo_state.dart | 10 ++ lib/src/chart_style.dart | 26 +++ lib/src/renderer/base_chart_renderer.dart | 44 ++++- lib/src/renderer/indicator_pane_renderer.dart | 16 +- lib/src/renderer/main_renderer.dart | 26 ++- lib/src/renderer/vol_renderer.dart | 24 ++- pubspec.yaml | 2 +- test/grid_style_test.dart | 154 ++++++++++++++++++ 11 files changed, 333 insertions(+), 19 deletions(-) create mode 100644 test/grid_style_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index f5f1e8d..18a7460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 3.1.0 - 2026-10-06 + +### Added + +- **`ChartStyle.gridColumnMode`** chooses where the grid's vertical lines go. + `GridColumnMode.dateTicks` (the default, and the behaviour so far) rules a line + wherever a candle crosses a time bucket; with only a few candles in view that + can leave the lines bunched together. `GridColumnMode.evenlySpaced` divides the + chart width into `gridColumns` equal bands instead, so the grid spans the whole + chart however sparse the data. The date labels keep their round times. +- **`ChartStyle.showGridRows` and `showGridColumns`** switch the horizontal and + vertical grid lines on and off independently. `hideGrid` still hides both, and + the pane borders stay either way. +- **`ChartStyle.gridDashPattern`** dashes the grid lines with a list of on/off + lengths, e.g. `[4, 3]`. Null is solid; an empty list or a non-positive length + is read as solid. +- The example app has switches for all three. + ## 3.0.0 - 2026-09-19 ### Breaking diff --git a/doc/date-axis.md b/doc/date-axis.md index 4369aaf..71e7232 100644 --- a/doc/date-axis.md +++ b/doc/date-axis.md @@ -31,6 +31,20 @@ ChartStyle( ); ``` +The grid can also be trimmed or dashed, each part on its own: + +```dart +ChartStyle( + showGridRows: false, // vertical lines only + showGridColumns: true, + gridDashPattern: [4, 3], // 4px dash, 3px gap; null is solid +); +``` + +`hideGrid` on the widget still hides the whole grid. The date labels keep +their round times in either column mode, so with `evenlySpaced` a line and a +label need not meet. + To control formatting, use `ChartStyle.dateTimeFormat` for a fixed pattern, or `dateFormatter` for full control. `dateFormatter` receives each candle and a flag indicating whether the long form (used by the crosshair) is required: diff --git a/example/lib/src/controls.dart b/example/lib/src/controls.dart index ac12611..a438f57 100644 --- a/example/lib/src/controls.dart +++ b/example/lib/src/controls.dart @@ -464,6 +464,24 @@ class Controls extends StatelessWidget { value: state.evenGridColumns, onChanged: (v) => state.update(() => state.evenGridColumns = v), ), + _Toggle( + label: 'Grid rows', + subtitle: 'The horizontal price lines', + value: state.showGridRows, + onChanged: (v) => state.update(() => state.showGridRows = v), + ), + _Toggle( + label: 'Grid columns', + subtitle: 'The vertical time lines', + value: state.showGridColumns, + onChanged: (v) => state.update(() => state.showGridColumns = v), + ), + _Toggle( + label: 'Dashed grid', + subtitle: 'gridDashPattern of 4 on, 3 off', + value: state.dashedGrid, + onChanged: (v) => state.update(() => state.dashedGrid = v), + ), _Toggle( label: 'Prices as currency', subtitle: r'priceFormatter writes them as $1234.50', diff --git a/example/lib/src/demo_state.dart b/example/lib/src/demo_state.dart index 898754c..0e81376 100644 --- a/example/lib/src/demo_state.dart +++ b/example/lib/src/demo_state.dart @@ -276,6 +276,13 @@ class DemoState extends ChangeNotifier { /// than ruling them at candle timestamps. bool evenGridColumns = false; + /// Whether the grid's horizontal and vertical lines are drawn. + bool showGridRows = true; + bool showGridColumns = true; + + /// Dashes the grid lines rather than drawing them solid. + bool dashedGrid = false; + /// Writes the prices as currency rather than as plain decimals. bool currencyPrices = false; @@ -330,6 +337,9 @@ class DemoState extends ChangeNotifier { gridColumnMode: evenGridColumns ? GridColumnMode.evenlySpaced : GridColumnMode.dateTicks, + showGridRows: showGridRows, + showGridColumns: showGridColumns, + gridDashPattern: dashedGrid ? const [4, 3] : null, ); } diff --git a/lib/src/chart_style.dart b/lib/src/chart_style.dart index 520c0ac..3df4ae3 100644 --- a/lib/src/chart_style.dart +++ b/lib/src/chart_style.dart @@ -479,6 +479,9 @@ class ChartStyle { this.gridRows = 8, this.gridColumns = 4, this.gridColumnMode = GridColumnMode.dateTicks, + this.showGridRows = true, + this.showGridColumns = true, + this.gridDashPattern, this.gridStrokeWidth = 0.5, this.separatorWidth = 1.0, this.hollowUpCandles = false, @@ -576,6 +579,23 @@ class ChartStyle { /// [GridColumnMode.dateTicks], matching the chart's previous behavior. final GridColumnMode gridColumnMode; + /// Whether the grid's horizontal (price) lines are drawn. + /// + /// Independent of [showGridColumns]; `hideGrid` on the widget still hides + /// the whole grid. The pane borders and separators are not part of the grid + /// and stay. + final bool showGridRows; + + /// Whether the grid's vertical (time) lines are drawn. See [gridColumnMode] + /// for where they go. + final bool showGridColumns; + + /// On/off lengths, in logical pixels, that dash the grid's lines — `[4, 3]` + /// is a 4px dash followed by a 3px gap. Null (the default) draws solid + /// lines. An empty list, or one with a non-positive or non-finite length, + /// is treated as solid. + final List? gridDashPattern; + /// Stroke width of the grid's hairlines. final double gridStrokeWidth; @@ -708,6 +728,9 @@ class ChartStyle { int? gridRows, int? gridColumns, GridColumnMode? gridColumnMode, + bool? showGridRows, + bool? showGridColumns, + List? gridDashPattern, double? gridStrokeWidth, double? separatorWidth, bool? hollowUpCandles, @@ -755,6 +778,9 @@ class ChartStyle { gridRows: gridRows ?? this.gridRows, gridColumns: gridColumns ?? this.gridColumns, gridColumnMode: gridColumnMode ?? this.gridColumnMode, + showGridRows: showGridRows ?? this.showGridRows, + showGridColumns: showGridColumns ?? this.showGridColumns, + gridDashPattern: gridDashPattern ?? this.gridDashPattern, gridStrokeWidth: gridStrokeWidth ?? this.gridStrokeWidth, separatorWidth: separatorWidth ?? this.separatorWidth, hollowUpCandles: hollowUpCandles ?? this.hollowUpCandles, diff --git a/lib/src/renderer/base_chart_renderer.dart b/lib/src/renderer/base_chart_renderer.dart index 6e3da68..96eb067 100644 --- a/lib/src/renderer/base_chart_renderer.dart +++ b/lib/src/renderer/base_chart_renderer.dart @@ -15,13 +15,16 @@ abstract class BaseChartRenderer { Color? separatorColor, Color? gridColumnColor, double gridStrokeWidth = 0.5, + this.showGridRows = true, + this.showGridColumns = true, + List? gridDashPattern, double separatorWidth = 1.0, this.labelCornerRadius = const BorderRadius.all(Radius.circular(3)), this.legendPadding = 4.0, this.legendBgColor, this.priceAxisGutter = 0.0, this.priceAxisGutterOnLeft = false, - }) { + }) : gridDashPattern = _validDash(gridDashPattern) { if (maxValue == minValue) { maxValue *= 1.5; minValue /= 2; @@ -38,6 +41,45 @@ abstract class BaseChartRenderer { ..strokeWidth = gridStrokeWidth; } + /// Whether the horizontal grid lines are drawn. + final bool showGridRows; + + /// Whether the vertical grid lines are drawn. + final bool showGridColumns; + + /// Validated dash lengths for the grid, or null for solid lines. + final List? gridDashPattern; + + static List? _validDash(List? pattern) { + if (pattern == null || pattern.isEmpty) return null; + if (pattern.any((v) => !v.isFinite || v <= 0)) return null; + return List.unmodifiable(pattern); + } + + /// Draws one grid line, dashed when [gridDashPattern] is set. + void drawGridLine(Canvas canvas, Offset from, Offset to, Paint paint) { + final pattern = gridDashPattern; + if (pattern == null) { + canvas.drawLine(from, to, paint); + return; + } + final delta = to - from; + final length = delta.distance; + if (length == 0) return; + final unit = delta / length; + var travelled = 0.0; + var i = 0; + while (travelled < length) { + final segment = pattern[i % pattern.length]; + if (i.isEven) { + final end = (travelled + segment).clamp(0.0, length); + canvas.drawLine(from + unit * travelled, from + unit * end, paint); + } + travelled += segment; + i++; + } + } + /// Corner radius of the legend pill. final BorderRadius labelCornerRadius; diff --git a/lib/src/renderer/indicator_pane_renderer.dart b/lib/src/renderer/indicator_pane_renderer.dart index 50ea283..714061b 100644 --- a/lib/src/renderer/indicator_pane_renderer.dart +++ b/lib/src/renderer/indicator_pane_renderer.dart @@ -39,6 +39,9 @@ class IndicatorPaneRenderer extends BaseChartRenderer { separatorColor: chartColors.effectiveSeparatorColor, gridColumnColor: chartColors.effectiveGridColumnColor, gridStrokeWidth: chartStyle.gridStrokeWidth, + showGridRows: chartStyle.showGridRows, + showGridColumns: chartStyle.showGridColumns, + gridDashPattern: chartStyle.gridDashPattern, separatorWidth: chartStyle.separatorWidth, labelCornerRadius: chartStyle.labelCornerRadius, legendPadding: chartStyle.legendPadding, @@ -250,16 +253,22 @@ class IndicatorPaneRenderer extends BaseChartRenderer { gridPaint, ); - if (indicator.fixedRange == null) { + if (showGridRows && indicator.fixedRange == null) { for (final value in valueTicks) { final y = getY(value); if (!y.isFinite || y <= chartRect.top || y >= chartRect.bottom) { continue; } - canvas.drawLine(Offset(0, y), Offset(chartRect.width, y), gridPaint); + drawGridLine( + canvas, + Offset(0, y), + Offset(chartRect.width, y), + gridPaint, + ); } } + if (!showGridColumns) return; final columns = columnXs ?? [ @@ -267,7 +276,8 @@ class IndicatorPaneRenderer extends BaseChartRenderer { chartRect.width / gridColumns * i, ]; for (final x in columns) { - canvas.drawLine( + drawGridLine( + canvas, Offset(x, chartRect.top - topPadding), Offset(x, chartRect.bottom), columnGridPaint, diff --git a/lib/src/renderer/main_renderer.dart b/lib/src/renderer/main_renderer.dart index e3f2e98..b69969b 100644 --- a/lib/src/renderer/main_renderer.dart +++ b/lib/src/renderer/main_renderer.dart @@ -68,6 +68,9 @@ class MainRenderer extends BaseChartRenderer { separatorColor: chartColors.effectiveSeparatorColor, gridColumnColor: chartColors.effectiveGridColumnColor, gridStrokeWidth: chartStyle.gridStrokeWidth, + showGridRows: chartStyle.showGridRows, + showGridColumns: chartStyle.showGridColumns, + gridDashPattern: chartStyle.gridDashPattern, separatorWidth: chartStyle.separatorWidth, labelCornerRadius: chartStyle.labelCornerRadius, legendPadding: chartStyle.legendPadding, @@ -1109,16 +1112,20 @@ class MainRenderer extends BaseChartRenderer { List? columnXs, }) { // Rule the pane where the labels are, not on evenly spaced pixels. - for (final value in priceTicks(gridRows)) { - final y = getY(value); - if (!y.isFinite || y < chartRect.top || y > chartRect.bottom) continue; - canvas.drawLine( - Offset(chartRect.left, y), - Offset(chartRect.right, y), - gridPaint, - ); + if (showGridRows) { + for (final value in priceTicks(gridRows)) { + final y = getY(value); + if (!y.isFinite || y < chartRect.top || y > chartRect.bottom) continue; + drawGridLine( + canvas, + Offset(chartRect.left, y), + Offset(chartRect.right, y), + gridPaint, + ); + } } + if (!showGridColumns) return; final columns = columnXs ?? [ @@ -1126,7 +1133,8 @@ class MainRenderer extends BaseChartRenderer { chartRect.width / gridColumns * i, ]; for (final x in columns) { - canvas.drawLine( + drawGridLine( + canvas, Offset(x, 0), Offset(x, chartRect.bottom), columnGridPaint, diff --git a/lib/src/renderer/vol_renderer.dart b/lib/src/renderer/vol_renderer.dart index e414528..2d1a0a5 100644 --- a/lib/src/renderer/vol_renderer.dart +++ b/lib/src/renderer/vol_renderer.dart @@ -30,6 +30,9 @@ class VolRenderer extends BaseChartRenderer { separatorColor: chartColors.effectiveSeparatorColor, gridColumnColor: chartColors.effectiveGridColumnColor, gridStrokeWidth: chartStyle.gridStrokeWidth, + showGridRows: chartStyle.showGridRows, + showGridColumns: chartStyle.showGridColumns, + gridDashPattern: chartStyle.gridDashPattern, separatorWidth: chartStyle.separatorWidth, labelCornerRadius: chartStyle.labelCornerRadius, legendPadding: chartStyle.legendPadding, @@ -207,12 +210,22 @@ class VolRenderer extends BaseChartRenderer { ); // A mark part-way up gives the bars something to be read against. - for (final value in volumeTicks) { - final y = getVolY(value); - if (!y.isFinite || y <= chartRect.top || y >= chartRect.bottom) continue; - canvas.drawLine(Offset(0, y), Offset(chartRect.width, y), gridPaint); + if (showGridRows) { + for (final value in volumeTicks) { + final y = getVolY(value); + if (!y.isFinite || y <= chartRect.top || y >= chartRect.bottom) { + continue; + } + drawGridLine( + canvas, + Offset(0, y), + Offset(chartRect.width, y), + gridPaint, + ); + } } + if (!showGridColumns) return; final columns = columnXs ?? [ @@ -220,7 +233,8 @@ class VolRenderer extends BaseChartRenderer { chartRect.width / gridColumns * i, ]; for (final x in columns) { - canvas.drawLine( + drawGridLine( + canvas, Offset(x, chartRect.top - topPadding), Offset(x, chartRect.bottom), columnGridPaint, diff --git a/pubspec.yaml b/pubspec.yaml index 23b4020..3a4b785 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: >- Candlestick charts for Flutter with 31 indicators, 29 drawing tools and 39 more chart widgets: depth, order flow, series, pie, treemap, sankey. Pure CustomPainter, no WebView. -version: 3.0.0 +version: 3.1.0 homepage: https://github.com/CtrlAltDevelop repository: https://github.com/CtrlAltDevelop/ohlcv_chart issue_tracker: https://github.com/CtrlAltDevelop/ohlcv_chart/issues diff --git a/test/grid_style_test.dart b/test/grid_style_test.dart new file mode 100644 index 0000000..0c6ec27 --- /dev/null +++ b/test/grid_style_test.dart @@ -0,0 +1,154 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:material_ui/material_ui.dart'; +import 'package:ohlcv_chart/ohlcv_chart.dart'; +import 'package:ohlcv_chart/src/renderer/chart_painter.dart'; + +import 'test_utils.dart'; + +const double _width = 500; +const double _height = 600; + +// Distinct colours so the probe can tell row lines from column lines. +const Color _rowColor = Color(0xFF112233); +const Color _columnColor = Color(0xFF445566); + +ChartPainter _painterOf(WidgetTester tester) { + final dynamic state = tester.state(find.byType(KChartWidget)); + // ignore: avoid_dynamic_calls + return state.painter as ChartPainter; +} + +Widget _chart({required ChartStyle style, int count = 3}) { + final data = candles(rampThenFall(count)); + DataUtil.calculate(data); + return MaterialApp( + home: Scaffold( + body: SizedBox( + width: _width, + height: _height, + child: KChartWidget( + data, + ChartColors(gridColor: _rowColor, gridColumnColor: _columnColor), + isTrendLine: false, + timeFrame: const Duration(minutes: 15), + chartStyle: style, + ), + ), + ), + ); +} + +/// Records every line drawn, with the colour it was drawn in. +class _LineProbe implements Canvas { + final List<(Offset, Offset, Color)> lines = []; + + Iterable<(Offset, Offset, Color)> get columns => + lines.where((l) => l.$3.toARGB32() == _columnColor.toARGB32()); + Iterable<(Offset, Offset, Color)> get rows => + lines.where((l) => l.$3.toARGB32() == _rowColor.toARGB32()); + + @override + void drawLine(Offset p1, Offset p2, Paint paint) { + lines.add((p1, p2, paint.color)); + } + + @override + dynamic noSuchMethod(Invocation invocation) => null; +} + +Future<_LineProbe> _paint(WidgetTester tester, ChartStyle style) async { + await tester.pumpWidget(_chart(style: style)); + final probe = _LineProbe(); + _painterOf(tester).paint(probe, const Size(_width, _height)); + return probe; +} + +/// Distinct x positions of the vertical column lines in the main pane. +List _columnXs(_LineProbe probe) => + ({for (final l in probe.columns) l.$1.dx}.toList()..sort()); + +void main() { + testWidgets('evenlySpaced spans the chart however few candles there are', ( + tester, + ) async { + final probe = await _paint( + tester, + ChartStyle(gridColumnMode: GridColumnMode.evenlySpaced), + ); + final xs = _columnXs(probe); + expect(xs.length, ChartStyle().gridColumns + 1); + final gaps = [for (var i = 1; i < xs.length; i++) xs[i] - xs[i - 1]]; + for (final gap in gaps) { + expect(gap, closeTo(gaps.first, 0.01)); + } + }); + + testWidgets('dateTicks stays the default and clusters on sparse data', ( + tester, + ) async { + expect(ChartStyle().gridColumnMode, GridColumnMode.dateTicks); + final probe = await _paint(tester, ChartStyle()); + // Three candles cross at most a couple of time buckets. + expect(_columnXs(probe).length, lessThanOrEqualTo(2)); + }); + + testWidgets('showGridColumns: false draws no vertical lines', (tester) async { + final probe = await _paint(tester, ChartStyle(showGridColumns: false)); + expect(probe.columns, isEmpty); + expect(probe.rows, isNotEmpty); + }); + + testWidgets('showGridRows: false draws no price lines', (tester) async { + final all = await _paint(tester, ChartStyle()); + final probe = await _paint( + tester, + ChartStyle( + showGridRows: false, + gridColumnMode: GridColumnMode.evenlySpaced, + ), + ); + // Only the pane borders remain; they are not part of the grid. + expect(probe.rows.length, lessThan(all.rows.length ~/ 2)); + expect(probe.columns, isNotEmpty); + }); + + testWidgets('gridDashPattern breaks lines into segments', (tester) async { + final solid = await _paint(tester, ChartStyle()); + final dashed = await _paint( + tester, + ChartStyle(gridDashPattern: const [4, 3]), + ); + expect(dashed.rows.length, greaterThan(solid.rows.length)); + expect(dashed.columns.length, greaterThan(solid.columns.length)); + final dashes = dashed.rows.where((l) => (l.$2 - l.$1).distance <= 4.001); + expect(dashes, isNotEmpty); + }); + + testWidgets('an invalid dash pattern falls back to solid', (tester) async { + final solid = await _paint(tester, ChartStyle()); + for (final bad in [ + const [], + const [0.0, 3.0], + const [4.0, -1.0], + const [double.infinity], + ]) { + final probe = await _paint(tester, ChartStyle(gridDashPattern: bad)); + expect(probe.rows.length, solid.rows.length, reason: '$bad'); + } + }); + + test('copyWith carries the grid options', () { + final style = ChartStyle().copyWith( + gridColumnMode: GridColumnMode.evenlySpaced, + showGridRows: false, + showGridColumns: false, + gridDashPattern: const [2, 2], + ); + expect(style.gridColumnMode, GridColumnMode.evenlySpaced); + expect(style.showGridRows, isFalse); + expect(style.showGridColumns, isFalse); + expect(style.gridDashPattern, const [2.0, 2.0]); + // Untouched fields survive. + expect(style.gridColumns, ChartStyle().gridColumns); + }); +} From b0b878805373ae0ee7af7cc9f7ff44d6300d1eb6 Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 02:00:10 +0330 Subject: [PATCH 03/12] docs: add a screenshot of the grid options --- screenshots/grid-options.jpg | Bin 0 -> 25280 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 screenshots/grid-options.jpg diff --git a/screenshots/grid-options.jpg b/screenshots/grid-options.jpg new file mode 100644 index 0000000000000000000000000000000000000000..74794e55463f5cce7dd30a355fd4e8a751c5bd6d GIT binary patch literal 25280 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 Date: Tue, 6 Oct 2026 02:03:46 +0330 Subject: [PATCH 04/12] chore: keep the version at 3.0.0 and list the grid options as unreleased --- CHANGELOG.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18a7460..0b87f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.1.0 - 2026-10-06 +## Unreleased ### Added diff --git a/pubspec.yaml b/pubspec.yaml index 3a4b785..23b4020 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: >- Candlestick charts for Flutter with 31 indicators, 29 drawing tools and 39 more chart widgets: depth, order flow, series, pie, treemap, sankey. Pure CustomPainter, no WebView. -version: 3.1.0 +version: 3.0.0 homepage: https://github.com/CtrlAltDevelop repository: https://github.com/CtrlAltDevelop/ohlcv_chart issue_tracker: https://github.com/CtrlAltDevelop/ohlcv_chart/issues From ef51d30d6da52df10d4248f8813915e0a001345a Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 02:28:22 +0330 Subject: [PATCH 05/12] feat: size indicator panes from the host, and maximize one KChartWidget.paneHeights and volumeHeight set the pane heights from outside, onPaneHeightsChanged reports drags, and KChartController gains setPaneHeight, resetPaneHeights, maximizePane, restorePanes and toggleMaximizePane. Host heights are not held to min/max pane height but are cut back to leave the candles a strip, and dragged heights now follow their pane through a reorder. Closes #8 --- CHANGELOG.md | 13 ++ doc/panes.md | 52 ++++- lib/src/chart_controller.dart | 67 ++++++ lib/src/k_chart_widget.dart | 258 +++++++++++++++++++++- lib/src/renderer/base_dimension.dart | 6 +- test/chart_link_test.dart | 11 + test/chart_overview_test.dart | 11 + test/pane_height_control_test.dart | 311 +++++++++++++++++++++++++++ 8 files changed, 713 insertions(+), 16 deletions(-) create mode 100644 test/pane_height_control_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b87f0c..3161fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,19 @@ lengths, e.g. `[4, 3]`. Null is solid; an empty list or a non-positive length is read as solid. - The example app has switches for all three. +- **`KChartWidget.paneHeights`** sizes the indicator panes from the host, and + `KChartWidget.volumeHeight` the volume pane. Heights are used as given — past + `ChartStyle.maxPaneHeight` if asked — and cut back only when a pane would + squeeze the candles out of the box. Null leaves the heights to the chart, as + before. `onPaneHeightsChanged` reports drags and controller changes. +- **`KChartController.maximizePane`, `restorePanes`, `toggleMaximizePane`, + `setPaneHeight`, `resetPaneHeights`, `paneHeights` and `maximizedPane`** drive + the same from code, for a "maximize this indicator" button. A maximized pane + follows the chart's size and its own indicator through a reorder. +- Dragged pane heights now move with their pane when `onReorderPane` reorders + the indicators, instead of staying at their position. +- `KChartHost` has five new members for the above; only an app that implements + it itself (a test double) needs to add them. ## 3.0.0 - 2026-09-19 diff --git a/doc/panes.md b/doc/panes.md index 4aa58e7..47f9ac7 100644 --- a/doc/panes.md +++ b/doc/panes.md @@ -29,13 +29,59 @@ KChartWidget( ## Height and order -Pane heights are managed by the chart, constrained between -`ChartStyle.minPaneHeight` and `maxPaneHeight`, and reset when the set of panes -changes. +By default pane heights are managed by the chart: 100 each, or whatever the user +dragged them to, constrained between `ChartStyle.minPaneHeight` and +`maxPaneHeight`, and reset when panes are added or removed. A reorder carries +each height along with its pane. Pane order is owned by your indicator list. The chart reports the move through `onReorderPane`, and your code applies it. +## Sizing panes from your code + +Two ways to set heights from outside — a "maximize this indicator" button is +the usual reason. + +**`KChartController`** takes commands: + +```dart +chart.maximizePane(1); // pane 1 fills the chart, candles shrink to a strip +chart.restorePanes(); // …and back +chart.toggleMaximizePane(1); // one button for both +chart.setPaneHeight(0, 220); // any single pane, any positive height +chart.resetPaneHeights(); // every pane back to the standard height +chart.paneHeights; // what is drawn now +``` + +A maximized pane follows the chart's size and moves with its indicator when you +reorder; dragging any pane lets go of it. It needs the chart to size its own +candle area, so it has no effect when `mBaseHeight` is set. + +**`KChartWidget.paneHeights`** is the same thing as state: + +```dart +KChartWidget( + candles, + ChartColors(), + indicators: indicators, + paneHeights: maximized ? [60, 500] : null, // null: the chart's own layout + volumeHeight: 80, // the volume pane, default 60 + onPaneHeightsChanged: (heights) => setState(() => saved = heights), +); +``` + +- Heights set from code are used as given, not held to `minPaneHeight` and + `maxPaneHeight`, so a pane can be taller than a user could drag it. A height + too big for the box is cut back so the candles keep a strip above it. +- A pane missing from a shorter list, or given a height that is not a positive + number, gets the standard height. +- The list is a starting point, not a lock: with `resizablePanes` on the user + can still drag, and `onPaneHeightsChanged` reports it (and controller + changes) so you can keep your copy in step. It is not called for changes you + made through `paneHeights`. +- Panes in `paneHeights` are matched by position. When `onReorderPane` moves + one, move its height with it. + `ChartStyle.paneResizeTolerance` and `paneGrabHeight` set the size of the resize and reorder hit areas. diff --git a/lib/src/chart_controller.dart b/lib/src/chart_controller.dart index b99c73c..d0b5ee0 100644 --- a/lib/src/chart_controller.dart +++ b/lib/src/chart_controller.dart @@ -63,6 +63,22 @@ abstract interface class KChartHost { /// Shifts the price axis to [pan], within the range the chart allows. void setChartPricePan(double pan); + + /// The height each indicator pane is drawn at, in the order they are stacked. + List get chartPaneHeights; + + /// Sizes pane [index] to [height]; false when there is no such pane or the + /// height is not a positive number. + bool setChartPaneHeight(int index, double height); + + /// Hands every pane back to the standard height. + void resetChartPaneHeights(); + + /// Which pane is stretched to fill the chart, or null when none is. + int? get chartMaximizedPane; + + /// Stretches pane [index] over the chart, or lets go of the stretch for null. + bool maximizeChartPane(int? index); } /// Drives a chart from outside it: where it is scrolled, how far it is zoomed, @@ -239,6 +255,57 @@ class KChartController extends ChangeNotifier { /// Shifts the price axis to [value], within the range the chart allows. void setPricePan(double value) => _host?.setChartPricePan(value); + + // ── Pane heights ───────────────────────────────────────────────────────── + + /// The height each indicator pane is drawn at, in the order they are stacked. + /// + /// Empty while nothing is attached. A pane that is [maximizedPane] reads as + /// the height it is currently stretched to. + List get paneHeights => _host?.chartPaneHeights ?? const []; + + /// Sizes the indicator pane at [index] to [height]. + /// + /// Unlike a drag, this is not held between `ChartStyle.minPaneHeight` and + /// `ChartStyle.maxPaneHeight`: any positive height is used as given, so a host + /// can grow a pane past what the user could drag it to. Panes are told apart + /// by position, and the heights are thrown away when panes are added or + /// removed. Reports whether there was such a pane to size. + bool setPaneHeight(int index, double height) => + _host?.setChartPaneHeight(index, height) ?? false; + + /// Hands every pane back to the standard height, undoing anything dragged, + /// set from here, or given through `KChartWidget.paneHeights`. + void resetPaneHeights() => _host?.resetChartPaneHeights(); + + /// Which pane is stretched to fill the chart, or null when none is. + int? get maximizedPane => _host?.chartMaximizedPane; + + /// Makes the pane at [index] take all the height it can, squashing the + /// candles to a strip above it and the other panes to their smallest. + /// + /// The stretch follows the chart's size and the pane itself, so it survives a + /// resize or the panes being reordered. [restorePanes] — or dragging any pane + /// — undoes it, handing back the heights the panes had before. Reports + /// whether there was such a pane. + /// + /// Needs the chart to size its own candle area, so it has no effect on one + /// given a fixed `mBaseHeight`. + bool maximizePane(int index) => _host?.maximizeChartPane(index) ?? false; + + /// Lets go of [maximizePane], putting the panes back as they were. + void restorePanes() => _host?.maximizeChartPane(null); + + /// Maximizes the pane at [index], or restores the panes if it already is. + /// + /// The shape a "maximize this indicator" button wants. + bool toggleMaximizePane(int index) { + if (maximizedPane == index) { + restorePanes(); + return true; + } + return maximizePane(index); + } } /// The candles in [candles] that cover [from] to [to], as an index pair. diff --git a/lib/src/k_chart_widget.dart b/lib/src/k_chart_widget.dart index c3214ac..581fa17 100644 --- a/lib/src/k_chart_widget.dart +++ b/lib/src/k_chart_widget.dart @@ -285,6 +285,9 @@ class KChartWidget extends StatefulWidget { this.resizablePanes = false, this.reorderablePanes = false, this.onReorderPane, + this.paneHeights, + this.volumeHeight, + this.onPaneHeightsChanged, this.onAddTrendLine, this.onAddHorizontalLine, this.onAddVerticalLine, @@ -692,6 +695,50 @@ class KChartWidget extends StatefulWidget { /// ``` final void Function(int from, int to)? onReorderPane; + /// Heights for the indicator panes, in the order they are stacked. + /// + /// Null — the default — leaves the heights to the chart: the standard height, + /// or whatever has been dragged when [resizablePanes] is on. A list sizes the + /// panes from outside, which is how a host makes one pane (nearly) fill the + /// chart and puts the layout back afterwards by passing null again. + /// + /// Heights are used as given, not held between `ChartStyle.minPaneHeight` and + /// `ChartStyle.maxPaneHeight` as a drag is, so a pane can be made taller than + /// a user could drag it. A pane missing from a shorter list, or given a + /// height that is not a positive number, gets the standard height. When the + /// chart sizes its own candle area, a height too big for the box is cut back + /// so the candles keep a strip above it. + /// + /// The list is a starting point rather than a lock: with [resizablePanes] on + /// the user can still drag from there, and [onPaneHeightsChanged] reports it + /// so the host can keep its own copy in step. The chart takes the list up + /// again whenever it changes. Panes are told apart by position, so when + /// [onReorderPane] moves one, move its height with it: + /// + /// ```dart + /// onReorderPane: (from, to) => setState(() { + /// indicators.insert(to, indicators.removeAt(from)); + /// heights.insert(to, heights.removeAt(from)); + /// }), + /// ``` + /// + /// To make a pane fill the chart without working out the heights yourself, + /// use `KChartController.maximizePane`. + final List? paneHeights; + + /// Height of the volume pane, in place of the standard 60. + /// + /// Null leaves it to the chart. It has no effect while [volHidden] is on, and + /// anything not a positive number is ignored. + final double? volumeHeight; + + /// Called with every pane's height after the user drags one, or after the + /// heights are changed through `KChartController`. + /// + /// Not called for a change made through [paneHeights], which the host + /// already knows about. + final ValueChanged>? onPaneHeightsChanged; + /// Shifted onto every candle's time before it is shown. /// /// Candle times are read as they come — usually UTC — and this is what puts @@ -1099,12 +1146,28 @@ class _KChartWidgetState extends State late Offset _toolbarOffset; - /// The height of each indicator pane, once the user has dragged one. + /// The height of each indicator pane, once one has been sized. /// - /// Empty until then, and thrown away whenever the panes change, so a new - /// indicator never inherits a height meant for a different one. + /// Dragged by the user, set through the controller, or given by + /// `KChartWidget.paneHeights`. Thrown away whenever panes are added or + /// removed, so a new indicator never inherits a height meant for a different + /// one; a reorder carries each height along with its pane. List _paneHeights = const []; + /// The indicator behind each of [_paneHeights], to follow a reorder. + List _paneOwners = const []; + + /// Whether the heights came from the host rather than the chart, so they are + /// cut back to fit the box instead of being trusted to. + bool _hostSizedPanes = false; + + /// The indicator whose pane is stretched over the chart, if any. + Indicator? _maximizedOwner; + + /// Set when `KChartWidget.paneHeights` changed and is yet to be taken up, + /// which waits for the build because the panes it applies to may change too. + bool _hostPaneHeightsStale = false; + /// True while a drag that began on the price labels is stretching the axis. bool _scalingPrice = false; @@ -1655,6 +1718,9 @@ class _KChartWidgetState extends State widget.replay?.addListener(_onReplayChanged); } if (!identical(oldWidget.candles, widget.candles)) _resolveIndicators(); + if (!listEquals(oldWidget.paneHeights, widget.paneHeights)) { + _hostPaneHeightsStale = true; + } if (oldWidget.lockPriceScale && !widget.lockPriceScale) { // Unlocked, the axis goes back to fitting the window. _lockedPriceRange = null; @@ -1781,6 +1847,7 @@ class _KChartWidgetState extends State paneCount: _resolved.panes.length, legendRowCount: _legendRowCount, paneHeights: _effectivePaneHeights, + volHeight: _volumeHeightOverride, ), ); } @@ -1793,21 +1860,119 @@ class _KChartWidgetState extends State ChartType get _chartType => widget.chartType ?? (widget.isLine ? ChartType.area : ChartType.candles); - /// How many legend rows sit above the candles: one per indicator group, plus - /// one for the OHLC legend when it is shown. + /// The volume pane's height when the host gave a usable one. + double? get _volumeHeightOverride { + final height = widget.volumeHeight; + return height != null && height.isFinite && height > 0 ? height : null; + } + + /// A height worth drawing: finite and above zero. + static bool _usableHeight(double height) => height.isFinite && height > 0; + /// The pane heights to lay out with, defaulting each pane to the standard - /// height until it has been dragged. + /// height until it has been sized. List get _effectivePaneHeights { - final count = _resolved.panes.length; - if (_paneHeights.length != count) { + final owners = [for (final pane in _resolved.panes) pane.indicator]; + final restructured = _paneHeights.length != owners.length; + + if (restructured) { + // Panes came or went: no height is safe to keep. _paneHeights = List.filled( - count, + owners.length, BaseDimension.secondaryPaneHeight, ); + _hostSizedPanes = false; + _maximizedOwner = null; + } else if (!listEquals(_paneOwners, owners)) { + // The same number of panes in another order: each height goes with its + // pane, and a pane never seen before starts at the standard height. + final before = _paneHeights; + _paneHeights = [ + for (final owner in owners) + _paneOwners.contains(owner) + ? before[_paneOwners.indexOf(owner)] + : BaseDimension.secondaryPaneHeight, + ]; + } + _paneOwners = owners; + + if (restructured || _hostPaneHeightsStale) { + final given = widget.paneHeights; + if (given != null) { + _paneHeights = [ + for (var i = 0; i < owners.length; i++) + i < given.length && _usableHeight(given[i]) + ? given[i] + : BaseDimension.secondaryPaneHeight, + ]; + _hostSizedPanes = true; + _maximizedOwner = null; + } else if (_hostPaneHeightsStale) { + // The host let go: back to the chart's own layout. + _paneHeights = List.filled( + owners.length, + BaseDimension.secondaryPaneHeight, + ); + _hostSizedPanes = false; + _maximizedOwner = null; + } + _hostPaneHeightsStale = false; } - return _paneHeights; + + final maximized = _maximizedPaneIndex; + if (maximized != null) return _heightsWithMaximized(maximized); + return _hostSizedPanes ? _cutBackToFit(_paneHeights) : _paneHeights; + } + + /// Where the maximized pane sits now, or null when none is. + int? get _maximizedPaneIndex { + final owner = _maximizedOwner; + if (owner == null) return null; + final at = _paneOwners.indexOf(owner); + return at == -1 ? null : at; + } + + /// Height the box has left for panes once the candles keep their strip, or + /// null when the box does not bound its height or the caller fixed the + /// candle height. + double? get _paneRoom { + if (widget.mBaseHeight != null || !mHeight.isFinite || mHeight <= 0) { + return null; + } + return mHeight - + BaseChartPainter.minMainHeight - + (widget.volHidden + ? 0 + : _volumeHeightOverride ?? BaseDimension.volumeHeight) - + BaseDimension.legendRowHeight * _legendRowCount; + } + + /// [_paneHeights] with the maximized pane given everything the others leave, + /// and the others at their smallest. + List _heightsWithMaximized(int index) { + final small = widget.chartStyle.minPaneHeight; + final room = _paneRoom; + return [ + for (var i = 0; i < _paneHeights.length; i++) + i != index + ? small + : room == null + ? _paneHeights[i] + : math.max(small, room - small * (_paneHeights.length - 1)), + ]; } + /// Host-given [heights], with any single pane too tall for the box cut back + /// so the candles are not squeezed out. + List _cutBackToFit(List heights) { + final room = _paneRoom; + if (room == null) return heights; + final ceiling = math.max(room, widget.chartStyle.minPaneHeight); + return [for (final height in heights) math.min(height, ceiling)]; + } + + /// How many legend rows sit above the candles: one per indicator group, plus + /// one for the OHLC legend when it is shown. int get _legendRowCount => _resolved.legendRowCount + (widget.showOhlcLegend ? 1 : 0) + @@ -2119,6 +2284,7 @@ class _KChartWidgetState extends State paneCount: _resolved.panes.length, legendRowCount: _legendRowCount, paneHeights: _effectivePaneHeights, + volHeight: _volumeHeightOverride, ); // Taken from the axis as it stands, which is last frame's fit: this @@ -2594,12 +2760,17 @@ class _KChartWidgetState extends State final heights = [..._effectivePaneHeights]; if (index < 0 || index >= heights.length) return; + // A pane the host made taller than the usual ceiling keeps its height + // under the finger rather than snapping down to it. heights[index] = (heights[index] + delta).clamp( widget.chartStyle.minPaneHeight, - widget.chartStyle.maxPaneHeight, + math.max(widget.chartStyle.maxPaneHeight, heights[index]), ); _paneHeights = heights; + _maximizedOwner = null; notifyChanged(); + widget.controller?.hostChanged(); + widget.onPaneHeightsChanged?.call(List.unmodifiable(heights)); } /// Works out where the pane being dragged was dropped, and reports the move. @@ -4130,6 +4301,71 @@ class _KChartWidgetState extends State widget.controller?.hostChanged(); } + @override + List get chartPaneHeights => + List.unmodifiable(_effectivePaneHeights); + + @override + bool setChartPaneHeight(int index, double height) { + final heights = [..._effectivePaneHeights]; + if (index < 0 || index >= heights.length || !_usableHeight(height)) { + return false; + } + heights[index] = height; + setState(() { + _paneHeights = heights; + _hostSizedPanes = true; + _maximizedOwner = null; + }); + widget.controller?.hostChanged(); + widget.onPaneHeightsChanged?.call(List.unmodifiable(heights)); + return true; + } + + @override + void resetChartPaneHeights() { + _effectivePaneHeights; + final untouched = + _maximizedOwner == null && + !_hostSizedPanes && + _paneHeights.every((h) => h == BaseDimension.secondaryPaneHeight); + if (untouched) return; + setState(() { + _paneHeights = List.filled( + _paneOwners.length, + BaseDimension.secondaryPaneHeight, + ); + _hostSizedPanes = false; + _maximizedOwner = null; + }); + widget.controller?.hostChanged(); + widget.onPaneHeightsChanged?.call(List.unmodifiable(_paneHeights)); + } + + @override + int? get chartMaximizedPane { + _effectivePaneHeights; + return _maximizedPaneIndex; + } + + @override + bool maximizeChartPane(int? index) { + _effectivePaneHeights; + if (index == null) { + if (_maximizedOwner == null) return true; + setState(() => _maximizedOwner = null); + widget.controller?.hostChanged(); + return true; + } + if (index < 0 || index >= _paneOwners.length) return false; + final owner = _paneOwners[index]; + if (_maximizedOwner != owner) { + setState(() => _maximizedOwner = owner); + widget.controller?.hostChanged(); + } + return true; + } + @override void showChartCrosshair(int? index, {double? price}) { if (index == null) { diff --git a/lib/src/renderer/base_dimension.dart b/lib/src/renderer/base_dimension.dart index ed712a6..53e5166 100644 --- a/lib/src/renderer/base_dimension.dart +++ b/lib/src/renderer/base_dimension.dart @@ -10,9 +10,10 @@ class BaseDimension { required int paneCount, required int legendRowCount, List? paneHeights, + double? volHeight, }) { _mBaseHeight = mBaseHeight; - _mVolumeHeight = volHidden ? 0 : volumeHeight; + _mVolumeHeight = volHidden ? 0 : volHeight ?? volumeHeight; _paneHeights = [ for (var i = 0; i < paneCount; i++) paneHeights != null && i < paneHeights.length @@ -49,6 +50,7 @@ class BaseDimension { required int paneCount, required int legendRowCount, List? paneHeights, + double? volHeight, }) { var panes = 0.0; for (var i = 0; i < paneCount; i++) { @@ -56,7 +58,7 @@ class BaseDimension { ? paneHeights[i] : secondaryPaneHeight; } - return (volHidden ? 0 : volumeHeight) + + return (volHidden ? 0 : volHeight ?? volumeHeight) + panes + legendRowHeight * legendRowCount; } diff --git a/test/chart_link_test.dart b/test/chart_link_test.dart index cca9060..a5fd27c 100644 --- a/test/chart_link_test.dart +++ b/test/chart_link_test.dart @@ -124,6 +124,17 @@ class _FakeHost implements KChartHost { pricePan = pan; controller?.hostChanged(); } + + @override + List get chartPaneHeights => const []; + @override + bool setChartPaneHeight(int index, double height) => false; + @override + void resetChartPaneHeights() {} + @override + int? get chartMaximizedPane => null; + @override + bool maximizeChartPane(int? index) => false; } ({KChartController controller, _FakeHost host}) chart({ diff --git a/test/chart_overview_test.dart b/test/chart_overview_test.dart index f075dad..d210150 100644 --- a/test/chart_overview_test.dart +++ b/test/chart_overview_test.dart @@ -63,6 +63,17 @@ class _FakeHost implements KChartHost { double get chartPricePan => 0; @override void setChartPricePan(double pan) {} + + @override + List get chartPaneHeights => const []; + @override + bool setChartPaneHeight(int index, double height) => false; + @override + void resetChartPaneHeights() {} + @override + int? get chartMaximizedPane => null; + @override + bool maximizeChartPane(int? index) => false; } List series(int count) => [ diff --git a/test/pane_height_control_test.dart b/test/pane_height_control_test.dart new file mode 100644 index 0000000..b6d25dc --- /dev/null +++ b/test/pane_height_control_test.dart @@ -0,0 +1,311 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:material_ui/material_ui.dart'; +import 'package:ohlcv_chart/ohlcv_chart.dart'; +import 'package:ohlcv_chart/src/renderer/chart_painter.dart'; + +import 'test_utils.dart'; + +const double _boxHeight = 700; +const double _tallBox = 1100; + +List _candles() { + final data = candles(rampThenFall(60)); + DataUtil.calculate(data); + return data; +} + +Widget _chart({ + List? indicators, + List? paneHeights, + double? volumeHeight, + KChartController? controller, + ValueChanged>? onChanged, + bool resizable = false, + double boxHeight = _boxHeight, +}) => MaterialApp( + home: Scaffold( + body: SizedBox( + width: 500, + height: boxHeight, + child: KChartWidget( + _candles(), + ChartColors(), + isTrendLine: false, + timeFrame: const Duration(minutes: 1), + showNowPrice: false, + indicators: indicators ?? [RsiIndicator(), MacdIndicator()], + paneHeights: paneHeights, + volumeHeight: volumeHeight, + controller: controller, + onPaneHeightsChanged: onChanged, + resizablePanes: resizable, + ), + ), + ), +); + +ChartPainter _painterOf(WidgetTester tester) { + final paint = tester.widget( + find + .descendant( + of: find.byType(KChartWidget), + matching: find.byWidgetPredicate( + (w) => w is CustomPaint && w.painter is ChartPainter, + ), + ) + .first, + ); + return paint.painter! as ChartPainter; +} + +List _paneHeightsOf(WidgetTester tester) => [ + for (final r in _painterOf(tester).mSecondaryRectList) r.mRect.height, +]; + +/// A test window tall enough for [_tallBox]. +void _tallView(WidgetTester tester) { + tester.view.physicalSize = const Size(800, 1200); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); +} + +void main() { + testWidgets('panes keep the standard height by default', (tester) async { + await tester.pumpWidget(_chart()); + final heights = _paneHeightsOf(tester); + expect(heights, hasLength(2)); + // The pane rect leaves room for the legend strip above it. + expect(heights[0], heights[1]); + }); + + testWidgets('paneHeights sizes the panes and null puts them back', ( + tester, + ) async { + await tester.pumpWidget(_chart()); + final standard = _paneHeightsOf(tester); + + await tester.pumpWidget(_chart(paneHeights: [250, 80])); + final sized = _paneHeightsOf(tester); + expect(sized[0], greaterThan(sized[1])); + expect(sized[0] - standard[0], closeTo(150, 0.01)); + expect(sized[1] - standard[1], closeTo(-20, 0.01)); + + await tester.pumpWidget(_chart()); + expect(_paneHeightsOf(tester), standard); + }); + + testWidgets('a short or unusable list falls back to the standard height', ( + tester, + ) async { + await tester.pumpWidget(_chart()); + final standard = _paneHeightsOf(tester); + + await tester.pumpWidget(_chart(paneHeights: [double.nan, -5])); + expect(_paneHeightsOf(tester), standard); + + await tester.pumpWidget(_chart(paneHeights: [200])); + final sized = _paneHeightsOf(tester); + expect(sized[0] - standard[0], closeTo(100, 0.01)); + expect(sized[1], standard[1]); + }); + + testWidgets('a height past the drag ceiling is used, not clamped', ( + tester, + ) async { + _tallView(tester); + await tester.pumpWidget(_chart(boxHeight: _tallBox)); + final standard = _paneHeightsOf(tester); + + await tester.pumpWidget( + _chart(paneHeights: [450, 100], boxHeight: _tallBox), + ); + expect(_paneHeightsOf(tester)[0] - standard[0], closeTo(350, 0.01)); + }); + + testWidgets('a height too big for the box is cut back to leave the candles', ( + tester, + ) async { + await tester.pumpWidget(_chart(paneHeights: [5000, 100])); + final painter = _painterOf(tester); + expect(painter.mMainRect.height, greaterThanOrEqualTo(60)); + expect( + painter.mSecondaryRectList.last.mRect.bottom, + lessThanOrEqualTo(_boxHeight), + ); + }); + + testWidgets('volumeHeight resizes the volume pane', (tester) async { + await tester.pumpWidget(_chart()); + final standard = _painterOf(tester).mVolRect!.height; + + await tester.pumpWidget(_chart(volumeHeight: 140)); + expect(_painterOf(tester).mVolRect!.height - standard, closeTo(80, 0.01)); + + await tester.pumpWidget(_chart(volumeHeight: -1)); + expect(_painterOf(tester).mVolRect!.height, standard); + }); + + testWidgets('the controller sizes and resets a pane', (tester) async { + final controller = KChartController(); + final reported = >[]; + await tester.pumpWidget( + _chart(controller: controller, onChanged: reported.add), + ); + final standard = _paneHeightsOf(tester); + expect(controller.paneHeights, [100, 100]); + + expect(controller.setPaneHeight(1, 300), isTrue); + await tester.pump(); + expect(controller.paneHeights, [100, 300]); + expect(_paneHeightsOf(tester)[1] - standard[1], closeTo(200, 0.01)); + expect(reported.last, [100, 300]); + + expect(controller.setPaneHeight(5, 300), isFalse); + expect(controller.setPaneHeight(0, 0), isFalse); + expect(controller.setPaneHeight(0, double.infinity), isFalse); + + controller.resetPaneHeights(); + await tester.pump(); + expect(_paneHeightsOf(tester), standard); + expect(reported.last, [100, 100]); + }); + + testWidgets('maximizePane fills the box and restorePanes undoes it', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + final standard = _paneHeightsOf(tester); + final mainBefore = _painterOf(tester).mMainRect.height; + + expect(controller.maximizePane(1), isTrue); + await tester.pump(); + expect(controller.maximizedPane, 1); + + final painter = _painterOf(tester); + final heights = _paneHeightsOf(tester); + expect(heights[1], greaterThan(heights[0] * 3)); + expect(painter.mMainRect.height, lessThan(mainBefore)); + expect(painter.mMainRect.height, greaterThanOrEqualTo(60)); + expect( + painter.mSecondaryRectList.last.mRect.bottom, + lessThanOrEqualTo(_boxHeight), + ); + + controller.restorePanes(); + await tester.pump(); + expect(controller.maximizedPane, isNull); + expect(_paneHeightsOf(tester), standard); + }); + + testWidgets('toggleMaximizePane flips and the stretch follows a reorder', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + + controller.toggleMaximizePane(0); + await tester.pump(); + expect(controller.maximizedPane, 0); + + // The same two indicators in the other order: the stretch goes with RSI. + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [MacdIndicator(), RsiIndicator()], + ), + ); + expect(controller.maximizedPane, 1); + + controller.toggleMaximizePane(1); + await tester.pump(); + expect(controller.maximizedPane, isNull); + }); + + testWidgets('maximizing is dropped when the panes change', (tester) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + controller.maximizePane(0); + await tester.pump(); + + await tester.pumpWidget( + _chart(controller: controller, indicators: [RsiIndicator()]), + ); + expect(controller.maximizedPane, isNull); + }); + + testWidgets('a reorder carries the dragged heights with their panes', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + controller.setPaneHeight(0, 300); + await tester.pump(); + expect(controller.paneHeights, [300, 100]); + + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [MacdIndicator(), RsiIndicator()], + ), + ); + expect(controller.paneHeights, [100, 300]); + }); + + testWidgets('adding a pane throws the sized heights away', (tester) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + controller.setPaneHeight(0, 300); + await tester.pump(); + + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator(), KdjIndicator()], + ), + ); + expect(controller.paneHeights, [100, 100, 100]); + }); + + testWidgets('a drag on a pane edge reports the new heights', (tester) async { + final reported = >[]; + await tester.pumpWidget(_chart(resizable: true, onChanged: reported.add)); + final edge = _painterOf(tester).mSecondaryRectList.first.mRect.bottom; + final box = tester.getTopLeft(find.byType(KChartWidget)); + + final gesture = await tester.startGesture(box + Offset(200, edge)); + await gesture.moveBy(const Offset(0, 20)); + await gesture.moveBy(const Offset(0, 20)); + await gesture.up(); + await tester.pump(); + + expect(reported, isNotEmpty); + expect(reported.last[0], greaterThan(100)); + expect(reported.last[1], 100); + }); + + testWidgets('a host-sized pane taller than the ceiling is not snapped down ' + 'by a drag', (tester) async { + _tallView(tester); + final reported = >[]; + await tester.pumpWidget( + _chart( + resizable: true, + paneHeights: [450, 100], + onChanged: reported.add, + boxHeight: _tallBox, + ), + ); + final edge = _painterOf(tester).mSecondaryRectList.first.mRect.bottom; + final box = tester.getTopLeft(find.byType(KChartWidget)); + + final gesture = await tester.startGesture(box + Offset(200, edge)); + await gesture.moveBy(const Offset(0, -20)); + await gesture.moveBy(const Offset(0, -20)); + await gesture.up(); + await tester.pump(); + + expect(reported, isNotEmpty); + expect(reported.last[0], greaterThan(400)); + }); +} From f1c57c1984f40a9643347e25e49201b481f0685f Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 02:53:24 +0330 Subject: [PATCH 06/12] docs: demo maximizing a pane in the example app, with screenshots --- CHANGELOG.md | 2 +- doc/panes.md | 4 +++ example/lib/src/controls.dart | 41 ++++++++++++++++++++++++++++ screenshots/panes-default.jpg | Bin 0 -> 56304 bytes screenshots/panes-maximized-obv.jpg | Bin 0 -> 47184 bytes screenshots/panes-maximized-rsi.jpg | Bin 0 -> 47397 bytes 6 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 screenshots/panes-default.jpg create mode 100644 screenshots/panes-maximized-obv.jpg create mode 100644 screenshots/panes-maximized-rsi.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 3161fcd..3bb3027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - **`ChartStyle.gridDashPattern`** dashes the grid lines with a list of on/off lengths, e.g. `[4, 3]`. Null is solid; an empty list or a non-positive length is read as solid. -- The example app has switches for all three. +- The example app has switches for all three, and a chip per pane that maximizes it. - **`KChartWidget.paneHeights`** sizes the indicator panes from the host, and `KChartWidget.volumeHeight` the volume pane. Heights are used as given — past `ChartStyle.maxPaneHeight` if asked — and cut back only when a pane would diff --git a/doc/panes.md b/doc/panes.md index 47f9ac7..f07d38d 100644 --- a/doc/panes.md +++ b/doc/panes.md @@ -53,6 +53,10 @@ chart.resetPaneHeights(); // every pane back to the standard height chart.paneHeights; // what is drawn now ``` +![Three panes at the standard height](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-default.jpg) +![The RSI pane maximized](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-maximized-rsi.jpg) +![The OBV pane maximized](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-maximized-obv.jpg) + A maximized pane follows the chart's size and moves with its indicator when you reorder; dragging any pane lets go of it. It needs the chart to size its own candle area, so it has no effect when `mBaseHeight` is set. diff --git a/example/lib/src/controls.dart b/example/lib/src/controls.dart index a438f57..a41a5e4 100644 --- a/example/lib/src/controls.dart +++ b/example/lib/src/controls.dart @@ -412,6 +412,7 @@ class Controls extends StatelessWidget { value: state.reorderablePanes, onChanged: (v) => state.update(() => state.reorderablePanes = v), ), + _PaneMaximizer(state: state), ], ), _Section( @@ -957,3 +958,43 @@ class _Choice extends StatelessWidget { ); } } + +/// One button per indicator pane that stretches it over the chart, through +/// `KChartController.toggleMaximizePane`, and one that puts the panes back. +class _PaneMaximizer extends StatelessWidget { + const _PaneMaximizer({required this.state}); + + final DemoState state; + + @override + Widget build(BuildContext context) { + final panes = [ + for (final indicator in state.indicators) + if (indicator.placement != IndicatorPlacement.overlay) indicator, + ]; + return ListenableBuilder( + listenable: state.chart, + builder: (context, _) { + final maximized = state.chart.maximizedPane; + return Wrap( + spacing: 8, + children: [ + for (var i = 0; i < panes.length; i++) + FilterChip( + label: Text('Maximize ${panes[i].label}'), + selected: maximized == i, + onSelected: (_) => state.chart.toggleMaximizePane(i), + ), + ActionChip( + label: const Text('Reset heights'), + onPressed: () { + state.chart.restorePanes(); + state.chart.resetPaneHeights(); + }, + ), + ], + ); + }, + ); + } +} diff --git a/screenshots/panes-default.jpg b/screenshots/panes-default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..30c521cb98b4fbe080539284479900df8096ae2a GIT binary patch literal 56304 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 VkeGh*>{baEZqoU;3cvh0_djm9-YWnA literal 0 HcmV?d00001 diff --git a/screenshots/panes-maximized-rsi.jpg b/screenshots/panes-maximized-rsi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a3d239751cacee74697dfc53fe36da7116cbc6e GIT binary patch literal 47397 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.>1|wPaS8`btM?s_d>$E) z+@I_#zoUv%a@i;=6sW*&F(VuXVTQ`7VrO1XfJB^!^z2WJGx``}CDEMGeP8;iP#6ZC zmb7`#58syKJ%vNa!?eXqaxF_TMXsE|^{6pgi9ct{7ZzOi&wkRX;p&SDh|t2cNg1x6 zYhnf6QpRLE63AcR?AZ1rwc#KE6+C(+1v2`eHdi?%!sgKh*ZL%VBQd+~oseoNRU0Ei zFUBsLoFBQ=vL>fE8=PqR{wy2*t@^f2Ufot>o!pe2ow}s{3_1jW%E_FapP1=4bE2~> zXO6G9uJ<*kOiPnaf|EwydvnFOD5>1mEb4n1$7)#dyI;wkHcv!EM%qklCKQ$?9O*Hq z>)JQ@d~=}8P)TONkb%eAW`1MnCjyKQ{?G)WH zhu($6nqZymF%2i27>S*U;=SoCNOZ>QLGMg@m`mRz59X=UN`wdc>L-OWSaq*j%W(Nc%I8WpTzeO7>D+Z@yb&Pp~6sOzpyh@8Wmr(+&C)s?b z>qtWEuUuvhbj)x5q$w7nb%;*psB^*!e_@)RPOebK1ktv~ilphhf3EL!8!@-rMByum z)*RREpxHM=|C~Vac}+#4J_%GmQ-#-Ri9#VIL84y#yR$wX{Yx*aA@Vq2dh>IbINjA-i0`f-KDv(sLSfJM&skF!RE# z$IQ;iDi%NbZLD0HlwauG{Wynaw<9SKufCwr)$wB>NmI`??~&W*Fo zvN&NWvTpWnaUobag^?mrxp?9o2XTi+JT3Xr!%g_7v6wMTPGJx)S*ViroAOB#|FB|4 zX2?!tvY;10q-ATjxWFtlfd7A8T#};|LAHeQ=F7o? zt-*&T#E9=`?@HDJ8@W4wz7(4}UYn*Un{l#|ETHC+4 zCGq-_5Vf9GCPC}mwJOr0!k0Wp!G1@C?&XSU+jqKOe?Al@+|N{)Tu80nGCzKSQ+4v< z2nBnf%vbnP(GGjSprzGI0X8UZlF63-Hn_e-x26`9bU+(|?t|@KsJf z%*+-4WiE0hx+I#ws$@!KO>O;_^3ze!-A-nhdCi?WDaN#T5S@av^im z^zf-z3b$x5tp~$P+=CA(Eu~*u0B&n+Y!|-bo%qV+Le1nty8FI&@7IL!>)R!?1g#o( zV*cy z_E$v$`&0eDSgL=JlK(}(*!)@IcXg3p7xY_x?O0hhwO1*(H~PoZ$ou*CHrl+it2bT< zvEtw9CHoyNoZJNTy34CTG+58vB=B9sV-D$T4(Y7zzrC=}EAV{C#qlGizfXV5RZxJ$ zP^o~w?dN*_oY80w92N^4#;tzPI{yv8tuRN)tJ1}($CO2KunfKZPV(1^+6p2^A@VY0 zA~orNsIkym#T%etZl{Ai<92ax369jlHb!Khqw_j_@BP>v(>jbChg6U~2h^gycBfr- zbmkaN&c1E3*j~N606AJNJHO$?{ZgAGl3>6yn?bwnvE?7I+5J=c!GNO7@OI@PkQC_! zS^uv{I~$3qRV61{In~F-e|4E^OiCSOvtj-Mk}$x!tZ`qVEpL50gS>Bj`0^adep$?p zdnIW3R0fBoB)d4^1u*WsYa2F8PR49k@Lf7i0q0z?mwOF0sRIeq^+|%~f{Rz*Y^X`} zW%C_jydIiR;I$c2b`8wXLsbf+lsM?N_n0yXn#!+n;bHxTQ&4yqCj_H3q;%$FIU0{9 zqxo=?k(JMZ2v_*3i*(Lu&!4e#0HgO`0kE&xjn7y}JW3G`xpp?);z+I+R;X`T+7J(M zXR4Q|sAK}mlnDl9t_2#B_}9Ve$~q{G$&!#)%I9Ts`SrKT#h|C8wFC zM|V~>)A+m&OOo@{K-ZK_At_gj<~w6GqX@-@^+qctnIQE^`GJsrbP=0Po3QBj)+H+t zg98swFK0RdjWhe)_U|w6h0|kj%^#)fto+pSM^IsT`Utg{l~u(Ur3Yz@_suObrLOg^ zvWJe(Y?xN_!5&Fc2B@D8uQ^D$EY(-*7Z^HDYF|E~rtdi8(3E0&pYI)a?zj_<0fVOH zI@xEei2)>0jr{j({HVu3En1DKuje3W_|X`c+|B9o--d{AmZ|-p2YT!Ev9kWn0`PBm zH?^IOox*obzF~J6yQ(sPIpOs*Gn=paS8@UuhQu2Ja4&DQQ5l=dUp#eh{y_iX^8#cf z0-veSEYz=|Jat7NM5<5yUtpESklf4A_0 zZ_1(My;nR7e|dGp$?|cEv4mT-;&`KH`H&j*lC7v9 z5FcD6Hu|aDnK|Ao@ny#^6s|Qg{n|^F#XDB+R)3{i^yH%lVvk>;|GFRmYRN9e#Rmj$ z;a3h}N*pt-7oyYsLa{)$ceypEm^2j0f44^TA3uc8u|FqpKr1Ia9sFb{W`v{iXYa^p zGBQ>7%J0M)DTR{N3ygdF0jiJ!99G9=I;n5bTK#pASpmo0!2f1}m64y6uw!DTK@KC} zV2&tHCZxquc6$(D7dTkHv4l%5Flop?u`g<>lLw9*KYBVWhNVfVuhSzvlX^q|bR2#O zpo{t9L0NsFn36Elhv7E7NHRwGRRS-9r3`gpSR zT|?hDCR1?fF@{ikTf+~xJ~diFXPsIL?)S~S5_5|b35FZAsa3$e0hGpIed>tl)7dvP z(uRfLub}=R&YbE_Ui}Zl?o6x#(>G}5m*sfjKsxNucN4-T=@>0!eN3D@P&hNJ2^(Y9 zRZ530_-K|%_FS=^C>WTNmf^Lztg&yt*aT~*gD%SWD?D)}Y7?XL_` zSdsyM2hL@NAYc?8mOYfrT$k&KhS6vH_@PjB4NSk=XkMBlnq_LJ8`jxm}aRR#h=1EC=kG*U$QIrvZbiFp_hTq)d-eqGrY z&9UhTPpZ6&J~r6*W}~2$K1K9h>^#eh_<*tO%@&2LzsvpDwr^op{Oj9+v17EF0Ito( z)aZF_1b<`^U(FeR;^ZV8lv1Z>vUcGq2)P-CRo0@tgZVHsxT#A;XpR~(tv^a&Pra|2 z>yf#1hT1R~&1x=JYKjo@#hNm-!ZIL&eL%;{cMJ9#z&xlt^!2ZJ=7RT{qts-Vl&w&*~0DF6Nx77!_ zV8CQ#$Gk;m;;9@^G}1NS$vyQ{W=_?R6Ie8>-P`k$iiQ_Er!<7@h(|kMTWUU&5+Dfc ziuV?RSM7=1NtN-9wJm}CL21LjPd(1IGlz$f*eU@gAtwH|$(*ujgyxL;*o6E{@Hq6) zEa-S`Ck67;K;0%fS@%U=X72VzB};D%ROtufdnTPdN>3e7`$8QArE|5jTP^^u=w(c; zH$m$|>;vC;QCmuF=U#)TKB+A;qxeayXV?Wv= zW8Wl{HP}d96`+pCr+c^FS@hzB@X(d=ZoS-Ai&Rt#GX$i6wQ#FY2O!W+tRyV*^@3Wxh&kAA!{b1Z%j z0!`}7Z+^$q{QJ!KH#yE>@d3n@T0c6kxzlAJv(11*nt8H@VwG_5>B*wiLRp}6|1Ab)W*_B*rj_XZ263Vy^0dTmF#b_VCt2Rmf!kj#* z!1rN6Ue9Q91=!rHIoVAcHQTq(#616#Bv>BB`nax?p5xzX&?_d<~BEIEueGq zH{k8)KmcDI9Xrms2!1hVHNFSWfhDdJ#?s;TUaxxYTa8gk&3)U{A=j6Wwi7zm2#=q- z>BA_h8J9u%WigVmvWq(WrLeMVo$;Nia(hFAX&7fwX8VL7KptQXK$Tp3g!||I>QA-s zKLRcuGyeuGJiE8L(VRl65^9ugM~?&H4_RH1cN*Fn3dHk#+*%4GL!VCApEx|4o%mU> zYA7knSMvijXl=`s)SLlQtT>R>Nn^r^(4+d{0&6cvlPha?blc^ZgtD>F-dz)-C8W89*R@jR zmKS&KK}h*{dl&)*?P>#FD-0ogUtoinRB@Cc#-YMFLTR`hvMh>y={*hOuu~o!oVq)X zD0uVLhWnVql*w;EcT_iRFtOs({(8b1!}$syGjLY3C)Slw|zA8dSV2sUi6gA zsCEI{-_$kE9tx4@E?PC25IK3{225rX-LerSM;Fy{hn$rdK;R4bB1H_r>a@lrs1S6n zR3)ErL&je#WPsx^wkkV&hPF~GvR(GIpt1|#I=H!9HT2&3bPVV8@gCm%T1UB%ZDl_j z;EU|xS>;#4Ck;Bu_lr^ecbf!q6!r@le3GKF(VFvnp0DI6mPA;|?StXSFy#v%=m|p+ zBkcW8h2Tk(DL$96Mn9BTVUL~;S<61upxaoZ2`)#uu;Z;5G7*V+XDcc>D9lw@z6%^V zpC@WA^ck1-)Qv_zzlMSRyXzjCS)qm0-WC}DlGzo@4#p(awm*x1`Zat;r$mui)7H8!4#ooO^n>fMV%PA z_{UU8H(AUrA%#lN#Qt#T5gDy6E{5}v66y9J*& zfs(CFSI8@?K(zDRJrmoou80-hu1q@jI!Q$tvtI5<;l{_|j}`5gDiVkED8}Y6yj3Oa zU7fCtQ=EL*Lmw{&yf45i_I$|$mMyhzUR!l}`;(d(DNc?h1qo;y>a44!ECZe1%I;?>Pv!kUHiFWbS0BN>`G zaIB`)$Ap(P+xCHy8-Ap!lE+LcbkC*4B*G%g3LQ=xK*3?Uz77~ErAI)k15N_`8cD4V_96FKT5fKvGk;8D*(Szif znZ1#loTQ^US8Zxz2v^DJq-0ihrPTnq8jMLcb{~g^?ubRLPCv=%(8m?@Oe*rz{S^ld zE&MSz1hZoL+1W@^? Date: Tue, 6 Oct 2026 03:01:36 +0330 Subject: [PATCH 07/12] feat: let the host own pane heights, and maximize the volume pane While KChartWidget.paneHeights is given the host owns the heights: a drag or KChartController.setPaneHeight is reported through onPaneHeightsChanged and the pane moves when the list is passed back. KChartController.maximizeVolume stretches the volume pane the way maximizePane does an indicator pane, and the example app gets a chip for it. --- CHANGELOG.md | 9 +- doc/panes.md | 13 ++- example/lib/src/controls.dart | 8 ++ lib/src/chart_controller.dart | 21 +++- lib/src/k_chart_widget.dart | 159 ++++++++++++++++++++--------- test/chart_link_test.dart | 4 + test/chart_overview_test.dart | 4 + test/pane_height_control_test.dart | 138 +++++++++++++++++++++++++ 8 files changed, 297 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb3027..a5b8402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,14 +19,17 @@ `KChartWidget.volumeHeight` the volume pane. Heights are used as given — past `ChartStyle.maxPaneHeight` if asked — and cut back only when a pane would squeeze the candles out of the box. Null leaves the heights to the chart, as - before. `onPaneHeightsChanged` reports drags and controller changes. -- **`KChartController.maximizePane`, `restorePanes`, `toggleMaximizePane`, + before. While a list is given the host owns the heights: a drag or a controller + call is reported through `onPaneHeightsChanged` and the pane moves when the + list is passed back. +- **`KChartController.maximizePane`, `maximizeVolume`, `restorePanes`, + `toggleMaximizePane`, `setPaneHeight`, `resetPaneHeights`, `paneHeights` and `maximizedPane`** drive the same from code, for a "maximize this indicator" button. A maximized pane follows the chart's size and its own indicator through a reorder. - Dragged pane heights now move with their pane when `onReorderPane` reorders the indicators, instead of staying at their position. -- `KChartHost` has five new members for the above; only an app that implements +- `KChartHost` has seven new members for the above; only an app that implements it itself (a test double) needs to add them. ## 3.0.0 - 2026-09-19 diff --git a/doc/panes.md b/doc/panes.md index f07d38d..0bb9ebc 100644 --- a/doc/panes.md +++ b/doc/panes.md @@ -48,6 +48,7 @@ the usual reason. chart.maximizePane(1); // pane 1 fills the chart, candles shrink to a strip chart.restorePanes(); // …and back chart.toggleMaximizePane(1); // one button for both +chart.maximizeVolume(); // the volume pane instead chart.setPaneHeight(0, 220); // any single pane, any positive height chart.resetPaneHeights(); // every pane back to the standard height chart.paneHeights; // what is drawn now @@ -57,7 +58,7 @@ chart.paneHeights; // what is drawn now ![The RSI pane maximized](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-maximized-rsi.jpg) ![The OBV pane maximized](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-maximized-obv.jpg) -A maximized pane follows the chart's size and moves with its indicator when you +Only one pane — or the volume pane — is maximized at a time. A maximized pane follows the chart's size and moves with its indicator when you reorder; dragging any pane lets go of it. It needs the chart to size its own candle area, so it has no effect when `mBaseHeight` is set. @@ -79,10 +80,12 @@ KChartWidget( too big for the box is cut back so the candles keep a strip above it. - A pane missing from a shorter list, or given a height that is not a positive number, gets the standard height. -- The list is a starting point, not a lock: with `resizablePanes` on the user - can still drag, and `onPaneHeightsChanged` reports it (and controller - changes) so you can keep your copy in step. It is not called for changes you - made through `paneHeights`. +- While a list is given you own the heights, like a controlled text field. A + drag (with `resizablePanes` on) or `setPaneHeight` does not move a pane by + itself: it is reported through `onPaneHeightsChanged`, and the pane moves when + you pass the new list back. Pass `null` to give the heights back to the chart. + Maximizing through the controller still works over a list, and lets go of + whenever a height changes. - Panes in `paneHeights` are matched by position. When `onReorderPane` moves one, move its height with it. diff --git a/example/lib/src/controls.dart b/example/lib/src/controls.dart index a41a5e4..81c353f 100644 --- a/example/lib/src/controls.dart +++ b/example/lib/src/controls.dart @@ -979,6 +979,14 @@ class _PaneMaximizer extends StatelessWidget { return Wrap( spacing: 8, children: [ + if (!state.volHidden) + FilterChip( + label: const Text('Maximize volume'), + selected: state.chart.isVolumeMaximized, + onSelected: (on) => on + ? state.chart.maximizeVolume() + : state.chart.restorePanes(), + ), for (var i = 0; i < panes.length; i++) FilterChip( label: Text('Maximize ${panes[i].label}'), diff --git a/lib/src/chart_controller.dart b/lib/src/chart_controller.dart index d0b5ee0..c5ad800 100644 --- a/lib/src/chart_controller.dart +++ b/lib/src/chart_controller.dart @@ -79,6 +79,12 @@ abstract interface class KChartHost { /// Stretches pane [index] over the chart, or lets go of the stretch for null. bool maximizeChartPane(int? index); + + /// Whether the volume pane is stretched over the chart. + bool get chartVolumeMaximized; + + /// Stretches the volume pane over the chart; false when it is hidden. + bool maximizeChartVolume(); } /// Drives a chart from outside it: where it is scrolled, how far it is zoomed, @@ -266,6 +272,10 @@ class KChartController extends ChangeNotifier { /// Sizes the indicator pane at [index] to [height]. /// + /// With `KChartWidget.paneHeights` given the host owns the heights, so this + /// is reported through `onPaneHeightsChanged` for the host to pass back + /// rather than moving the pane itself. + /// /// Unlike a drag, this is not held between `ChartStyle.minPaneHeight` and /// `ChartStyle.maxPaneHeight`: any positive height is used as given, so a host /// can grow a pane past what the user could drag it to. Panes are told apart @@ -293,7 +303,16 @@ class KChartController extends ChangeNotifier { /// given a fixed `mBaseHeight`. bool maximizePane(int index) => _host?.maximizeChartPane(index) ?? false; - /// Lets go of [maximizePane], putting the panes back as they were. + /// Whether the volume pane is stretched over the chart. + bool get isVolumeMaximized => _host?.chartVolumeMaximized ?? false; + + /// Makes the volume pane take all the height it can, in the way + /// [maximizePane] does for an indicator pane. Reports false when the volume + /// pane is hidden. Only one pane, or the volume, is maximized at a time. + bool maximizeVolume() => _host?.maximizeChartVolume() ?? false; + + /// Lets go of [maximizePane] or [maximizeVolume], putting the panes back as + /// they were. void restorePanes() => _host?.maximizeChartPane(null); /// Maximizes the pane at [index], or restores the panes if it already is. diff --git a/lib/src/k_chart_widget.dart b/lib/src/k_chart_widget.dart index 581fa17..59b45dc 100644 --- a/lib/src/k_chart_widget.dart +++ b/lib/src/k_chart_widget.dart @@ -709,11 +709,12 @@ class KChartWidget extends StatefulWidget { /// chart sizes its own candle area, a height too big for the box is cut back /// so the candles keep a strip above it. /// - /// The list is a starting point rather than a lock: with [resizablePanes] on - /// the user can still drag from there, and [onPaneHeightsChanged] reports it - /// so the host can keep its own copy in step. The chart takes the list up - /// again whenever it changes. Panes are told apart by position, so when - /// [onReorderPane] moves one, move its height with it: + /// While a list is given the host owns the heights, like a controlled text + /// field: a drag (with [resizablePanes] on), or a call to + /// `KChartController.setPaneHeight`, does not move a pane by itself. It is + /// reported through [onPaneHeightsChanged] instead, and the pane moves when + /// the host passes the new list back. Panes are told apart by position, so + /// when [onReorderPane] moves one, move its height with it: /// /// ```dart /// onReorderPane: (from, to) => setState(() { @@ -732,11 +733,12 @@ class KChartWidget extends StatefulWidget { /// anything not a positive number is ignored. final double? volumeHeight; - /// Called with every pane's height after the user drags one, or after the - /// heights are changed through `KChartController`. + /// Called with every pane's height when the user drags one, or the heights + /// are changed through `KChartController`. /// - /// Not called for a change made through [paneHeights], which the host - /// already knows about. + /// With [paneHeights] given this is how the host hears what was asked for, to + /// pass back; without it the chart has already moved the pane. Not called for + /// a change made through [paneHeights] itself. final ValueChanged>? onPaneHeightsChanged; /// Shifted onto every candle's time before it is shown. @@ -1164,6 +1166,9 @@ class _KChartWidgetState extends State /// The indicator whose pane is stretched over the chart, if any. Indicator? _maximizedOwner; + /// Whether the volume pane is stretched over the chart. + bool _volumeMaximized = false; + /// Set when `KChartWidget.paneHeights` changed and is yet to be taken up, /// which waits for the build because the panes it applies to may change too. bool _hostPaneHeightsStale = false; @@ -1862,6 +1867,21 @@ class _KChartWidgetState extends State /// The volume pane's height when the host gave a usable one. double? get _volumeHeightOverride { + if (_volumeMaximized && + !widget.volHidden && + widget.mBaseHeight == null && + mHeight.isFinite && + mHeight > 0) { + // Everything else at its smallest, the volume pane gets what is left. + final small = widget.chartStyle.minPaneHeight; + return math.max( + small, + mHeight - + BaseChartPainter.minMainHeight - + BaseDimension.legendRowHeight * _legendRowCount - + small * _paneOwners.length, + ); + } final height = widget.volumeHeight; return height != null && height.isFinite && height > 0 ? height : null; } @@ -1882,7 +1902,7 @@ class _KChartWidgetState extends State BaseDimension.secondaryPaneHeight, ); _hostSizedPanes = false; - _maximizedOwner = null; + _clearMaximized(); } else if (!listEquals(_paneOwners, owners)) { // The same number of panes in another order: each height goes with its // pane, and a pane never seen before starts at the standard height. @@ -1896,34 +1916,46 @@ class _KChartWidgetState extends State } _paneOwners = owners; - if (restructured || _hostPaneHeightsStale) { - final given = widget.paneHeights; - if (given != null) { - _paneHeights = [ - for (var i = 0; i < owners.length; i++) - i < given.length && _usableHeight(given[i]) - ? given[i] - : BaseDimension.secondaryPaneHeight, - ]; - _hostSizedPanes = true; - _maximizedOwner = null; - } else if (_hostPaneHeightsStale) { - // The host let go: back to the chart's own layout. - _paneHeights = List.filled( - owners.length, - BaseDimension.secondaryPaneHeight, - ); - _hostSizedPanes = false; - _maximizedOwner = null; - } - _hostPaneHeightsStale = false; + final given = widget.paneHeights; + if (given != null) { + // The host owns the heights: whatever it passes is what is drawn. + _paneHeights = [ + for (var i = 0; i < owners.length; i++) + i < given.length && _usableHeight(given[i]) + ? given[i] + : BaseDimension.secondaryPaneHeight, + ]; + _hostSizedPanes = true; + if (restructured || _hostPaneHeightsStale) _clearMaximized(); + } else if (_hostPaneHeightsStale) { + // The host let go: back to the chart's own layout. + _paneHeights = List.filled( + owners.length, + BaseDimension.secondaryPaneHeight, + ); + _hostSizedPanes = false; + _clearMaximized(); } + _hostPaneHeightsStale = false; + if (_volumeMaximized && !widget.volHidden) { + final small = widget.chartStyle.minPaneHeight; + return List.filled(owners.length, small); + } final maximized = _maximizedPaneIndex; if (maximized != null) return _heightsWithMaximized(maximized); return _hostSizedPanes ? _cutBackToFit(_paneHeights) : _paneHeights; } + /// Whether the host owns the pane heights, by having passed a list. + bool get _heightsLocked => widget.paneHeights != null; + + /// Lets go of any pane, or the volume pane, stretched over the chart. + void _clearMaximized() { + _maximizedOwner = null; + _volumeMaximized = false; + } + /// Where the maximized pane sits now, or null when none is. int? get _maximizedPaneIndex { final owner = _maximizedOwner; @@ -2766,8 +2798,9 @@ class _KChartWidgetState extends State widget.chartStyle.minPaneHeight, math.max(widget.chartStyle.maxPaneHeight, heights[index]), ); - _paneHeights = heights; - _maximizedOwner = null; + // A host that owns the heights hears about the drag and passes it back. + if (!_heightsLocked) _paneHeights = heights; + _clearMaximized(); notifyChanged(); widget.controller?.hostChanged(); widget.onPaneHeightsChanged?.call(List.unmodifiable(heights)); @@ -4313,9 +4346,12 @@ class _KChartWidgetState extends State } heights[index] = height; setState(() { - _paneHeights = heights; - _hostSizedPanes = true; - _maximizedOwner = null; + // A host that owns the heights hears about it and passes it back. + if (!_heightsLocked) { + _paneHeights = heights; + _hostSizedPanes = true; + } + _clearMaximized(); }); widget.controller?.hostChanged(); widget.onPaneHeightsChanged?.call(List.unmodifiable(heights)); @@ -4325,42 +4361,65 @@ class _KChartWidgetState extends State @override void resetChartPaneHeights() { _effectivePaneHeights; + final standard = List.filled( + _paneOwners.length, + BaseDimension.secondaryPaneHeight, + ); final untouched = _maximizedOwner == null && - !_hostSizedPanes && - _paneHeights.every((h) => h == BaseDimension.secondaryPaneHeight); + !_volumeMaximized && + listEquals(_paneHeights, standard) && + !_heightsLocked; if (untouched) return; setState(() { - _paneHeights = List.filled( - _paneOwners.length, - BaseDimension.secondaryPaneHeight, - ); - _hostSizedPanes = false; - _maximizedOwner = null; + if (!_heightsLocked) { + _paneHeights = standard; + _hostSizedPanes = false; + } + _clearMaximized(); }); widget.controller?.hostChanged(); - widget.onPaneHeightsChanged?.call(List.unmodifiable(_paneHeights)); + widget.onPaneHeightsChanged?.call(List.unmodifiable(standard)); } @override int? get chartMaximizedPane { _effectivePaneHeights; - return _maximizedPaneIndex; + return _volumeMaximized ? null : _maximizedPaneIndex; } @override bool maximizeChartPane(int? index) { _effectivePaneHeights; if (index == null) { - if (_maximizedOwner == null) return true; - setState(() => _maximizedOwner = null); + if (_maximizedOwner == null && !_volumeMaximized) return true; + setState(_clearMaximized); widget.controller?.hostChanged(); return true; } if (index < 0 || index >= _paneOwners.length) return false; final owner = _paneOwners[index]; - if (_maximizedOwner != owner) { - setState(() => _maximizedOwner = owner); + if (_maximizedOwner != owner || _volumeMaximized) { + setState(() { + _volumeMaximized = false; + _maximizedOwner = owner; + }); + widget.controller?.hostChanged(); + } + return true; + } + + @override + bool get chartVolumeMaximized => _volumeMaximized && !widget.volHidden; + + @override + bool maximizeChartVolume() { + if (widget.volHidden) return false; + if (!_volumeMaximized) { + setState(() { + _maximizedOwner = null; + _volumeMaximized = true; + }); widget.controller?.hostChanged(); } return true; diff --git a/test/chart_link_test.dart b/test/chart_link_test.dart index a5fd27c..5c1dbdb 100644 --- a/test/chart_link_test.dart +++ b/test/chart_link_test.dart @@ -135,6 +135,10 @@ class _FakeHost implements KChartHost { int? get chartMaximizedPane => null; @override bool maximizeChartPane(int? index) => false; + @override + bool get chartVolumeMaximized => false; + @override + bool maximizeChartVolume() => false; } ({KChartController controller, _FakeHost host}) chart({ diff --git a/test/chart_overview_test.dart b/test/chart_overview_test.dart index d210150..9f2ba60 100644 --- a/test/chart_overview_test.dart +++ b/test/chart_overview_test.dart @@ -74,6 +74,10 @@ class _FakeHost implements KChartHost { int? get chartMaximizedPane => null; @override bool maximizeChartPane(int? index) => false; + @override + bool get chartVolumeMaximized => false; + @override + bool maximizeChartVolume() => false; } List series(int count) => [ diff --git a/test/pane_height_control_test.dart b/test/pane_height_control_test.dart index b6d25dc..a4c5b70 100644 --- a/test/pane_height_control_test.dart +++ b/test/pane_height_control_test.dart @@ -308,4 +308,142 @@ void main() { expect(reported, isNotEmpty); expect(reported.last[0], greaterThan(400)); }); + + testWidgets('a host that passes paneHeights owns them: a drag only reports', ( + tester, + ) async { + _tallView(tester); + final reported = >[]; + await tester.pumpWidget( + _chart( + resizable: true, + paneHeights: [100, 100], + onChanged: reported.add, + boxHeight: _tallBox, + ), + ); + final before = _paneHeightsOf(tester); + final edge = _painterOf(tester).mSecondaryRectList.first.mRect.bottom; + final box = tester.getTopLeft(find.byType(KChartWidget)); + + final gesture = await tester.startGesture(box + Offset(200, edge)); + await gesture.moveBy(const Offset(0, 20)); + await gesture.moveBy(const Offset(0, 20)); + await gesture.up(); + await tester.pump(); + + expect(reported, isNotEmpty); + expect(reported.last[0], greaterThan(100)); + expect(_paneHeightsOf(tester), before); + + // Passing the reported heights back is what moves the pane. + await tester.pumpWidget( + _chart( + resizable: true, + paneHeights: reported.last, + onChanged: reported.add, + boxHeight: _tallBox, + ), + ); + expect(_paneHeightsOf(tester)[0], greaterThan(before[0])); + }); + + testWidgets('setPaneHeight on a host-owned chart reports without moving', ( + tester, + ) async { + final controller = KChartController(); + final reported = >[]; + await tester.pumpWidget( + _chart( + controller: controller, + paneHeights: [100, 100], + onChanged: reported.add, + ), + ); + final before = _paneHeightsOf(tester); + + expect(controller.setPaneHeight(0, 250), isTrue); + await tester.pump(); + expect(reported.last, [250, 100]); + expect(_paneHeightsOf(tester), before); + }); + + testWidgets('maximizePane still works over host-owned heights', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget( + _chart(controller: controller, paneHeights: [100, 100]), + ); + final before = _paneHeightsOf(tester); + + controller.maximizePane(0); + await tester.pump(); + expect(_paneHeightsOf(tester)[0], greaterThan(before[0] * 2)); + + controller.restorePanes(); + await tester.pump(); + expect(_paneHeightsOf(tester), before); + }); + + testWidgets('maximizeVolume fills the box and restorePanes undoes it', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + final standard = _painterOf(tester).mVolRect!.height; + final panes = _paneHeightsOf(tester); + + expect(controller.maximizeVolume(), isTrue); + await tester.pump(); + expect(controller.isVolumeMaximized, isTrue); + expect(controller.maximizedPane, isNull); + + final painter = _painterOf(tester); + expect(painter.mVolRect!.height, greaterThan(standard * 3)); + expect(painter.mMainRect.height, greaterThanOrEqualTo(60)); + expect( + painter.mSecondaryRectList.last.mRect.bottom, + lessThanOrEqualTo(_boxHeight), + ); + + // Maximizing a pane takes the volume pane back down. + controller.maximizePane(0); + await tester.pump(); + expect(controller.isVolumeMaximized, isFalse); + expect(controller.maximizedPane, 0); + + controller.maximizeVolume(); + controller.restorePanes(); + await tester.pump(); + expect(controller.isVolumeMaximized, isFalse); + expect(_painterOf(tester).mVolRect!.height, standard); + expect(_paneHeightsOf(tester), panes); + }); + + testWidgets('the volume pane cannot be maximized while it is hidden', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + width: 500, + height: _boxHeight, + child: KChartWidget( + _candles(), + ChartColors(), + isTrendLine: false, + timeFrame: const Duration(minutes: 1), + volHidden: true, + controller: controller, + ), + ), + ), + ), + ); + expect(controller.maximizeVolume(), isFalse); + expect(controller.isVolumeMaximized, isFalse); + }); } From 68e665c3a8e348072ec96d42a0f237b08b0e76ff Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 03:12:53 +0330 Subject: [PATCH 08/12] feat: size the chart's parts by proportion with paneRatios paneRatios splits the chart height between the candles, the volume pane and each indicator pane, so [3, 1, 2] is half, a sixth and a third, and the split holds as the chart is resized. The host owns the proportions: a drag reports the moved list through onPaneRatiosChanged. The example app gets a switch for it. --- CHANGELOG.md | 7 ++ doc/panes.md | 36 ++++++ example/lib/src/chart_page.dart | 3 + example/lib/src/controls.dart | 6 + example/lib/src/demo_state.dart | 15 +++ lib/src/k_chart_widget.dart | 108 ++++++++++++++++ screenshots/panes-ratios.jpg | Bin 0 -> 54632 bytes test/pane_height_control_test.dart | 194 +++++++++++++++++++++++++++++ 8 files changed, 369 insertions(+) create mode 100644 screenshots/panes-ratios.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index a5b8402..6ad4cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,13 @@ `setPaneHeight`, `resetPaneHeights`, `paneHeights` and `maximizedPane`** drive the same from code, for a "maximize this indicator" button. A maximized pane follows the chart's size and its own indicator through a reorder. +- **`KChartWidget.paneRatios`** splits the chart's height by proportion — the + candles, the volume pane, then each indicator pane — so `[3, 1, 2]` is half + the height for the candles, a sixth for the volume and a third for one pane, + and the split holds as the chart is resized. It wins over `paneHeights` and + `volumeHeight`, needs a bounded height and no `mBaseHeight`, and is owned by + the host: a drag reports the moved proportions through `onPaneRatiosChanged`. + The example app has a switch for it. - Dragged pane heights now move with their pane when `onReorderPane` reorders the indicators, instead of staying at their position. - `KChartHost` has seven new members for the above; only an app that implements diff --git a/doc/panes.md b/doc/panes.md index 0bb9ebc..99be1f4 100644 --- a/doc/panes.md +++ b/doc/panes.md @@ -92,6 +92,42 @@ KChartWidget( `ChartStyle.paneResizeTolerance` and `paneGrabHeight` set the size of the resize and reorder hit areas. +## Sizing by proportion + +Pixel heights do not survive a resize. `paneRatios` divides the chart's height +by proportion instead, top to bottom: the candles, the volume pane, then each +indicator pane. + +```dart +KChartWidget( + candles, + ChartColors(), + indicators: [RsiIndicator()], + paneRatios: [3, 1, 2], // 6 units: candles 3, volume 1, RSI 2 +); +``` + +![Candles 3, volume 1, MACD 2 and RSI 2](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-ratios.jpg) + +- The height left after the legend rows above the candles is split by those + units, and the split holds as the box is resized. +- With `volHidden` the volume's number is left out, so the list is one shorter. +- A part missing from a shorter list, or given a number that is not above zero, + counts as 1. +- It needs the box to bound the chart's height and `mBaseHeight` to be left + off; otherwise it is ignored. It wins over `paneHeights` and `volumeHeight`, + and a maximized pane wins over it. +- You own the proportions, as with `paneHeights`. With `resizablePanes` on, a + drag moves room between the two parts either side of the edge — the last pane + takes from the part above — and `onPaneRatiosChanged` reports the new list, + keeping its total. Pass it back for the drag to take effect: + +```dart +paneRatios: ratios, +resizablePanes: true, +onPaneRatiosChanged: (next) => setState(() => ratios = next), +``` + --- [← All docs](README.md) · [Package README](../README.md) diff --git a/example/lib/src/chart_page.dart b/example/lib/src/chart_page.dart index 9c1c776..40dfb49 100644 --- a/example/lib/src/chart_page.dart +++ b/example/lib/src/chart_page.dart @@ -128,6 +128,9 @@ class _Chart extends StatelessWidget { crosshairOnHover: state.crosshairOnHover, timeZoneOffset: state.timeZoneOffset, resizablePanes: state.resizablePanes, + paneRatios: state.paneRatios, + onPaneRatiosChanged: (ratios) => + state.update(() => state.paneRatios = ratios), reorderablePanes: state.reorderablePanes, onReorderPane: state.reorderPane, selectAfterDrawing: !state.keepToolArmed, diff --git a/example/lib/src/controls.dart b/example/lib/src/controls.dart index 81c353f..a7b7106 100644 --- a/example/lib/src/controls.dart +++ b/example/lib/src/controls.dart @@ -412,6 +412,12 @@ class Controls extends StatelessWidget { value: state.reorderablePanes, onChanged: (v) => state.update(() => state.reorderablePanes = v), ), + _Toggle( + label: 'Fixed proportions', + subtitle: 'paneRatios: candles 3, volume 1, each pane 2', + value: state.paneRatios != null, + onChanged: state.setProportionalPanes, + ), _PaneMaximizer(state: state), ], ), diff --git a/example/lib/src/demo_state.dart b/example/lib/src/demo_state.dart index 0e81376..f670b41 100644 --- a/example/lib/src/demo_state.dart +++ b/example/lib/src/demo_state.dart @@ -153,6 +153,21 @@ class DemoState extends ChangeNotifier { /// Whether an indicator pane can be dragged taller or shorter. bool resizablePanes = true; + /// Proportions of the chart's height — candles, volume, then each pane — or + /// null to leave the heights to the chart. + List? paneRatios; + + /// Turns fixed proportions on, as 3 for the candles, 1 for the volume and 2 + /// for each indicator pane, or off. + void setProportionalPanes(bool on) => update(() { + final panes = indicators + .where((i) => i.placement != IndicatorPlacement.overlay) + .length; + paneRatios = on + ? [3, if (!volHidden) 1, for (var i = 0; i < panes; i++) 2] + : null; + }); + /// Whether an indicator pane can be dragged up or down the stack. bool reorderablePanes = true; diff --git a/lib/src/k_chart_widget.dart b/lib/src/k_chart_widget.dart index 59b45dc..485a6b7 100644 --- a/lib/src/k_chart_widget.dart +++ b/lib/src/k_chart_widget.dart @@ -288,6 +288,8 @@ class KChartWidget extends StatefulWidget { this.paneHeights, this.volumeHeight, this.onPaneHeightsChanged, + this.paneRatios, + this.onPaneRatiosChanged, this.onAddTrendLine, this.onAddHorizontalLine, this.onAddVerticalLine, @@ -741,6 +743,40 @@ class KChartWidget extends StatefulWidget { /// a change made through [paneHeights] itself. final ValueChanged>? onPaneHeightsChanged; + /// Splits the chart's height between its parts by proportion, top to bottom: + /// the candles, then the volume pane (left out of the list when [volHidden]), + /// then each indicator pane. + /// + /// `[3, 1, 2]` over candles, volume and one indicator pane makes six units: + /// half the height for the candles, a sixth for the volume and a third for + /// the indicator. The proportions hold as the chart is resized, which is + /// what pixel heights cannot do. The room is what is left once the legend + /// rows above the candles have taken theirs. + /// + /// A part missing from a shorter list, or given a number that is not above + /// zero, counts as 1. Null — the default — leaves the heights to the chart, + /// or to [paneHeights] and [volumeHeight]. + /// + /// It needs the box to bound the chart's height and `mBaseHeight` to be left + /// off; otherwise there is nothing to divide and it is ignored. Where it + /// applies it wins over [paneHeights] and [volumeHeight], and a maximized + /// pane (see `KChartController.maximizePane`) wins over it. + /// + /// Like [paneHeights], the host owns the proportions while they are given: a + /// drag with [resizablePanes] on moves the edge between two neighbours, and + /// is reported through [onPaneRatiosChanged] for the host to pass back. + final List? paneRatios; + + /// Called with the new proportions when the user drags a pane's edge, in the + /// order [paneRatios] uses and keeping their total, so a drag only moves + /// room between the two parts either side of the edge. + /// + /// ```dart + /// paneRatios: ratios, + /// onPaneRatiosChanged: (next) => setState(() => ratios = next), + /// ``` + final ValueChanged>? onPaneRatiosChanged; + /// Shifted onto every candle's time before it is shown. /// /// Candle times are read as they come — usually UTC — and this is what puts @@ -1865,6 +1901,27 @@ class _KChartWidgetState extends State ChartType get _chartType => widget.chartType ?? (widget.isLine ? ChartType.area : ChartType.candles); + /// The parts of the chart sized by [KChartWidget.paneRatios], or null where + /// they do not apply: no ratios given, a fixed candle height, or a box that + /// does not bound the height. + ({List ratios, double unit})? get _ratioSplit { + final given = widget.paneRatios; + if (given == null || + widget.mBaseHeight != null || + !mHeight.isFinite || + mHeight <= 0) { + return null; + } + final parts = 1 + (widget.volHidden ? 0 : 1) + _resolved.panes.length; + final ratios = [ + for (var i = 0; i < parts; i++) + i < given.length && _usableHeight(given[i]) ? given[i] : 1.0, + ]; + final room = mHeight - BaseDimension.legendRowHeight * _legendRowCount; + if (room <= 0) return null; + return (ratios: ratios, unit: room / ratios.fold(0.0, (a, b) => a + b)); + } + /// The volume pane's height when the host gave a usable one. double? get _volumeHeightOverride { if (_volumeMaximized && @@ -1882,6 +1939,10 @@ class _KChartWidgetState extends State small * _paneOwners.length, ); } + final split = _ratioSplit; + if (split != null && !widget.volHidden) { + return split.ratios[1] * split.unit; + } final height = widget.volumeHeight; return height != null && height.isFinite && height > 0 ? height : null; } @@ -1944,6 +2005,14 @@ class _KChartWidgetState extends State } final maximized = _maximizedPaneIndex; if (maximized != null) return _heightsWithMaximized(maximized); + final split = _ratioSplit; + if (split != null) { + final first = widget.volHidden ? 1 : 2; + return [ + for (var i = 0; i < owners.length; i++) + split.ratios[first + i] * split.unit, + ]; + } return _hostSizedPanes ? _cutBackToFit(_paneHeights) : _paneHeights; } @@ -2792,6 +2861,12 @@ class _KChartWidgetState extends State final heights = [..._effectivePaneHeights]; if (index < 0 || index >= heights.length) return; + final split = _ratioSplit; + if (split != null && _maximizedPaneIndex == null && !_volumeMaximized) { + _resizeByRatio(split, index, delta); + return; + } + // A pane the host made taller than the usual ceiling keeps its height // under the finger rather than snapping down to it. heights[index] = (heights[index] + delta).clamp( @@ -2806,6 +2881,39 @@ class _KChartWidgetState extends State widget.onPaneHeightsChanged?.call(List.unmodifiable(heights)); } + /// Moves room between pane [index] and its neighbour by [delta] pixels. + /// + /// The edge dragged is the pane's lower one, so the room comes from the pane + /// below. The last pane has nothing below it and takes from the part above. + /// The total is kept, and no part is squeezed under its smallest. The host + /// owns the proportions, so this only reports where they would go. + void _resizeByRatio( + ({List ratios, double unit}) split, + int index, + double delta, + ) { + final ratios = [...split.ratios]; + final slot = (widget.volHidden ? 1 : 2) + index; + final other = slot + 1 < ratios.length ? slot + 1 : slot - 1; + + double floorOf(int part) => + (part == 0 + ? BaseChartPainter.minMainHeight + : widget.chartStyle.minPaneHeight) / + split.unit; + + // Pixels moved, in proportion units, held so neither side drops below its + // floor: what the pane can gain is what the other can spare, and back. + final lowest = floorOf(slot) - ratios[slot]; + final highest = ratios[other] - floorOf(other); + if (lowest > highest) return; + final move = (delta / split.unit).clamp(lowest, highest); + ratios[slot] += move; + ratios[other] -= move; + notifyChanged(); + widget.onPaneRatiosChanged?.call(List.unmodifiable(ratios)); + } + /// Works out where the pane being dragged was dropped, and reports the move. void _finishPaneReorder() { final from = _reorderingPane; diff --git a/screenshots/panes-ratios.jpg b/screenshots/panes-ratios.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7538a7f096562c1f2c925b5291cafb3d50eff162 GIT binary patch literal 54632 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 literal 0 HcmV?d00001 diff --git a/test/pane_height_control_test.dart b/test/pane_height_control_test.dart index a4c5b70..1389bf5 100644 --- a/test/pane_height_control_test.dart +++ b/test/pane_height_control_test.dart @@ -22,6 +22,9 @@ Widget _chart({ ValueChanged>? onChanged, bool resizable = false, double boxHeight = _boxHeight, + List? ratios, + ValueChanged>? onRatios, + bool volHidden = false, }) => MaterialApp( home: Scaffold( body: SizedBox( @@ -39,6 +42,9 @@ Widget _chart({ controller: controller, onPaneHeightsChanged: onChanged, resizablePanes: resizable, + paneRatios: ratios, + onPaneRatiosChanged: onRatios, + volHidden: volHidden, ), ), ), @@ -446,4 +452,192 @@ void main() { expect(controller.maximizeVolume(), isFalse); expect(controller.isVolumeMaximized, isFalse); }); + + group('paneRatios', () { + testWidgets('divides the height between candles, volume and panes', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + ratios: [3, 1, 2, 4], + ), + ); + final heights = controller.paneHeights; + // The two panes hold their 2 : 4 proportion exactly. + expect(heights[1] / heights[0], closeTo(2, 0.001)); + + // The parts fill the box (the test window is shorter than the box), + // less the strip the date axis takes under the last pane. + final painter = _painterOf(tester); + final bottom = painter.mSecondaryRectList.last.mRect.bottom; + expect( + bottom, + closeTo( + tester.view.physicalSize.height / tester.view.devicePixelRatio, + 30, + ), + ); + + // 3 : 1 : 2 : 4 — the first pane is 2/10 of what is left of the box. + final main = painter.mMainRect.height; + final volume = painter.mVolRect!.height; + expect(main, greaterThan(volume * 2)); + expect(heights[0], greaterThan(volume)); + }); + + testWidgets('keeps the proportions when the box is resized', ( + tester, + ) async { + _tallView(tester); + final controller = KChartController(); + Widget at(double height) => _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + ratios: [3, 1, 2, 4], + boxHeight: height, + ); + + await tester.pumpWidget(at(600)); + final small = controller.paneHeights; + await tester.pumpWidget(at(1000)); + final large = controller.paneHeights; + + expect(large[0], greaterThan(small[0])); + expect(large[1] / large[0], closeTo(2, 0.001)); + expect(large[0] / small[0], closeTo(large[1] / small[1], 0.001)); + }); + + testWidgets('leaves the volume out of the list when it is hidden', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget( + _chart( + controller: controller, + volHidden: true, + indicators: [RsiIndicator(), MacdIndicator()], + ratios: [2, 1, 3], + ), + ); + final heights = controller.paneHeights; + expect(heights[1] / heights[0], closeTo(3, 0.001)); + }); + + testWidgets('a short list or a bad number counts as 1', (tester) async { + final controller = KChartController(); + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + ratios: [3, 1, double.nan], + ), + ); + final heights = controller.paneHeights; + expect(heights[0], closeTo(heights[1], 0.001)); + }); + + testWidgets('wins over paneHeights and volumeHeight, and null lets go', ( + tester, + ) async { + final controller = KChartController(); + Widget chart({List? ratios}) => _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + paneHeights: [300, 40], + volumeHeight: 200, + ratios: ratios, + ); + + await tester.pumpWidget(chart(ratios: [3, 1, 2, 2])); + final heights = controller.paneHeights; + expect(heights[0], closeTo(heights[1], 0.001)); + + await tester.pumpWidget(chart()); + expect(controller.paneHeights, [300, 40]); + }); + + testWidgets('maximizePane wins over it, and restores to it', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + ratios: [3, 1, 2, 2], + ), + ); + final before = controller.paneHeights; + + controller.maximizePane(0); + await tester.pump(); + expect(controller.paneHeights[0], greaterThan(before[0] * 2)); + + controller.restorePanes(); + await tester.pump(); + expect(controller.paneHeights, before); + }); + + testWidgets('a drag reports moved proportions with the total kept', ( + tester, + ) async { + final reported = >[]; + await tester.pumpWidget( + _chart( + resizable: true, + indicators: [RsiIndicator(), MacdIndicator()], + ratios: [3, 1, 2, 4], + onRatios: reported.add, + ), + ); + final before = _paneHeightsOf(tester); + final edge = _painterOf(tester).mSecondaryRectList.first.mRect.bottom; + final box = tester.getTopLeft(find.byType(KChartWidget)); + + final gesture = await tester.startGesture(box + Offset(200, edge)); + await gesture.moveBy(const Offset(0, 20)); + await gesture.moveBy(const Offset(0, 20)); + await gesture.up(); + await tester.pump(); + + expect(reported, isNotEmpty); + final last = reported.last; + expect(last.fold(0, (a, b) => a + b), closeTo(10, 0.0001)); + // The pane grew and the one below it gave the room; the rest held. + expect(last[2], greaterThan(2)); + expect(last[3], lessThan(4)); + expect(last[0], 3); + expect(last[1], 1); + // The host owns the proportions, so nothing moved until it passes them back. + expect(_paneHeightsOf(tester), before); + }); + + testWidgets('is ignored when the candle height is fixed', (tester) async { + final controller = KChartController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + width: 500, + height: _boxHeight, + child: KChartWidget( + _candles(), + ChartColors(), + isTrendLine: false, + timeFrame: const Duration(minutes: 1), + mBaseHeight: 300, + indicators: [RsiIndicator()], + paneRatios: const [1, 1, 5], + controller: controller, + ), + ), + ), + ), + ); + expect(controller.paneHeights, [100]); + }); + }); } From 085e669a9ee547928280b82fdb0efd36337e0826 Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 03:26:03 +0330 Subject: [PATCH 09/12] feat: add PaneSizeMode with a custom layout the user edits by dragging paneSizeMode picks heights (pixels, the default), ratios (paneRatios) or custom. In custom mode KChartController.editPanes shows a line between each two parts, the candles and volume included, to drag between the smallest and largest height each may take. The layout is kept as proportions and reported through onPaneRatiosChanged. paneRatios now only applies in the ratios and custom modes. The example app gets a sizing chooser and an Edit layout chip. --- CHANGELOG.md | 20 +- doc/panes.md | 69 ++++++- example/lib/src/chart_page.dart | 1 + example/lib/src/controls.dart | 20 +- example/lib/src/demo_state.dart | 16 +- lib/src/chart_controller.dart | 31 ++++ lib/src/export.dart | 1 + lib/src/k_chart_widget.dart | 263 ++++++++++++++++++++++++--- lib/src/pane_size_mode.dart | 32 ++++ screenshots/panes-custom-dragged.jpg | Bin 0 -> 53675 bytes screenshots/panes-custom-edit.jpg | Bin 0 -> 54840 bytes test/chart_link_test.dart | 4 + test/chart_overview_test.dart | 4 + test/pane_height_control_test.dart | 230 +++++++++++++++++++++++ 14 files changed, 636 insertions(+), 55 deletions(-) create mode 100644 lib/src/pane_size_mode.dart create mode 100644 screenshots/panes-custom-dragged.jpg create mode 100644 screenshots/panes-custom-edit.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ad4cc8..28d088a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,16 +27,20 @@ `setPaneHeight`, `resetPaneHeights`, `paneHeights` and `maximizedPane`** drive the same from code, for a "maximize this indicator" button. A maximized pane follows the chart's size and its own indicator through a reorder. -- **`KChartWidget.paneRatios`** splits the chart's height by proportion — the - candles, the volume pane, then each indicator pane — so `[3, 1, 2]` is half - the height for the candles, a sixth for the volume and a third for one pane, - and the split holds as the chart is resized. It wins over `paneHeights` and - `volumeHeight`, needs a bounded height and no `mBaseHeight`, and is owned by - the host: a drag reports the moved proportions through `onPaneRatiosChanged`. - The example app has a switch for it. +- **`KChartWidget.paneSizeMode`** chooses how the chart's parts are sized. + `PaneSizeMode.heights` is the default and the behaviour so far. `ratios` splits + the height by `paneRatios` — the candles, the volume pane, then each indicator + pane, so `[3, 1, 2]` is half, a sixth and a third — and holds the split as the + chart is resized. `custom` lets the user lay the chart out: `KChartController. + editPanes` shows a line between each two parts to drag between the smallest and + largest height each may take, the layout is kept as proportions, and + `onPaneRatiosChanged` reports it for saving. Both need a bounded height and no + `mBaseHeight`; the example app has a switch for them. +- `KChartController.editPanes`, `finishEditingPanes`, `toggleEditPanes` and + `isEditingPanes` drive the editing; `KChartHost` has two more members for it. - Dragged pane heights now move with their pane when `onReorderPane` reorders the indicators, instead of staying at their position. -- `KChartHost` has seven new members for the above; only an app that implements +- `KChartHost` has nine new members for the above; only an app that implements it itself (a test double) needs to add them. ## 3.0.0 - 2026-09-19 diff --git a/doc/panes.md b/doc/panes.md index 99be1f4..b5f062a 100644 --- a/doc/panes.md +++ b/doc/panes.md @@ -92,31 +92,41 @@ KChartWidget( `ChartStyle.paneResizeTolerance` and `paneGrabHeight` set the size of the resize and reorder hit areas. -## Sizing by proportion +## Sizing modes -Pixel heights do not survive a resize. `paneRatios` divides the chart's height -by proportion instead, top to bottom: the candles, the volume pane, then each -indicator pane. +`paneSizeMode` chooses how the heights of the candles, the volume pane and the +indicator panes are worked out: + +| Mode | Heights come from | Who changes them | +|---|---|---| +| `PaneSizeMode.heights` (default) | pixels: 100 per pane, 60 for volume, or `paneHeights` / `volumeHeight` | you, or the user dragging a pane's lower edge with `resizablePanes` | +| `PaneSizeMode.ratios` | proportions in `paneRatios` | you | +| `PaneSizeMode.custom` | the user's own layout | the user, dragging lines in edit mode | + +`ratios` and `custom` need the box to bound the chart's height and `mBaseHeight` +to be left off; otherwise the pixel heights are used. + +### Proportions ```dart KChartWidget( candles, ChartColors(), indicators: [RsiIndicator()], + paneSizeMode: PaneSizeMode.ratios, paneRatios: [3, 1, 2], // 6 units: candles 3, volume 1, RSI 2 ); ``` ![Candles 3, volume 1, MACD 2 and RSI 2](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-ratios.jpg) -- The height left after the legend rows above the candles is split by those - units, and the split holds as the box is resized. +- The list runs top to bottom: the candles, the volume pane, then each indicator + pane. The height left after the legend rows above the candles is split by + those units, and the split holds as the box is resized. - With `volHidden` the volume's number is left out, so the list is one shorter. - A part missing from a shorter list, or given a number that is not above zero, - counts as 1. -- It needs the box to bound the chart's height and `mBaseHeight` to be left - off; otherwise it is ignored. It wins over `paneHeights` and `volumeHeight`, - and a maximized pane wins over it. + counts as 1. Without `paneRatios` the mode has nothing to split by, and the + pixel heights are used. - You own the proportions, as with `paneHeights`. With `resizablePanes` on, a drag moves room between the two parts either side of the edge — the last pane takes from the part above — and `onPaneRatiosChanged` reports the new list, @@ -128,6 +138,45 @@ resizablePanes: true, onPaneRatiosChanged: (next) => setState(() => ratios = next), ``` +### Custom layout + +The user lays the chart out themselves. Put it in `custom` mode and turn editing +on from the controller; a line appears between each two parts, the candles and +volume included, to drag up or down: + +```dart +final chart = KChartController(); + +KChartWidget( + candles, + ChartColors(), + controller: chart, + indicators: [RsiIndicator()], + paneSizeMode: PaneSizeMode.custom, + onPaneRatiosChanged: (ratios) => saved = ratios, // optional +); + +chart.editPanes(); // show the lines +chart.finishEditingPanes(); // put them away, keeping the layout +chart.toggleEditPanes(); // one button for both +chart.resetPaneHeights(); // back to the standard layout +``` + +![Edit mode: a line between each two parts](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-custom-edit.jpg) +![The line between MACD and RSI dragged up](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-custom-dragged.jpg) + +- A line moves room between the two parts either side of it, and stops where + either reaches its limit: `ChartStyle.minPaneHeight` and `maxPaneHeight` for + the volume and the panes, a 60 px floor for the candles, which have no + ceiling because they are what is left. +- The chart owns the layout and keeps it as proportions, so it holds through a + resize. It starts from `paneRatios` when you give some, or from the standard + heights, and passing a different `paneRatios` later starts it over from that. +- `onPaneRatiosChanged` reports every change in `paneRatios` order, which is + what to save and pass back as `paneRatios` next time. +- The lines are not shown while a pane is maximized, and `resizablePanes` has no + effect in this mode; the lines replace it. + --- [← All docs](README.md) · [Package README](../README.md) diff --git a/example/lib/src/chart_page.dart b/example/lib/src/chart_page.dart index 40dfb49..bcd2899 100644 --- a/example/lib/src/chart_page.dart +++ b/example/lib/src/chart_page.dart @@ -128,6 +128,7 @@ class _Chart extends StatelessWidget { crosshairOnHover: state.crosshairOnHover, timeZoneOffset: state.timeZoneOffset, resizablePanes: state.resizablePanes, + paneSizeMode: state.paneMode, paneRatios: state.paneRatios, onPaneRatiosChanged: (ratios) => state.update(() => state.paneRatios = ratios), diff --git a/example/lib/src/controls.dart b/example/lib/src/controls.dart index a7b7106..e5dd2e7 100644 --- a/example/lib/src/controls.dart +++ b/example/lib/src/controls.dart @@ -412,11 +412,15 @@ class Controls extends StatelessWidget { value: state.reorderablePanes, onChanged: (v) => state.update(() => state.reorderablePanes = v), ), - _Toggle( - label: 'Fixed proportions', - subtitle: 'paneRatios: candles 3, volume 1, each pane 2', - value: state.paneRatios != null, - onChanged: state.setProportionalPanes, + _Choice( + label: 'Sizing', + value: state.paneMode, + options: const { + PaneSizeMode.heights: 'pixels', + PaneSizeMode.ratios: '3 : 1 : 2', + PaneSizeMode.custom: 'custom', + }, + onChanged: state.setPaneMode, ), _PaneMaximizer(state: state), ], @@ -985,6 +989,12 @@ class _PaneMaximizer extends StatelessWidget { return Wrap( spacing: 8, children: [ + if (state.paneMode == PaneSizeMode.custom) + FilterChip( + label: const Text('Edit layout'), + selected: state.chart.isEditingPanes, + onSelected: (_) => state.chart.toggleEditPanes(), + ), if (!state.volHidden) FilterChip( label: const Text('Maximize volume'), diff --git a/example/lib/src/demo_state.dart b/example/lib/src/demo_state.dart index f670b41..71849f7 100644 --- a/example/lib/src/demo_state.dart +++ b/example/lib/src/demo_state.dart @@ -153,17 +153,21 @@ class DemoState extends ChangeNotifier { /// Whether an indicator pane can be dragged taller or shorter. bool resizablePanes = true; - /// Proportions of the chart's height — candles, volume, then each pane — or - /// null to leave the heights to the chart. + /// How the panes are sized: in pixels, by proportion, or laid out by dragging. + PaneSizeMode paneMode = PaneSizeMode.heights; + + /// Proportions of the chart's height — candles, volume, then each pane — for + /// [PaneSizeMode.ratios], and where the custom layout starts from. List? paneRatios; - /// Turns fixed proportions on, as 3 for the candles, 1 for the volume and 2 - /// for each indicator pane, or off. - void setProportionalPanes(bool on) => update(() { + /// Switches how the panes are sized. Proportions start as 3 for the candles, + /// 1 for the volume and 2 for each indicator pane. + void setPaneMode(PaneSizeMode mode) => update(() { + paneMode = mode; final panes = indicators .where((i) => i.placement != IndicatorPlacement.overlay) .length; - paneRatios = on + paneRatios = mode == PaneSizeMode.ratios ? [3, if (!volHidden) 1, for (var i = 0; i < panes; i++) 2] : null; }); diff --git a/lib/src/chart_controller.dart b/lib/src/chart_controller.dart index c5ad800..46d7b87 100644 --- a/lib/src/chart_controller.dart +++ b/lib/src/chart_controller.dart @@ -85,6 +85,13 @@ abstract interface class KChartHost { /// Stretches the volume pane over the chart; false when it is hidden. bool maximizeChartVolume(); + + /// Whether the lines between the parts are showing, to be dragged. + bool get chartEditingPanes; + + /// Shows or hides those lines; false unless the chart is in + /// `PaneSizeMode.custom`. + bool setChartEditingPanes(bool editing); } /// Drives a chart from outside it: where it is scrolled, how far it is zoomed, @@ -315,6 +322,30 @@ class KChartController extends ChangeNotifier { /// they were. void restorePanes() => _host?.maximizeChartPane(null); + /// Whether the lines between the parts are showing, to be dragged. + bool get isEditingPanes => _host?.chartEditingPanes ?? false; + + /// Shows a line between each two parts of the chart — the candles, the volume + /// and each pane — for the user to drag up or down, between the smallest and + /// largest height each may take. + /// + /// Only has an effect in `PaneSizeMode.custom`, and reports whether it did. + /// The lines are not shown while a pane is maximized. + bool editPanes() => _host?.setChartEditingPanes(true) ?? false; + + /// Hides the lines, keeping the layout they were dragged to. + void finishEditingPanes() => _host?.setChartEditingPanes(false); + + /// Shows the lines, or hides them if they are showing: the shape an "edit + /// layout" button wants. + bool toggleEditPanes() { + if (isEditingPanes) { + finishEditingPanes(); + return true; + } + return editPanes(); + } + /// Maximizes the pane at [index], or restores the panes if it already is. /// /// The shape a "maximize this indicator" button wants. diff --git a/lib/src/export.dart b/lib/src/export.dart index b00ca1e..c190539 100644 --- a/lib/src/export.dart +++ b/lib/src/export.dart @@ -7,6 +7,7 @@ export './comparison.dart'; export './chart_style.dart'; export './chart_translations.dart'; export './chart_type.dart'; +export './pane_size_mode.dart'; export './chart_workspace.dart'; export './depth_chart.dart'; export './depth_ladder.dart'; diff --git a/lib/src/k_chart_widget.dart b/lib/src/k_chart_widget.dart index 485a6b7..38d275a 100644 --- a/lib/src/k_chart_widget.dart +++ b/lib/src/k_chart_widget.dart @@ -18,6 +18,7 @@ import 'trading.dart'; import 'chart_style.dart'; import 'chart_translations.dart'; import 'chart_type.dart'; +import 'pane_size_mode.dart'; import 'components/popup_info_view.dart'; import 'drawing/drawing_controller.dart'; import 'drawing/drawing_coordinates.dart'; @@ -288,6 +289,7 @@ class KChartWidget extends StatefulWidget { this.paneHeights, this.volumeHeight, this.onPaneHeightsChanged, + this.paneSizeMode = PaneSizeMode.heights, this.paneRatios, this.onPaneRatiosChanged, this.onAddTrendLine, @@ -743,9 +745,18 @@ class KChartWidget extends StatefulWidget { /// a change made through [paneHeights] itself. final ValueChanged>? onPaneHeightsChanged; - /// Splits the chart's height between its parts by proportion, top to bottom: - /// the candles, then the volume pane (left out of the list when [volHidden]), - /// then each indicator pane. + /// How the heights of the candles, the volume pane and the indicator panes + /// are worked out: in pixels ([PaneSizeMode.heights], the default), by + /// proportion ([PaneSizeMode.ratios]), or laid out by the user dragging the + /// lines between them ([PaneSizeMode.custom]). + /// + /// The proportions and the editing only apply when the box bounds the chart's + /// height and `mBaseHeight` is left off; otherwise the pixel heights are used. + final PaneSizeMode paneSizeMode; + + /// Proportions of the chart's height, top to bottom: the candles, then the + /// volume pane (left out of the list when [volHidden]), then each indicator + /// pane. /// /// `[3, 1, 2]` over candles, volume and one indicator pane makes six units: /// half the height for the candles, a sixth for the volume and a third for @@ -754,22 +765,22 @@ class KChartWidget extends StatefulWidget { /// rows above the candles have taken theirs. /// /// A part missing from a shorter list, or given a number that is not above - /// zero, counts as 1. Null — the default — leaves the heights to the chart, - /// or to [paneHeights] and [volumeHeight]. - /// - /// It needs the box to bound the chart's height and `mBaseHeight` to be left - /// off; otherwise there is nothing to divide and it is ignored. Where it - /// applies it wins over [paneHeights] and [volumeHeight], and a maximized - /// pane (see `KChartController.maximizePane`) wins over it. - /// - /// Like [paneHeights], the host owns the proportions while they are given: a - /// drag with [resizablePanes] on moves the edge between two neighbours, and - /// is reported through [onPaneRatiosChanged] for the host to pass back. + /// zero, counts as 1. + /// + /// Used by [PaneSizeMode.ratios], where the host owns the proportions: a drag + /// with [resizablePanes] on moves the edge between two neighbours, and is + /// reported through [onPaneRatiosChanged] for the host to pass back. In + /// [PaneSizeMode.custom] it is where the user starts from, and passing a + /// different list puts the layout back to that. A maximized pane (see + /// `KChartController.maximizePane`) wins over either. final List? paneRatios; - /// Called with the new proportions when the user drags a pane's edge, in the - /// order [paneRatios] uses and keeping their total, so a drag only moves - /// room between the two parts either side of the edge. + /// Called with the new proportions when the user drags a pane's edge or a + /// line in [PaneSizeMode.custom], in the order [paneRatios] uses and keeping + /// their total, so a drag only moves room between the two parts either side. + /// + /// In [PaneSizeMode.custom] the chart has already moved; this is for saving + /// the layout and passing it back as [paneRatios] later. /// /// ```dart /// paneRatios: ratios, @@ -1205,6 +1216,13 @@ class _KChartWidgetState extends State /// Whether the volume pane is stretched over the chart. bool _volumeMaximized = false; + /// The proportions the user has laid the chart out to in + /// [PaneSizeMode.custom], or empty until they are first worked out. + List _customRatios = const []; + + /// Whether the lines between the parts are showing, to be dragged. + bool _editingPanes = false; + /// Set when `KChartWidget.paneHeights` changed and is yet to be taken up, /// which waits for the build because the panes it applies to may change too. bool _hostPaneHeightsStale = false; @@ -1762,6 +1780,13 @@ class _KChartWidgetState extends State if (!listEquals(oldWidget.paneHeights, widget.paneHeights)) { _hostPaneHeightsStale = true; } + if (oldWidget.paneSizeMode != widget.paneSizeMode || + !listEquals(oldWidget.paneRatios, widget.paneRatios)) { + // A new mode, or new proportions from the host, start the user's layout + // over from there. + _customRatios = const []; + } + if (widget.paneSizeMode != PaneSizeMode.custom) _editingPanes = false; if (oldWidget.lockPriceScale && !widget.lockPriceScale) { // Unlocked, the axis goes back to fitting the window. _lockedPriceRange = null; @@ -1901,27 +1926,72 @@ class _KChartWidgetState extends State ChartType get _chartType => widget.chartType ?? (widget.isLine ? ChartType.area : ChartType.candles); - /// The parts of the chart sized by [KChartWidget.paneRatios], or null where - /// they do not apply: no ratios given, a fixed candle height, or a box that - /// does not bound the height. + /// The parts of the chart sized by proportion, or null where that does not + /// apply: pixel heights, a fixed candle height, or a box that does not bound + /// the height. + /// + /// In [PaneSizeMode.custom] the proportions are the user's, worked out from + /// the standard layout until they first change them. ({List ratios, double unit})? get _ratioSplit { - final given = widget.paneRatios; - if (given == null || + final mode = widget.paneSizeMode; + if (mode == PaneSizeMode.heights || widget.mBaseHeight != null || !mHeight.isFinite || mHeight <= 0) { return null; } final parts = 1 + (widget.volHidden ? 0 : 1) + _resolved.panes.length; - final ratios = [ + final room = mHeight - BaseDimension.legendRowHeight * _legendRowCount; + if (room <= 0) return null; + + List filled(List given) => [ for (var i = 0; i < parts; i++) i < given.length && _usableHeight(given[i]) ? given[i] : 1.0, ]; - final room = mHeight - BaseDimension.legendRowHeight * _legendRowCount; - if (room <= 0) return null; + + final List ratios; + if (mode == PaneSizeMode.ratios) { + final given = widget.paneRatios; + if (given == null) return null; + ratios = filled(given); + } else { + if (_customRatios.length != parts) { + _customRatios = _startingRatios(parts, room, filled); + } + ratios = _customRatios; + } return (ratios: ratios, unit: room / ratios.fold(0.0, (a, b) => a + b)); } + /// Where the user's layout starts: the host's proportions when it gave some, + /// or the standard heights, with the candles taking what they leave. + List _startingRatios( + int parts, + double room, + List Function(List) filled, + ) { + final given = widget.paneRatios; + if (given != null) return filled(given); + + final volume = widget.volHidden + ? 0.0 + : (widget.volumeHeight != null && _usableHeight(widget.volumeHeight!) + ? widget.volumeHeight! + : BaseDimension.volumeHeight); + final heights = widget.paneHeights; + final panes = [ + for (var i = 0; i < _resolved.panes.length; i++) + heights != null && i < heights.length && _usableHeight(heights[i]) + ? heights[i] + : BaseDimension.secondaryPaneHeight, + ]; + final main = math.max( + BaseChartPainter.minMainHeight, + room - volume - panes.fold(0.0, (a, b) => a + b), + ); + return [main, if (!widget.volHidden) volume, ...panes]; + } + /// The volume pane's height when the host gave a usable one. double? get _volumeHeightOverride { if (_volumeMaximized && @@ -2818,6 +2888,7 @@ class _KChartWidgetState extends State (defaultTargetPlatform != TargetPlatform.iOS && defaultTargetPlatform != TargetPlatform.android))) _buildScaleX(), + ..._buildPaneDividers(baseDimension), if (widget.showScrollToNowButton && !isChartAtRightEdge) _buildScrollToNowButton(), ], @@ -2832,7 +2903,9 @@ class _KChartWidgetState extends State /// Which indicator pane's lower edge is within reach of [pos], if any. int? _paneEdgeAt(Offset pos) { - if (!widget.resizablePanes) return null; + if (!widget.resizablePanes || widget.paneSizeMode == PaneSizeMode.custom) { + return null; + } final tolerance = widget.chartStyle.paneResizeTolerance; for (var i = 0; i < painter.mSecondaryRectList.length; i++) { final rect = painter.mSecondaryRectList[i].mRect; @@ -2881,6 +2954,121 @@ class _KChartWidgetState extends State widget.onPaneHeightsChanged?.call(List.unmodifiable(heights)); } + /// Smallest height part [part] may take in [PaneSizeMode.custom]: the candles + /// have their own floor, the volume and the panes share one. + double _smallestPart(int part) => part == 0 + ? BaseChartPainter.minMainHeight + : widget.chartStyle.minPaneHeight; + + /// Largest height part [part] may take in [PaneSizeMode.custom]; the candles + /// have no ceiling, as they are what is left. + double _largestPart(int part) => + part == 0 ? double.infinity : widget.chartStyle.maxPaneHeight; + + /// Moves the line between part [line] and the one below it by [delta] pixels, + /// within what each of the two may take. + void _moveDivider(int line, double delta) { + final split = _ratioSplit; + if (split == null || widget.paneSizeMode != PaneSizeMode.custom) return; + if (line < 0 || line + 1 >= split.ratios.length) return; + + final above = split.ratios[line] * split.unit; + final below = split.ratios[line + 1] * split.unit; + final lowest = math.max( + _smallestPart(line) - above, + below - _largestPart(line + 1), + ); + final highest = math.min( + _largestPart(line) - above, + below - _smallestPart(line + 1), + ); + // Parts already outside their bounds, say after a resize, are left alone + // rather than snapped. + if (lowest > highest) return; + final moved = delta.clamp(lowest, highest); + if (moved == 0) return; + + final ratios = [...split.ratios]; + ratios[line] += moved / split.unit; + ratios[line + 1] -= moved / split.unit; + setState(() => _customRatios = ratios); + widget.controller?.hostChanged(); + widget.onPaneRatiosChanged?.call(List.unmodifiable(ratios)); + } + + /// Where each line between the parts sits, top to bottom, or none where the + /// chart is not laid out for lines. + List _dividerPositions(BaseDimension dimension) { + final paneHeights = dimension.paneHeights; + final volume = dimension.mVolumeHeight; + final main = + dimension.mDisplayHeight - + painter.mTopPadding - + painter.mBottomPadding - + volume - + dimension.totalSecondaryHeight; + if (main < BaseChartPainter.minMainHeight) return const []; + + final lines = []; + var y = painter.mTopPadding + main; + lines.add(y); + if (!widget.volHidden) { + y += volume; + lines.add(y); + } + for (var i = 0; i < paneHeights.length - 1; i++) { + y += paneHeights[i]; + lines.add(y); + } + return lines; + } + + /// The lines between the parts, while they are being edited. + List _buildPaneDividers(BaseDimension dimension) { + if (!_editingPanes || + widget.paneSizeMode != PaneSizeMode.custom || + _ratioSplit == null || + _maximizedPaneIndex != null || + _volumeMaximized) { + return const []; + } + final accent = widget.chartColors.kLineColor; + final lines = _dividerPositions(dimension); + return [ + for (var i = 0; i < lines.length; i++) + Positioned( + left: 0, + right: 0, + top: lines[i] - 10, + height: 20, + child: MouseRegion( + cursor: SystemMouseCursors.resizeUpDown, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onVerticalDragUpdate: (details) => + _moveDivider(i, details.delta.dy), + child: Center( + child: Stack( + alignment: Alignment.center, + children: [ + Container(height: 2, color: accent.withValues(alpha: 0.8)), + Container( + width: 36, + height: 6, + decoration: BoxDecoration( + color: accent, + borderRadius: BorderRadius.circular(3), + ), + ), + ], + ), + ), + ), + ), + ), + ]; + } + /// Moves room between pane [index] and its neighbour by [delta] pixels. /// /// The edge dragged is the pane's lower one, so the room comes from the pane @@ -4469,6 +4657,15 @@ class _KChartWidgetState extends State @override void resetChartPaneHeights() { _effectivePaneHeights; + if (widget.paneSizeMode == PaneSizeMode.custom && + _customRatios.isNotEmpty) { + setState(() { + _customRatios = const []; + _clearMaximized(); + }); + widget.controller?.hostChanged(); + return; + } final standard = List.filled( _paneOwners.length, BaseDimension.secondaryPaneHeight, @@ -4517,6 +4714,20 @@ class _KChartWidgetState extends State return true; } + @override + bool get chartEditingPanes => + _editingPanes && widget.paneSizeMode == PaneSizeMode.custom; + + @override + bool setChartEditingPanes(bool editing) { + if (widget.paneSizeMode != PaneSizeMode.custom) return false; + if (_editingPanes != editing) { + setState(() => _editingPanes = editing); + widget.controller?.hostChanged(); + } + return true; + } + @override bool get chartVolumeMaximized => _volumeMaximized && !widget.volHidden; diff --git a/lib/src/pane_size_mode.dart b/lib/src/pane_size_mode.dart new file mode 100644 index 0000000..7457a2e --- /dev/null +++ b/lib/src/pane_size_mode.dart @@ -0,0 +1,32 @@ +/// How the heights of the candles, the volume pane and the indicator panes are +/// worked out. +/// +/// Pass one to `KChartWidget.paneSizeMode`. +enum PaneSizeMode { + /// Pixel heights: 100 for each indicator pane and 60 for the volume pane, or + /// what `KChartWidget.paneHeights` and `volumeHeight` say, with the candles + /// taking the rest. The default. + /// + /// With `resizablePanes` on, dragging a pane's lower edge changes its height. + heights, + + /// Proportions: `KChartWidget.paneRatios` splits the height between the + /// candles, the volume pane and each indicator pane, and the split holds as + /// the chart is resized. + /// + /// The host owns the proportions; a drag with `resizablePanes` on reports the + /// new ones through `onPaneRatiosChanged`. + ratios, + + /// The user lays the chart out themselves. Turn editing on with + /// `KChartController.editPanes` and a line appears between each two parts, + /// to be dragged up or down between the smallest and largest height each part + /// may take (`ChartStyle.minPaneHeight` and `maxPaneHeight`, and a minimum + /// for the candles). + /// + /// The chart owns the layout and keeps it as proportions, so it holds through + /// a resize. It starts from `paneRatios` when given, or from the standard + /// heights, and the result is reported through `onPaneRatiosChanged` for + /// saving. + custom, +} diff --git a/screenshots/panes-custom-dragged.jpg b/screenshots/panes-custom-dragged.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5ffef8093524ac8b23170cd4d8dbfed0c9715e53 GIT binary patch literal 53675 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⪼SROAWI2Sql z==UpCo2tA5>OE=kZPW3lH6zHKHM%Gc7mBL+aqtDZ9F-k_CVkSt8!tpqXRf{c5t9MI z`*tTr4^x?>fdR3zlm2orq|+Ff?0(I#{_T0^_HurFB1f7sIk8=xQv!*B0%LXWvBX;9 zypjL}XE>d^6Jh`}iF1}LJh6`W;?P!u)5$5ISY>`U*K8LK;>MV`G+8+Yrn&HYUt~YD zG=^h7D=En6%FNHQwuo|)U{WYWYd~2lO_^FP%i*iG7kCDhaS1EFPJIf34JPs~{HWcZ z3Ex|gmK=S^8WYf5(43W1vqT&1q|>7i>_fd$cheZX7cI)hhm46&QKKX<352&D3Z$EK zNK2>`u&jkTT;jfozfwEpx*+-`2UAw0Wx{qS4vPQ{L(uu~3LGE$9xrZ97Y^s;;5#aH z^?%6|WcV~h&G)*!yAx6$<+*tq{ee1rCmc0sRnw2N;O%-;NucimfFyOj*lq}i@En2} zOcMNLSBx||sEQ=oCtFxl zBd5J-tM?0+ROeI{`M4C~>moL{UgeLEV=J~Ca^=^mN?_!jjX_!~%>*TmR!o%_yed{R z6mAnHrNlmOt}0fn_Nw`;(8 z>`Kd*dlM79a$*Q)vejY9333zHVbmlWfK<9?2PS@HqFHM7_D=P@S~6l9*w>-#*h=X| z$;*wFPSNZsulbdU;YvEN_}Or?HY4`f|G7z0F$cP?k11@jfr&tu^>ME`f+q6GLR5D= zOOsw_>kJI45^Ci_CY*e2O)c2VEpKtk_e}d;;dFg$xilw~MU2!P?a`3{s-K#;nwIdV zm*NB;+T=_(rkj0uRF}8qg;GDX;xpkJt%T>jhVPrMYKV-blvqZn^kVZ>x4KysUaOJ0 zF|Yr^@0!yPayS;Sa1vgD0YblXciGJ2q**`6#3^6~s>oJ(GL<$$xQ7g1cLwtO2HehD zs!zn$lhGd5q}zQ|l$I3slm+c%A!PPuYad+_9;vLY8A>FnkkE5**49Z># zy~irSQCJ7%OBs$|O=VB@CY6s3cEL2iP8<=32}8)v^`FKw=FXU%0GX zd$?vEsonErb?LE2A$7vOLIrUPVD2hYp4*|uX4&Lgwqny9$9XfJr$*yN7c>uYwE-(} zF4+;`;mlcyoP4H_IuQk>TbV?9)}$e-k{T#sN8dQb+w0){wwId(mmn_JSGiS+uO>ld z;aeewlHb&(KdVl4#sC$bdYNf%v*uMO%{J1xlQmgdHsXXL1#?(71{L(kI6(VBULu4@ z0ERDr@ zp<|{BmpkzWZmAL@E_im=?oP7pjH$;LLC{^KySzy_RPL$1UUm4UjE#V1R#jIZ%KIg+q{ypuGO)sSZLUd`#_JKdqRM zrx27(;l7Z0y58I@>+HX*M&yJUHoC;uw_hb>Ib%cl!jku>p^HOQ*{-UV^xJ!Ee}3ia zy`h^i=57qh>@L|^G!xF;FH>}zG;Rx)HLaclQ;LerOo#ra>rsvEziz_8(z&LSqc5NW zGC`Rh6)FSQvs=x@G`y`U^9_phW8MHq_;*h;-l;i*;@bVZ?df09J+QkM-ltO&&O(sY zT^uFd97(c}Imk1RdEpm-{v2Pa)fjc*Au7REvBgTaIoYH~vp>H$68V#=>Ai~L5%?mD zShcof%?%;r#Eo}D@D9pT7to}JLQVx4(Ax04N1c@nhw?IZ@R?uX;MH)D^gKF{Gr%8Y zN-->O6c(Z}j0xJer*G1^rQ}jS9um=2ZV}+q?P+J9x152iE>_~6f+)eyr5_6%mbaRAs=1!> zBHp&kSceIug@?P{=8mgKR>w_OS)=WSt6^AhkG*r9iSDFagf229&wdtW5I z-+UKW@0Z`GZr6`bTvxh!N%&n2_)_GZZc^fWFY-{#jE2Ln-a8~E-m|z17IsXJS7zk# zT?=sWT>lQG&R18yI=;;f`$e*=M$WGiJqtPPkB^~Ts#u> zJYwye%iNjm;fRpm0LL^e*6@{O3wb#w)DyIdomlIL9DI2fX2+x-lpD_uB*-@A@|`tK z&GFo=4Q1tJxpfbGQ~jm&+_|$*k7@Kfy);u^_TfGA!hUHLETuta zq2jPhCilb-&Rzk{j=}&W-(1b@H=u&DA(_HgJ3M6BPK~&RCV>BAZLaM^8?uDjpC*k#FOuE1&&hc z@B&r}$#lVn^@GOlnpFRcnBB8c3kimriB$~REp(m|9HI8sUeyuV}Mjfcas>vKbSq(M5;M6pT8@>?>orMc{?6L>wpDyQvFCuRUed1 zV1fjMhV|B5+UW{V3AC=WrFRF1Ovgoa|=s^bl6N!j)xo_vI1Y}AyHifVNdW+64?>tmn! zHWRCTv^z1Kc|WFKpo1nby;kk~%xHC@txbfW=?5L;MIF{^i~J z{QQ67q0!ED_mXi3Tx6nfn*ca#OXYgFSl|Y)5=lV?3d6OqBp~+FAhm z=91oR$vf1&k*-1o5+_BrZ$ao};k>n)uu^Fr>V`|*1R3_=U4Ow~i&668jF~KzYEp4*dIWE-;g+3kD+5opT!O+aT zy7OZ`U^0OVPs{-msg>6>9Zfvfce-FdNO|oAgf-P28jGJ=%6wi+nj@CTkNG)rGgtWIsEK zyn~vukRk=u)6|-zfZUqBCAV2%BB|Z8dDFTH0ZzF#d-QpM(E#Sis5FJa)cU5JwCQP9 z&E{X~Q}+aF$`acGqNoVDiYW4k^m8guuinc**Pb3+S-8YZy;C%z1m_`AxEB?RB`lJ|EE#tQxp-i zQM@v22YoM!7h2S{itD!zGyNFtrqZR#4_H1mc!rGw>AwM$aRw_5vJcz80jHw9<|DN0 z!}E*EBjCM+V*Q6Au-8)+YI4Of0L7R|XkeR&_b5spxr1H~v6KDW1tlle+VwonZ@}^N z(A0S=W^p&Wh0FFTfi83&4eGW3_kc*2Jo*jXLcMOrM(T~4*G%(;;(nkXP^NZZeK$X zSV{=lbk6O-Y5xYMIvL?Y*?Nk;jc}7T_-{Z;S*PRBuvvr0>(QY#YVx2BX4g9EV#L&n zN&mUO%eLJvRDE0mB%#6cdIrnsBLYMkv2DqMo$%S%ET(eH+J?alb}=}`>a7NAMbmkn z{iBd?{=W-z){p2=XAUxmST&=leht=Wy~^7uF4xfM^v`ffeMl%U0P zTJ7yuwsW_(l&D!Gz*6u9lZ!imi$9gNWe|U?iMa5Cy7f=euE{~BAPs5kqoH zU*Do9#ip}k3LPfk1Jf8{A+G7*vF~Xh+FI(tIbiqx(97t0VQ=y#`5HnUMMCknbL8CI zsuDj!?VteEGPcM5Ve^f5BWNS%(J|P`WGOXoL9-Nk`No+{38=a?`~fS3OLldsm+;P~ z14K3(f{2AbIxH=q8fhr`>-`sc#DhqL#}Crj_NUy;OFTA+q#qG1#1eu+b{mr|xf_%0 z;^BcSVY3Z2z}Jo;joR4UsB^zc2B${5H3C*}Lol<_a&vqdE%fIuIZ5kFap;q1ZBoYX z#h|^cYOuSqRHYnX7dd$&8AlH%sBv52qJ@Mom?wh1rc#!&+I~Bxr13Xkw{P(DoNCOG zE}D+b(W$;(wq~lFw#ohK>_~G3(ttdLZC&hXPG&Su^DK%(>;55Y(lExQ%DQ)SN!;{l z8d#^Xc=6$>axc^?3NY=o#_zCnPHlf$$NiM6Rq2)t5?WWxmOvl8)DxV_P|!`=wH)jp z4b2FYO`(*06;D`;0BICh7~_4BU?&rF(pSQ=3VpvFG{J>)0V;Xl#b0cHp<~c+GfAQ@ z^l@~qqLT^8lic$CNPlK-kXASUzQ#jFWTZVxc%$?|B3PrQHJgJ>1&y0cq`d9jER>NK z6sub+spCwsS8yVP`fQK8Voa(3)vIMfcw%U3dqW>-lss8#ag;eS*ZbnRHY1BEXLmcwb$~r9t13v7(1RkuI=w zJOE-BlQ>--p|4vnEbKCp^v@<_D|$#MQ%}g8inV+PQwU5+NT((l9S%8t03D3lu29p*9ZWYadzRVsV15l&xC-#@D(4U%doi;T;eL@?QF!0gV-BW>PZqkTbK+lt{^L5W{k3r_{EGGo+&f<-}-C5pn zFHp{Z7+HNVV`biNcrq=%9nMs-G9IfxJ@2tULZApX{%&&E-%VGm8;W2k^d3F7WPKhW z0Dmc&XaH)ge99|ejpV5BHWPPso%ZOF+0eZObz03drzU0a-FEucMm@;aXc!E*mRKKF zxnI-jP#EsuL)V}p!^scdt=0JVl$N5-PQUoBRE~hPz`xG;f{-h2^Os6fqf!{i16#k| z4=O0Fd}&aKRvK^3tdlaO1^_iX#u+Kb4toLE5w(aC;Pum9yTq)yL{f6$Gt09_5bMQP z=cA29=PvvKRYXHi7S8G25@}99o!j<5&hZTg)Nz_S= zpSSs7I?yMyYnK)pX7;Xzqd!><>q@YRO!P1PRjqbuO{A{t|De+RADx?8bWQ}A z2R-)xVQQOp31-Z#N*60{2g5s%UX9N4W3E)jyqvsJ-*<92Nmn79HS#Xh`q;L=)Gy;r z0|kVuf=b|&-WHuk4unWCwR&aa^*8Qt{UY$3?3QYQ<_U(RbuU&$D8l;oWwR$UGv^5_ zbsAsy75!#Xp|4k@%IZ;SOpJdk9@87q=@4T3NYqlv3Tv>1S^pT}K^i%kw}equ5R>j7EQ%0j=^Bu&4h}cJ6R^3(t9L!;PPLjJsVXz5Tt}!^ihJZ|{cKV$%x(A) zR~#c~)beGElPAMsiMI{MS-pI~LYv_@ctDe+zvv_Ks0$)pRy@aKQ=g?TXqJ`ft7KFB zZaHNO9=maj6ja+Ah?QveWcejg=dzRyo;mfh{SgFkh0nt-)I`4f>^A^GgS&@|UNK2K zT{e>#s9feXs^QDl=8ERC%ACu?Gs z;2m8H(lCO}nX$FCX}6PK#)pz){|edR`Vl6u?Zx=0KRvwv@eTjAC6w8LBLvg8CVSk- zvVe|Ce&+HyYa@$}_on{ZK_~5Y4_2@6C|gC60oCiQilqd0iZ-?R_fK;=PI_y?Uy|A6 zHa33ph1fAZ*-7T6l+1*pP(KC=QVf1Qr*{#W7OLCKL0d!4Q}5mj(MANr4RTpi4c@=h z__pHWNzP5dDrEwdROMhZ;laNBFsAe6X{(^i;jEGBxntET48MBQNvWE@JRx#{8vf%f zxwUStWspu3*r7v8JwO^{{&P;N*(?B)sW9;jL#nu!Y&50ek7jcd6fVeG6``l@TTg`; zFiNE7oY!ID+m^R0No`3;?G6Exn~unnuM2;@>@4WvR}>bOEi9hat0?EH*%xf*9S?jA z(NTz8Rt6?EF&SsRCLg-KB=um~*nHoMRb#Fl@9hWX%9+}SN zj2QINdEMy<4r$6uRw`+LA1znU97*PMRgfvySFS(jdOq|$0m0_*EB;hFtZHz7-iFSZ zi1qXVGxb?+Mpj9>3Fl^F2WCwLfBbZP&GfXl(fqi^FA4xemd;ccHLm0~(_`?tu$X=S z|1jd{o~$8zcqsVC-rAWgebu!`(74;>wuu&X;W2+ErFcylJO5hsfdxkl#8b^2n2z1a zjxe2?kgW*ojuzfxYtp#1QAed*Z!~ed&x1}K?(w$zs{QjYGZ+9lFrhdYDLC-ZY+bXH zr!B0wcNJuCux64^46VMNU>|ULy+7AWR=Nbdvb|%i&VLd*f?Dkrn7XC3 zUzyDC@EQ(GDAfKnJzSt%v5FI}S4vDxT>VGybpjuDhwUca3JJZ(_04)VdxpeYaVf~Y z?Aloho)%Z_C$&gItz<6)Hk#@BPdVE$zj25-EP{#AnjnIAn^hb}e2^KnrKr%aMf@0ckZQp>#K<^)?WT$C)YFD7v*J|R$RW~oO zF5CrCZM@yDK0V3Xbu@5FK7I9Pf$C*=8>t)~UosAUDu%ODl8nsnTvV5T28OPJ94+$mmar!0M7aBf4rfNX$#u&Sy^ z)F|w{`nZ?T2)vF9%wVy@anc(V!nCQwaIG28Pjqk`ovIPSPcn<-ujrQ>BWfolA;Q#W zHky7e#S6D*b#JpB@zq=lmww}}^_C@U7=*I?;weV!XUPgzGG&?7nVGFRt~&pa85DcZ z505mg7b8~^^Q77O$zPiYGjr?c-Ud>KK02tHH4Pw0HZ>;+i7bMTrO=LXmt=ZGZX{cf zKepr)o+u6~olPg4r_~N5U6 zWh=h#(fg2w_Z#%8lKw4xvS%Ee@}%FbW?}W>DT1A0=SvXJkE=yF!5*TynyBAda2a{`~#o-W0*C;9#;EGbX;8AaE!_8H$+C|4)KfD&OEo)^&db=P8=%}TySu1 z8#$@MmNZS~Z$M!H@H^#QCi`xhVlZl~ctcmYi-WyJM@f#49_ z-L(oST!JRSJvan+w?cyicc*~D-622%!P2+7bNZaq-S55Q{c*=PzEQvSu06NRHP>8o ztu^QKq#(_^w+w+rL|fcltNq+3K`YBhbFZ(f8Q^?&=L@&ngt6R|46sgVcCJuTG zE&CB<>SFb8UU2ht@Am31g$w`iB`KFafX<#+X&Mg;Shdu$mI46MZ&pI!JG&g0u^P=lP&vd-^Z4Nt@Kc0{s)ZV_tRPPROQ8wt5{)>RhlOx#;6 zoMxO9t6PRSk(bLd_bbx?EgM4RD++lUp?q7$aZts{t=>2ZG|U!NYhu3uU(SHOE<<{6 z?M>^_wT4oM)ogrCZ>_5ipGqNbSARd5u2sj;bs$$E6gu4DBmGR%&1?=s64e4%fLsoQ z49YQFQd*N!byY*<`^*2CdjVhp@dVT)rPh zFF|fS=F$vc?>t_MR?(a~*RH-Cq+Yp3TYJXU16$-F=ClnzZggIHx`*$3W*xU?L(||( z3I@V%MHjio=QUbGX)8h5@FL)(?N$bBYk{j550?pDKW(IN;UtBp3YH$eMY?7CAhSWF ziK2bkliS+XB9n2gnZUU;{V8;Dvp@yv{pj$a^!b%e>6EW0@gDA{vT)HNfJO7RdKK=i7al< ziO>tOQH*K+8B#%zaBVzVKk(Xngv2~r5mg&^;p^4Ir{`~z$lEh!C@Q_i6y!QO)nf05 zi~F+ajh(p4h8vS?(ZZ@r)1x9fFWlRVZKfGgch;-ci6AjPS?I_j`SLXF8T{v9# zlmmj!uUImD|2SJOOh&|2d97DYo8?wIa=NcR0=kjX9)|nSeZd)L9xwId_FJ_@1D!^8 zS4RACU4p3lnEQ5KamK^e&Z`I8I%SdSW!v_2;|>{!~^H++Sr;d8YSs3*v24{`$5Z z+`#4o6NRb1@A$Fs*F{w1ZH_z@lrHWDPPaxP(m##OrcVQ8ulS7-^`oPmsl~s9Sx0J` z$u{RLh)5}cG1Rolx;H?au|C^OFF%?GMfR%V+3}KZ7nan2m09d!dlM9QY|qK%<8455 z73X$U!q|P7J2hqzwz{xmF?fZdz|u67)XZt={};jMwyZs_6#9XyBJ@_M>H6UHbuWCx zBgz%GRsPyj%i;HLiKf(M#~pA)r>U`{vKN(lWqOqFx9mi3x z)}DppSd`(%Svo6uXCOqNURhyyF}2~&|0BMc(+zb07ayWMX*LU)OI5UZCf)EE%D9f* z($v5DDbR=R8jk`zhfmm&87AB(XS!oVDxjLRJts-cpB$Pi5Y4 z0ucqBlp2O1D^FJcqAk|JURwtn({JN@4T;aDM%JFI;$UFC@pBmoxS7%XfY^1sV*Qt+ z$@(Q%u2kNgbyn2949k{Km2SDJXJ1r$KWm3JW5e{NesE!E7bnu;$XUt3_1W#uzQ38B zXtNtc@G(;|(ou429tx~&R^KQ(_9`rTz3pO6V886++tfMq35$up+AL9x zB5Ghd3q7;5uiV3|4`N)OR`bo9_^WwV=GmratI4(F$k40q0a?Qz+m1RhHe6*-DKaOS zhflY9TH2!b)k9$&89@lx;?~W(Ibr>HMf&VI%Yudmo3X`CuUd+G?V-7kb7qb&9=XUY zN23Wg2kxeT{O_826cNu}o71X=bd zV-@UrnTRE@i<{IfM>R@5@0~@%3*kn#HU7%1MXnSBlO;bA!B?`*tqZJ0rKKOO&?Ogc z^i=yXWM1s}g)v?;L<`vUvL=3_a8RPARXAEJeBvh)DScz1!PX)8GRdRL5}hgkUc21itvfBD-%ziAReU` z(D6-0wyPzFuXx4b-ckAUe^}S#dnzxj!FOpLUAtPwjEU*+*k5}&$hxTxtZ!7dJB7UZ z3T1!A@2#Uht;(DE?(W?I11x>M@RpEdOuffuLT#jHS{B{h7*li7^R#ez1%{xB^6zYXzYFQyKorYaL!%Z)d31a!47QZWn7<*3O*9_u9odC!VZ;zBsXZzw zU`s{tefmUqqaIPwmK0h)7O{3UYOu<8_|ADszf^WJJuv)(E3cXw#+>xkMB3P| zLR>FoEU%aPH>6UcYTQo3{D4c6ADq$-JnW{h*DR$QMBj23BYCY|CrA;OsMhE=VEm+{ z%PNW=kEqMGe|EKIZs52ZZ0NpD3e^>{Thn=4Y5VGM_x1iIHtf#SMEZmgmr6tGFk!ec zq>}~3nh?}rGS&&-I3d+uQNo~Dka9KW0p>f$E{a1zNK+F4!yDye_qglX7-(n{zyg^8-9v8+gOhsuOU>Ylg0#+!zsf-<;`3& z;cN?yWn>U53(K|gr_B<-z+$5^r5Nq;#kjj8R6M$S2x;*#Ii8aaHg^?x-jg|!5jyfM zpgio!%&k}islt|$I56ajI4-JnO?fqvi%ISC;2y9fxg}^m)$k*@XLPn9{>V6r3pauN zJYu4pmw+MK$rS5garWYDGu_11*jnK1`>tBbqpBF0hAE&df$0LnQ^TbI%WVD2p_u_b z>S?E{ZgC!R zGuE4$L+GBn$e{H)jsZ5!F7;0o$*W>0mdjPAHD*=Sv^(@CzJDx#GwQEA1Lw*Jx4uA{ z_m6y0a>jhLZ~r1=QJOa;PF6ndHa?;Y*JWWL^a0UR4maW|a7?q_NW z&vx!YEL^ZgI-W` zm117o$J&M5KAG@ei|6|dX=#2kan0rXCD#Ba<=`6;K~%~5oszUOH2B&v{FVE`fXm6n z@Rb9lJkeyB{E5i~CePIh=qlAIr{enLhVb9_k*z-E>wHFMV?MgnY9_PD;O0wrP~aL} zw{uEwz;4lBW(LNIVYLoZTKvYr?05YsK#O_?=Noa>LnoaB_nL&>+0q4vPPoYPmxzj1 zd)Sv+@CAQMy`fmwDr~bog`_-lmYjieDWjQGaLM4HDsrp{u+JuT=Pv~}>jIB z_p~Ln$5K-B?A74{DZNg&zc!;1SYk8ukOPedi>CBI-LfSHd);0 zkLnye()2Q{>dr1*cDBBd8_-j|k<{~X^>F$Lh9C9G|9SDhrHvV;Tk+jP-5o--wwx~_azceej+H4BJ_9W9&yT>n0}xBWq1pE| zPJODM)tzb?oeOnvv&P$q-)+pnw!D>>ip@YPRI5W(X(gNgo^owbcS}7WjqY<5+r?s$ zcOf{RO);$w5Z>{&v(f62FNR5;UE_mtdGST8R6wEDkKYjaRrXI>Pa}sEu4Xo%;KZA% z36QU1>~YgVL8tKL9hrA#_Kx*Cdqg2_hUoEPWp&v-{@}FHw>RWXPRF-UZI8dJ)U|MS zelUbtqg4`Wt$n-#A}p`V@Nd-UBc{(5r`!#quY3g>BR@Hw^8R}ZMsiX!%WYRd=!fuB z;R_BPEe-+oj?~bMK5F~X(7Ks#q|6PxZ%Nz!-WKU^<>3Ldz2AAbDN?Tlnmb{ZD>unF ze%yRF`QEj+4xW+MzC`~Js`@bN5qq!Dl99Z`<9pWf`pO6vhIKl-U-@T`VfxdQ?Tp?W z`aMd$wcuC*q<@ug zc&HtQ?Y-TUJN6{-tvZAkW9g5V_}@9Lo#t@+J z<`U4R1Yc8rV)RcD4le=(o{q0R?YYLEZ-scx z&e=cwg)v>q>ng}c&atNUOHjaykEM;{6mY#Ro^JG{RA3I>_45Md~w~d z?^m|GBJ(4XDpU%GchF*y9=^3SQdQu%PRv;wdVJ-}x|}kWQURRb+GSY_n{1y`=9?2J z74R!|{8Rz=@Tw5?UHQ2uw;iovj3|iYQMgT&KUiT|6pc?E z@9<*CvuTpz0o>UIaooO-om+NJU?XNCc6yA$y$Zr&~iT?g&CfotKHK zqjV*B5GT8K)9l6<3noyHL(he;%Pot)pfslD%u*frd5fSH6_nyn1&%*+uhkb7D~qOx zh_&{%NZ^b+xVnN$88io{3{##|g|wAY*geyTuU_ou&8)I@7B*=PPF&D(*KqAqq_vi3 z7~e=LkeR+-`1BaLU@2ip!M2f47ndbWo}OjD5E3IJ&19BBpS2Eq=Qq!Qt~W~@#fzz~ zJo5-Qk!)}fck_hrDu*qvX7pvFoJCsV$5s}qq*#K4c`6tN{L_=69}WnhWjm{SGUGe$ z%)qCs^B0FRoxvz&EZOoJl<_jR-Wd!zWbj>lW85R$yNXW|`u&4@>dI4qchqkztsu*| z>C|#h_NyrYPYL$;Tv4I;+w4M@$-AhW5AajX%z2(u3%LGn2EKIIbG-jvivOeX`Xv6i zQKzL*fCPxhSIZG4aV$+cp`C7@7lI1Rn}naM(dA6oKO|4^3^|K$fbLZy$O~OZR@g?O zPdB&xq+|DMq%eH-c&*{D_|M+19KCE(bAB@o5aG|V_$qK3@p?oNP0pCuE_E)J=k zUaO-RPB2uGDrocOV!AcBVtB5GocU4yIr*{?DC5)iM;8^sG0WlfFvA(ji<)|w!g0R~1$)eE)mDM*i*cNBXym}+ljwT}rS+k@DGiRNiwHXCJ9pJQb&h)G!5ibf z!E3Qo0ZvE@0vY@LqyIuM{|l%5AGqeo%i+sm{{R;GM;R8*{=2{NKH*75D#7J|^peZ< z{Dk-t>929Bc&SfTpYVyx9qhOo&MRC{fr7Uxo4lU?ov~mRH(4#B^biA}Juo{Xts)fD z%m)|y6!|FTq$SD(5u@1G;;F3RxVfL?h}i{$cq{xc!HO1jBM#Ur+smkJJgo9Zw?bOj zV&*>H3}CV3@7@3ay9GITlIqhK=-cRSup=c`5g-2wNl{sFVPypK%F-Q4x~z%9se*7d~NS06>{M2YXVTZgWG5aj)S~<@d8D#hLVox43V_ zW>4S7zVCx!Ef~ttC22aA&Yp+n?A$1V{ zJDi6k1H$|+yH}yi*ntv!(e4Hm*DXt;aili$-jKvAuO+V<6npA44Kp$zA&%%%P0c1qhZYUzLLv!XGbEc2CMVx6}$8zjNHw zx;8g@T3N!Y(>=t9UmN(54Yf`dX*SS(+LW&eV5>^j_COm`iyhs4Opjj7yUEy9d%R4C zjoEbHIO9V!1-IpsF989&SA556Bz1XwD{9J=F4dycIL+;JH#bAzdGr@;DjJo4R zWHzFHtE~Y%1CB9WkAUH}j<2UTYfW)0a>{bvqJ&pPC-LwNr6%k>1)qQ^C~(>ak=k@< zgc6Nh%Y2yUtdbgz!Rw-7Jyrr@xzvgoSRRzeOMNF*5en=E7=`^jKGB6Qhvq`;Eo!~+ zqUx-7NJt&4t@<-!kpj2W+yy_ckoZ5@2$|Wb;{Oycs6hE4`#(x&aF_ra*WiWiC(;`nFAYjLmLm+`6TOcG2IkRp8S z?O3-tqWCbHcvrWqYd^~)GP-ixRiL@W=(Sz;*&v(LkReWy*oNRDfgi04&J_~>0@ z((?Qnrr1`2{;!v^_4}ZjM4atlgIK%dY>>q2h& zqBxtXhW+cMsV#7@BExEy#xey`kpxVP1{iUqEx$`SWP8N&2kKRR=d%?HcZiO%F3QU0 zgp<>caxS0H{VaT1t70U^rfA`+h+8B?M&0eT zZnw=dhPCBaopeI;gZRS@8fI9^cFK0ie+ii+AS0-j4qCVdY5ph868QQ{{IC8}#1Z|@ z$q`ICsL$!bmx0S1oW+WHeA}n$Mg(j2O?UkHhH6hm^-Omb=Acg-!h^%Bw1h9vvbl8? z`_c!APp@r}q%}eQy|zsE*9A)?QUdJrL2;DbjVcCRR#rJ{Khdc%s&@

vFAGtt31r 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 SUw{sN&#wHh`(ysL`2PTw0Nca> literal 0 HcmV?d00001 diff --git a/screenshots/panes-custom-edit.jpg b/screenshots/panes-custom-edit.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ba9dce7fa0bfa869258be989ce1ca1dad2e1c189 GIT binary patch literal 54840 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 literal 0 HcmV?d00001 diff --git a/test/chart_link_test.dart b/test/chart_link_test.dart index 5c1dbdb..d0cc031 100644 --- a/test/chart_link_test.dart +++ b/test/chart_link_test.dart @@ -139,6 +139,10 @@ class _FakeHost implements KChartHost { bool get chartVolumeMaximized => false; @override bool maximizeChartVolume() => false; + @override + bool get chartEditingPanes => false; + @override + bool setChartEditingPanes(bool editing) => false; } ({KChartController controller, _FakeHost host}) chart({ diff --git a/test/chart_overview_test.dart b/test/chart_overview_test.dart index 9f2ba60..68c460f 100644 --- a/test/chart_overview_test.dart +++ b/test/chart_overview_test.dart @@ -78,6 +78,10 @@ class _FakeHost implements KChartHost { bool get chartVolumeMaximized => false; @override bool maximizeChartVolume() => false; + @override + bool get chartEditingPanes => false; + @override + bool setChartEditingPanes(bool editing) => false; } List series(int count) => [ diff --git a/test/pane_height_control_test.dart b/test/pane_height_control_test.dart index 1389bf5..64d5c09 100644 --- a/test/pane_height_control_test.dart +++ b/test/pane_height_control_test.dart @@ -25,6 +25,7 @@ Widget _chart({ List? ratios, ValueChanged>? onRatios, bool volHidden = false, + PaneSizeMode? mode, }) => MaterialApp( home: Scaffold( body: SizedBox( @@ -42,6 +43,9 @@ Widget _chart({ controller: controller, onPaneHeightsChanged: onChanged, resizablePanes: resizable, + paneSizeMode: + mode ?? + (ratios != null ? PaneSizeMode.ratios : PaneSizeMode.heights), paneRatios: ratios, onPaneRatiosChanged: onRatios, volHidden: volHidden, @@ -630,6 +634,7 @@ void main() { timeFrame: const Duration(minutes: 1), mBaseHeight: 300, indicators: [RsiIndicator()], + paneSizeMode: PaneSizeMode.ratios, paneRatios: const [1, 1, 5], controller: controller, ), @@ -640,4 +645,229 @@ void main() { expect(controller.paneHeights, [100]); }); }); + group('PaneSizeMode.custom', () { + Finder dividers() => find.byWidgetPredicate( + (w) => w is MouseRegion && w.cursor == SystemMouseCursors.resizeUpDown, + ); + + Widget custom({ + KChartController? controller, + ValueChanged>? onRatios, + List? ratios, + double boxHeight = _boxHeight, + }) => _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + mode: PaneSizeMode.custom, + ratios: ratios, + onRatios: onRatios, + boxHeight: boxHeight, + ); + + double total(List list) => list.fold(0, (a, b) => a + b); + + testWidgets('starts from the standard layout', (tester) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + final standard = controller.paneHeights; + final volume = _painterOf(tester).mVolRect!.height; + + await tester.pumpWidget(custom(controller: controller)); + expect(controller.paneHeights, standard); + expect(_painterOf(tester).mVolRect!.height, closeTo(volume, 0.01)); + }); + + testWidgets('editing shows a line between each two parts', (tester) async { + final controller = KChartController(); + await tester.pumpWidget(custom(controller: controller)); + expect(dividers(), findsNothing); + expect(controller.isEditingPanes, isFalse); + + expect(controller.editPanes(), isTrue); + await tester.pump(); + expect(controller.isEditingPanes, isTrue); + // Candles | volume | pane | pane. + expect(dividers(), findsNWidgets(3)); + + controller.finishEditingPanes(); + await tester.pump(); + expect(dividers(), findsNothing); + }); + + testWidgets('editing needs the custom mode', (tester) async { + final controller = KChartController(); + await tester.pumpWidget(_chart(controller: controller)); + expect(controller.editPanes(), isFalse); + expect(controller.isEditingPanes, isFalse); + expect(dividers(), findsNothing); + }); + + testWidgets('dragging a line moves room between its two parts', ( + tester, + ) async { + final controller = KChartController(); + final reported = >[]; + await tester.pumpWidget( + custom(controller: controller, onRatios: reported.add), + ); + controller.editPanes(); + await tester.pump(); + final before = controller.paneHeights; + + // The line between the two panes, dragged up. + await tester.drag(dividers().at(2), const Offset(0, -30)); + await tester.pump(); + + final after = controller.paneHeights; + expect(after[0], closeTo(before[0] - 30, 0.5)); + expect(after[1], closeTo(before[1] + 30, 0.5)); + expect(reported, isNotEmpty); + expect(total(reported.last), closeTo(total(reported.first), 0.001)); + + // The layout stays once the lines are put away. + controller.finishEditingPanes(); + await tester.pump(); + expect(controller.paneHeights, after); + }); + + testWidgets( + 'a pane stops at the smallest height, and so does its neighbour', + (tester) async { + _tallView(tester); + final controller = KChartController(); + await tester.pumpWidget( + custom(controller: controller, boxHeight: _tallBox), + ); + controller.editPanes(); + await tester.pump(); + + // Pulled up past its floor, the first pane stops at the smallest height. + await tester.drag(dividers().at(2), const Offset(0, -300)); + await tester.pump(); + expect(controller.paneHeights[0], closeTo(40, 0.5)); + + // Pushed the other way, it stops where the pane below reaches its own + // floor: the two share 200 and the lower one keeps 40. + await tester.drag(dividers().at(2), const Offset(0, 500)); + await tester.pump(); + expect(controller.paneHeights[0], closeTo(160, 0.5)); + expect(controller.paneHeights[1], closeTo(40, 0.5)); + }, + ); + + testWidgets('a pane stops at the largest height', (tester) async { + _tallView(tester); + final controller = KChartController(); + await tester.pumpWidget( + custom( + controller: controller, + ratios: [2, 1, 3, 3], + boxHeight: _tallBox, + ), + ); + expect(controller.paneHeights[0], lessThan(400)); + controller.editPanes(); + await tester.pump(); + + await tester.drag(dividers().at(2), const Offset(0, 150)); + await tester.pump(); + expect(controller.paneHeights[0], closeTo(400, 0.5)); + }); + + testWidgets('the candles keep a strip however far a line is pulled', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget(custom(controller: controller)); + controller.editPanes(); + await tester.pump(); + + await tester.drag(dividers().at(0), const Offset(0, -500)); + await tester.pump(); + expect(_painterOf(tester).mMainRect.height, greaterThanOrEqualTo(59)); + }); + + testWidgets('the layout holds as proportions when the box is resized', ( + tester, + ) async { + _tallView(tester); + final controller = KChartController(); + await tester.pumpWidget(custom(controller: controller, boxHeight: 700)); + controller.editPanes(); + await tester.pump(); + await tester.drag(dividers().at(2), const Offset(0, -30)); + await tester.pump(); + final small = controller.paneHeights; + + await tester.pumpWidget(custom(controller: controller, boxHeight: 1000)); + final large = controller.paneHeights; + expect(large[0] / large[1], closeTo(small[0] / small[1], 0.001)); + expect(large[0], greaterThan(small[0])); + }); + + testWidgets('resetPaneHeights puts the standard layout back', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget(custom(controller: controller)); + final standard = controller.paneHeights; + controller.editPanes(); + await tester.pump(); + await tester.drag(dividers().at(2), const Offset(0, -30)); + await tester.pump(); + expect(controller.paneHeights, isNot(standard)); + + controller.resetPaneHeights(); + await tester.pump(); + expect(controller.paneHeights, standard); + }); + + testWidgets('paneRatios is the layout to start from, and a new list ' + 'starts it over', (tester) async { + final controller = KChartController(); + await tester.pumpWidget( + custom(controller: controller, ratios: [3, 1, 2, 4]), + ); + final heights = controller.paneHeights; + expect(heights[1] / heights[0], closeTo(2, 0.001)); + + controller.editPanes(); + await tester.pump(); + await tester.drag(dividers().at(2), const Offset(0, -20)); + await tester.pump(); + final dragged = controller.paneHeights; + expect(dragged[1] / dragged[0], isNot(closeTo(2, 0.01))); + + await tester.pumpWidget( + custom(controller: controller, ratios: [3, 1, 4, 2]), + ); + final restarted = controller.paneHeights; + expect(restarted[1] / restarted[0], closeTo(0.5, 0.001)); + }); + + testWidgets('no lines show while a pane is maximized', (tester) async { + final controller = KChartController(); + await tester.pumpWidget(custom(controller: controller)); + controller.editPanes(); + controller.maximizePane(0); + await tester.pump(); + expect(dividers(), findsNothing); + + controller.restorePanes(); + await tester.pump(); + expect(dividers(), findsNWidgets(3)); + }); + + testWidgets('leaving the custom mode puts the lines away', (tester) async { + final controller = KChartController(); + await tester.pumpWidget(custom(controller: controller)); + controller.editPanes(); + await tester.pump(); + expect(dividers(), findsNWidgets(3)); + + await tester.pumpWidget(_chart(controller: controller)); + expect(dividers(), findsNothing); + expect(controller.isEditingPanes, isFalse); + }); + }); } From 45690d852f11a0586997fc81d71fb8519c227aaf Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 03:35:15 +0330 Subject: [PATCH 10/12] fix: keep dragged panes big enough to grab again A pane or the volume pane is held at 32 px or more however small ChartStyle.minPaneHeight is set, so the edge or line that drags it back out can always be reached. --- CHANGELOG.md | 3 ++ doc/panes.md | 3 ++ lib/src/chart_style.dart | 5 ++- lib/src/k_chart_widget.dart | 29 +++++++----- test/pane_height_control_test.dart | 72 ++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d088a..256a6fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,9 @@ `mBaseHeight`; the example app has a switch for them. - `KChartController.editPanes`, `finishEditingPanes`, `toggleEditPanes` and `isEditingPanes` drive the editing; `KChartHost` has two more members for it. +- `ChartStyle.minPaneHeight` is held at 32 or more when a pane is dragged or + maximized around, so a pane can never be made so small that its edge cannot be + grabbed to make it bigger again. - Dragged pane heights now move with their pane when `onReorderPane` reorders the indicators, instead of staying at their position. - `KChartHost` has nine new members for the above; only an app that implements diff --git a/doc/panes.md b/doc/panes.md index b5f062a..2fa82ee 100644 --- a/doc/panes.md +++ b/doc/panes.md @@ -169,6 +169,9 @@ chart.resetPaneHeights(); // back to the standard layout either reaches its limit: `ChartStyle.minPaneHeight` and `maxPaneHeight` for the volume and the panes, a 60 px floor for the candles, which have no ceiling because they are what is left. +- However small `minPaneHeight` is set, a pane or the volume pane is held at 32 + px or more when dragged or maximized around, so there is always an edge or + line left to pull it back out. - The chart owns the layout and keeps it as proportions, so it holds through a resize. It starts from `paneRatios` when you give some, or from the standard heights, and passing a different `paneRatios` later starts it over from that. diff --git a/lib/src/chart_style.dart b/lib/src/chart_style.dart index 3df4ae3..cb59317 100644 --- a/lib/src/chart_style.dart +++ b/lib/src/chart_style.dart @@ -645,7 +645,10 @@ class ChartStyle { /// sized to. Every other bar is drawn relative to that one. final double profileWidth; - /// Shortest an indicator pane may be dragged. + /// Shortest an indicator pane, or the volume pane, may be dragged. + /// + /// Held at 32 or more whatever is set here, so a pane can never be dragged so + /// small that its edge cannot be grabbed to make it bigger again. final double minPaneHeight; /// Tallest an indicator pane may be dragged. diff --git a/lib/src/k_chart_widget.dart b/lib/src/k_chart_widget.dart index 38d275a..983c44a 100644 --- a/lib/src/k_chart_widget.dart +++ b/lib/src/k_chart_widget.dart @@ -2000,7 +2000,7 @@ class _KChartWidgetState extends State mHeight.isFinite && mHeight > 0) { // Everything else at its smallest, the volume pane gets what is left. - final small = widget.chartStyle.minPaneHeight; + final small = _minPaneHeight; return math.max( small, mHeight - @@ -2017,6 +2017,16 @@ class _KChartWidgetState extends State return height != null && height.isFinite && height > 0 ? height : null; } + /// The least a pane or the volume pane may be dragged to, or maximized + /// around: `ChartStyle.minPaneHeight`, but never so little that the edge or + /// line left to drag it back out cannot be reached. The drag zones either + /// side of a part are 10 px each, so 32 leaves a gap between them. + double get _minPaneHeight => + math.max(widget.chartStyle.minPaneHeight, _smallestGrabbablePane); + + /// Shortest a part may be and still have its edges grabbed one at a time. + static const double _smallestGrabbablePane = 32; + /// A height worth drawing: finite and above zero. static bool _usableHeight(double height) => height.isFinite && height > 0; @@ -2070,7 +2080,7 @@ class _KChartWidgetState extends State _hostPaneHeightsStale = false; if (_volumeMaximized && !widget.volHidden) { - final small = widget.chartStyle.minPaneHeight; + final small = _minPaneHeight; return List.filled(owners.length, small); } final maximized = _maximizedPaneIndex; @@ -2121,7 +2131,7 @@ class _KChartWidgetState extends State /// [_paneHeights] with the maximized pane given everything the others leave, /// and the others at their smallest. List _heightsWithMaximized(int index) { - final small = widget.chartStyle.minPaneHeight; + final small = _minPaneHeight; final room = _paneRoom; return [ for (var i = 0; i < _paneHeights.length; i++) @@ -2138,7 +2148,7 @@ class _KChartWidgetState extends State List _cutBackToFit(List heights) { final room = _paneRoom; if (room == null) return heights; - final ceiling = math.max(room, widget.chartStyle.minPaneHeight); + final ceiling = math.max(room, _minPaneHeight); return [for (final height in heights) math.min(height, ceiling)]; } @@ -2943,7 +2953,7 @@ class _KChartWidgetState extends State // A pane the host made taller than the usual ceiling keeps its height // under the finger rather than snapping down to it. heights[index] = (heights[index] + delta).clamp( - widget.chartStyle.minPaneHeight, + _minPaneHeight, math.max(widget.chartStyle.maxPaneHeight, heights[index]), ); // A host that owns the heights hears about the drag and passes it back. @@ -2956,9 +2966,8 @@ class _KChartWidgetState extends State /// Smallest height part [part] may take in [PaneSizeMode.custom]: the candles /// have their own floor, the volume and the panes share one. - double _smallestPart(int part) => part == 0 - ? BaseChartPainter.minMainHeight - : widget.chartStyle.minPaneHeight; + double _smallestPart(int part) => + part == 0 ? BaseChartPainter.minMainHeight : _minPaneHeight; /// Largest height part [part] may take in [PaneSizeMode.custom]; the candles /// have no ceiling, as they are what is left. @@ -3085,9 +3094,7 @@ class _KChartWidgetState extends State final other = slot + 1 < ratios.length ? slot + 1 : slot - 1; double floorOf(int part) => - (part == 0 - ? BaseChartPainter.minMainHeight - : widget.chartStyle.minPaneHeight) / + (part == 0 ? BaseChartPainter.minMainHeight : _minPaneHeight) / split.unit; // Pixels moved, in proportion units, held so neither side drops below its diff --git a/test/pane_height_control_test.dart b/test/pane_height_control_test.dart index 64d5c09..aff745d 100644 --- a/test/pane_height_control_test.dart +++ b/test/pane_height_control_test.dart @@ -26,6 +26,7 @@ Widget _chart({ ValueChanged>? onRatios, bool volHidden = false, PaneSizeMode? mode, + ChartStyle? style, }) => MaterialApp( home: Scaffold( body: SizedBox( @@ -47,6 +48,7 @@ Widget _chart({ mode ?? (ratios != null ? PaneSizeMode.ratios : PaneSizeMode.heights), paneRatios: ratios, + chartStyle: style ?? ChartStyle(), onPaneRatiosChanged: onRatios, volHidden: volHidden, ), @@ -870,4 +872,74 @@ void main() { expect(controller.isEditingPanes, isFalse); }); }); + + group('smallest height', () { + // Asked for no minimum at all, the chart still keeps one that can be + // grabbed again. + final noMinimum = ChartStyle(minPaneHeight: 0); + + Finder dividers() => find.byWidgetPredicate( + (w) => w is MouseRegion && w.cursor == SystemMouseCursors.resizeUpDown, + ); + + testWidgets('a line cannot squeeze a pane past what can be grabbed', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + mode: PaneSizeMode.custom, + style: noMinimum, + ), + ); + controller.editPanes(); + await tester.pump(); + + await tester.drag(dividers().at(2), const Offset(0, -300)); + await tester.pump(); + expect(controller.paneHeights[0], closeTo(32, 0.5)); + + // And the line is still there to pull it back out. + expect(dividers(), findsNWidgets(3)); + await tester.drag(dividers().at(2), const Offset(0, 40)); + await tester.pump(); + expect(controller.paneHeights[0], closeTo(72, 0.5)); + }); + + testWidgets('a dragged edge stops at the same floor', (tester) async { + final reported = >[]; + await tester.pumpWidget( + _chart(resizable: true, onChanged: reported.add, style: noMinimum), + ); + final edge = _painterOf(tester).mSecondaryRectList.first.mRect.bottom; + final box = tester.getTopLeft(find.byType(KChartWidget)); + + final gesture = await tester.startGesture(box + Offset(200, edge)); + for (var i = 0; i < 12; i++) { + await gesture.moveBy(const Offset(0, -10)); + } + await gesture.up(); + await tester.pump(); + + expect(reported.last[0], closeTo(32, 0.5)); + }); + + testWidgets('maximizing leaves the other panes at that floor', ( + tester, + ) async { + final controller = KChartController(); + await tester.pumpWidget( + _chart( + controller: controller, + indicators: [RsiIndicator(), MacdIndicator()], + style: noMinimum, + ), + ); + controller.maximizePane(0); + await tester.pump(); + expect(controller.paneHeights[1], closeTo(32, 0.5)); + }); + }); } From d6605c3ababe56d047842b6ded266b79bccbdaca Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 03:47:49 +0330 Subject: [PATCH 11/12] ci: publish to pub.dev when a release is created from main A v tag starts the publish workflow, which checks the tag is on main, matches the pubspec version and has a CHANGELOG entry before publishing through pub.dev's automated publishing. Releasing is documented in CONTRIBUTING.md. --- .github/workflows/publish.yml | 83 +++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 14 ++++++ 2 files changed, 97 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9ee5504 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,83 @@ +# Publishes the package to pub.dev when a release is created. +# +# Creating a GitHub release from main tags it, and the tag is what starts this: +# `v3.1.0` publishes version 3.1.0. Nothing is published unless the tag is on +# main, matches the version in pubspec.yaml and has its CHANGELOG entry. +# +# One-off setup on pub.dev (package Admin tab > Automated publishing): +# - enable publishing from GitHub Actions for CtrlAltDevelop/ohlcv_chart +# - tag pattern: v{{version}} +# - require the GitHub Actions environment: pub.dev +# and create an environment named `pub.dev` in the repository settings, with +# a required reviewer if every publish should be approved by hand. + +name: Publish + +on: + push: + tags: ["v[0-9]+.[0-9]+.[0-9]+*"] + +permissions: {} + +jobs: + verify: + name: Verify the release + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: The tag is on main + run: | + if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then + echo "::error::$GITHUB_REF_NAME is not on main; release from main." + exit 1 + fi + + - name: The tag matches the pubspec version + run: | + version=$(sed -n 's/^version: *//p' pubspec.yaml | tr -d '\r') + if [ "v$version" != "$GITHUB_REF_NAME" ]; then + echo "::error::Tag $GITHUB_REF_NAME does not match pubspec version $version." + exit 1 + fi + + - name: The CHANGELOG has an entry for it + run: | + version="${GITHUB_REF_NAME#v}" + if ! grep -q "^## $version\b" CHANGELOG.md; then + echo "::error::CHANGELOG.md has no '## $version' entry." + exit 1 + fi + + publish: + name: Publish to pub.dev + needs: verify + runs-on: ubuntu-latest + environment: pub.dev + permissions: + contents: read + # Lets pub.dev trust this run without a stored secret. + id-token: write + steps: + - uses: actions/checkout@v7 + + # Sets up the credentials pub.dev issues to this run; the Flutter + # setup below brings the SDK `flutter pub publish` needs. + - uses: dart-lang/setup-dart@v1 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - run: flutter pub get + + - name: Dry run + run: flutter pub publish --dry-run + + - name: Publish + run: flutter pub publish --force diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f35deda..7652858 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,6 +64,20 @@ their shape rather than inventing a new one. page too. A new top-level feature gets a new page, linked from both [`README.md`](README.md) and [`doc/README.md`](doc/README.md). +## Releasing + +Maintainers only. Releases are published to pub.dev automatically: + +1. Merge `develop` into `main` with the version bumped in `pubspec.yaml` and + the `## Unreleased` heading in `CHANGELOG.md` renamed to + `## - `. +2. Create a GitHub release from `main` with the tag `v`. + +The tag starts [`publish.yml`](.github/workflows/publish.yml), which checks the +tag is on `main`, matches the pubspec version and has its changelog entry, and +then publishes. It needs automated publishing enabled on pub.dev for this +repository, with the tag pattern `v{{version}}` and the `pub.dev` environment. + ## Reporting a bug or requesting a feature Use the issue templates — they ask for the couple of things that are always From 47f8c4125c995d0f2ecf53bf9889783f0cd7086a Mon Sep 17 00:00:00 2001 From: Mohammad Zarif Date: Tue, 6 Oct 2026 04:04:22 +0330 Subject: [PATCH 12/12] chore: release 3.1.0 Bump the version, date the changelog and group it into Added and Changed, and point the install snippet at 3.1.0. --- CHANGELOG.md | 63 ++++++++++++++++++++++++++++------------------------ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 256a6fd..50858f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,31 @@ -## Unreleased +## 3.1.0 - 2026-10-06 ### Added +- **Sizing the panes from your code.** `KChartWidget.paneHeights` sets the + indicator panes' heights and `volumeHeight` the volume pane's. Heights are + used as given — past `ChartStyle.maxPaneHeight` if asked — and cut back only + when a pane would squeeze the candles out of the box. Null leaves the heights + to the chart, as before. While a list is given the host owns the heights: a + drag or a controller call is reported through `onPaneHeightsChanged`, and the + pane moves when the list is passed back. +- **Maximizing a pane.** `KChartController.maximizePane`, `maximizeVolume`, + `restorePanes` and `toggleMaximizePane` stretch one pane over the chart, the + candles keeping a strip above it, for a "maximize this indicator" button. The + stretch follows the chart's size and its own indicator through a reorder. + `setPaneHeight`, `resetPaneHeights`, `paneHeights`, `maximizedPane` and + `isVolumeMaximized` complete the set. +- **`KChartWidget.paneSizeMode`** chooses how the chart's parts are sized. + `PaneSizeMode.heights` is the default and the behaviour so far. + `PaneSizeMode.ratios` splits the height by `paneRatios` — the candles, the + volume pane, then each indicator pane, so `[3, 1, 2]` is half, a sixth and a + third — and holds the split as the chart is resized. `PaneSizeMode.custom` + lets the user lay the chart out: `KChartController.editPanes` shows a line + between each two parts to drag between the smallest and largest height each + may take, the layout is kept as proportions, and `onPaneRatiosChanged` + reports it for saving. `finishEditingPanes`, `toggleEditPanes` and + `isEditingPanes` complete the set. Both modes need a bounded height and no + `mBaseHeight`. - **`ChartStyle.gridColumnMode`** chooses where the grid's vertical lines go. `GridColumnMode.dateTicks` (the default, and the behaviour so far) rules a line wherever a candle crosses a time bucket; with only a few candles in view that @@ -14,37 +38,18 @@ - **`ChartStyle.gridDashPattern`** dashes the grid lines with a list of on/off lengths, e.g. `[4, 3]`. Null is solid; an empty list or a non-positive length is read as solid. -- The example app has switches for all three, and a chip per pane that maximizes it. -- **`KChartWidget.paneHeights`** sizes the indicator panes from the host, and - `KChartWidget.volumeHeight` the volume pane. Heights are used as given — past - `ChartStyle.maxPaneHeight` if asked — and cut back only when a pane would - squeeze the candles out of the box. Null leaves the heights to the chart, as - before. While a list is given the host owns the heights: a drag or a controller - call is reported through `onPaneHeightsChanged` and the pane moves when the - list is passed back. -- **`KChartController.maximizePane`, `maximizeVolume`, `restorePanes`, - `toggleMaximizePane`, - `setPaneHeight`, `resetPaneHeights`, `paneHeights` and `maximizedPane`** drive - the same from code, for a "maximize this indicator" button. A maximized pane - follows the chart's size and its own indicator through a reorder. -- **`KChartWidget.paneSizeMode`** chooses how the chart's parts are sized. - `PaneSizeMode.heights` is the default and the behaviour so far. `ratios` splits - the height by `paneRatios` — the candles, the volume pane, then each indicator - pane, so `[3, 1, 2]` is half, a sixth and a third — and holds the split as the - chart is resized. `custom` lets the user lay the chart out: `KChartController. - editPanes` shows a line between each two parts to drag between the smallest and - largest height each may take, the layout is kept as proportions, and - `onPaneRatiosChanged` reports it for saving. Both need a bounded height and no - `mBaseHeight`; the example app has a switch for them. -- `KChartController.editPanes`, `finishEditingPanes`, `toggleEditPanes` and - `isEditingPanes` drive the editing; `KChartHost` has two more members for it. +- The example app has switches for the grid options, a sizing chooser for the + panes, an Edit layout chip, and a chip to maximize each pane and the volume. + +### Changed + +- Dragged pane heights move with their pane when `onReorderPane` reorders the + indicators, instead of staying at their position. - `ChartStyle.minPaneHeight` is held at 32 or more when a pane is dragged or maximized around, so a pane can never be made so small that its edge cannot be grabbed to make it bigger again. -- Dragged pane heights now move with their pane when `onReorderPane` reorders - the indicators, instead of staying at their position. -- `KChartHost` has nine new members for the above; only an app that implements - it itself (a test double) needs to add them. +- `KChartHost` has nine new members for the above. Only an app that implements + it itself, such as a test double, needs to add them. ## 3.0.0 - 2026-09-19 diff --git a/README.md b/README.md index 0cf1866..e18c77e 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ indicator and drawing tool is included; there is no paid tier. ```yaml dependencies: material_ui: ">=1.0.0 <2.0.0" - ohlcv_chart: ">=3.0.0 <4.0.0" + ohlcv_chart: ">=3.1.0 <4.0.0" ``` Every widget here is built on the `material_ui` package rather than the diff --git a/pubspec.yaml b/pubspec.yaml index 23b4020..3a4b785 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: >- Candlestick charts for Flutter with 31 indicators, 29 drawing tools and 39 more chart widgets: depth, order flow, series, pie, treemap, sankey. Pure CustomPainter, no WebView. -version: 3.0.0 +version: 3.1.0 homepage: https://github.com/CtrlAltDevelop repository: https://github.com/CtrlAltDevelop/ohlcv_chart issue_tracker: https://github.com/CtrlAltDevelop/ohlcv_chart/issues