Skip to content

Commit b0d328d

Browse files
Merge pull request #11 from CtrlAltDevelop/develop
release: 3.1.0
2 parents d2aa56e + 47f8c41 commit b0d328d

32 files changed

Lines changed: 2535 additions & 43 deletions

‎.github/workflows/publish.yml‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Publishes the package to pub.dev when a release is created.
2+
#
3+
# Creating a GitHub release from main tags it, and the tag is what starts this:
4+
# `v3.1.0` publishes version 3.1.0. Nothing is published unless the tag is on
5+
# main, matches the version in pubspec.yaml and has its CHANGELOG entry.
6+
#
7+
# One-off setup on pub.dev (package Admin tab > Automated publishing):
8+
# - enable publishing from GitHub Actions for CtrlAltDevelop/ohlcv_chart
9+
# - tag pattern: v{{version}}
10+
# - require the GitHub Actions environment: pub.dev
11+
# and create an environment named `pub.dev` in the repository settings, with
12+
# a required reviewer if every publish should be approved by hand.
13+
14+
name: Publish
15+
16+
on:
17+
push:
18+
tags: ["v[0-9]+.[0-9]+.[0-9]+*"]
19+
20+
permissions: {}
21+
22+
jobs:
23+
verify:
24+
name: Verify the release
25+
runs-on: ubuntu-latest
26+
permissions:
27+
contents: read
28+
steps:
29+
- uses: actions/checkout@v7
30+
with:
31+
fetch-depth: 0
32+
33+
- name: The tag is on main
34+
run: |
35+
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
36+
echo "::error::$GITHUB_REF_NAME is not on main; release from main."
37+
exit 1
38+
fi
39+
40+
- name: The tag matches the pubspec version
41+
run: |
42+
version=$(sed -n 's/^version: *//p' pubspec.yaml | tr -d '\r')
43+
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
44+
echo "::error::Tag $GITHUB_REF_NAME does not match pubspec version $version."
45+
exit 1
46+
fi
47+
48+
- name: The CHANGELOG has an entry for it
49+
run: |
50+
version="${GITHUB_REF_NAME#v}"
51+
if ! grep -q "^## $version\b" CHANGELOG.md; then
52+
echo "::error::CHANGELOG.md has no '## $version' entry."
53+
exit 1
54+
fi
55+
56+
publish:
57+
name: Publish to pub.dev
58+
needs: verify
59+
runs-on: ubuntu-latest
60+
environment: pub.dev
61+
permissions:
62+
contents: read
63+
# Lets pub.dev trust this run without a stored secret.
64+
id-token: write
65+
steps:
66+
- uses: actions/checkout@v7
67+
68+
# Sets up the credentials pub.dev issues to this run; the Flutter
69+
# setup below brings the SDK `flutter pub publish` needs.
70+
- uses: dart-lang/setup-dart@v1
71+
72+
- uses: subosito/flutter-action@v2
73+
with:
74+
channel: stable
75+
cache: true
76+
77+
- run: flutter pub get
78+
79+
- name: Dry run
80+
run: flutter pub publish --dry-run
81+
82+
- name: Publish
83+
run: flutter pub publish --force

‎CHANGELOG.md‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,56 @@
1+
## 3.1.0 - 2026-10-06
2+
3+
### Added
4+
5+
- **Sizing the panes from your code.** `KChartWidget.paneHeights` sets the
6+
indicator panes' heights and `volumeHeight` the volume pane's. Heights are
7+
used as given — past `ChartStyle.maxPaneHeight` if asked — and cut back only
8+
when a pane would squeeze the candles out of the box. Null leaves the heights
9+
to the chart, as before. While a list is given the host owns the heights: a
10+
drag or a controller call is reported through `onPaneHeightsChanged`, and the
11+
pane moves when the list is passed back.
12+
- **Maximizing a pane.** `KChartController.maximizePane`, `maximizeVolume`,
13+
`restorePanes` and `toggleMaximizePane` stretch one pane over the chart, the
14+
candles keeping a strip above it, for a "maximize this indicator" button. The
15+
stretch follows the chart's size and its own indicator through a reorder.
16+
`setPaneHeight`, `resetPaneHeights`, `paneHeights`, `maximizedPane` and
17+
`isVolumeMaximized` complete the set.
18+
- **`KChartWidget.paneSizeMode`** chooses how the chart's parts are sized.
19+
`PaneSizeMode.heights` is the default and the behaviour so far.
20+
`PaneSizeMode.ratios` splits the height by `paneRatios` — the candles, the
21+
volume pane, then each indicator pane, so `[3, 1, 2]` is half, a sixth and a
22+
third — and holds the split as the chart is resized. `PaneSizeMode.custom`
23+
lets the user lay the chart out: `KChartController.editPanes` shows a line
24+
between each two parts to drag between the smallest and largest height each
25+
may take, the layout is kept as proportions, and `onPaneRatiosChanged`
26+
reports it for saving. `finishEditingPanes`, `toggleEditPanes` and
27+
`isEditingPanes` complete the set. Both modes need a bounded height and no
28+
`mBaseHeight`.
29+
- **`ChartStyle.gridColumnMode`** chooses where the grid's vertical lines go.
30+
`GridColumnMode.dateTicks` (the default, and the behaviour so far) rules a line
31+
wherever a candle crosses a time bucket; with only a few candles in view that
32+
can leave the lines bunched together. `GridColumnMode.evenlySpaced` divides the
33+
chart width into `gridColumns` equal bands instead, so the grid spans the whole
34+
chart however sparse the data. The date labels keep their round times.
35+
- **`ChartStyle.showGridRows` and `showGridColumns`** switch the horizontal and
36+
vertical grid lines on and off independently. `hideGrid` still hides both, and
37+
the pane borders stay either way.
38+
- **`ChartStyle.gridDashPattern`** dashes the grid lines with a list of on/off
39+
lengths, e.g. `[4, 3]`. Null is solid; an empty list or a non-positive length
40+
is read as solid.
41+
- The example app has switches for the grid options, a sizing chooser for the
42+
panes, an Edit layout chip, and a chip to maximize each pane and the volume.
43+
44+
### Changed
45+
46+
- Dragged pane heights move with their pane when `onReorderPane` reorders the
47+
indicators, instead of staying at their position.
48+
- `ChartStyle.minPaneHeight` is held at 32 or more when a pane is dragged or
49+
maximized around, so a pane can never be made so small that its edge cannot be
50+
grabbed to make it bigger again.
51+
- `KChartHost` has nine new members for the above. Only an app that implements
52+
it itself, such as a test double, needs to add them.
53+
154
## 3.0.0 - 2026-09-19
255

356
### Breaking

‎CONTRIBUTING.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ their shape rather than inventing a new one.
6464
page too. A new top-level feature gets a new page, linked from both
6565
[`README.md`](README.md) and [`doc/README.md`](doc/README.md).
6666

67+
## Releasing
68+
69+
Maintainers only. Releases are published to pub.dev automatically:
70+
71+
1. Merge `develop` into `main` with the version bumped in `pubspec.yaml` and
72+
the `## Unreleased` heading in `CHANGELOG.md` renamed to
73+
`## <version> - <date>`.
74+
2. Create a GitHub release from `main` with the tag `v<version>`.
75+
76+
The tag starts [`publish.yml`](.github/workflows/publish.yml), which checks the
77+
tag is on `main`, matches the pubspec version and has its changelog entry, and
78+
then publishes. It needs automated publishing enabled on pub.dev for this
79+
repository, with the tag pattern `v{{version}}` and the `pub.dev` environment.
80+
6781
## Reporting a bug or requesting a feature
6882

6983
Use the issue templates — they ask for the couple of things that are always

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ indicator and drawing tool is included; there is no paid tier.
387387
```yaml
388388
dependencies:
389389
material_ui: ">=1.0.0 <2.0.0"
390-
ohlcv_chart: ">=3.0.0 <4.0.0"
390+
ohlcv_chart: ">=3.1.0 <4.0.0"
391391
```
392392
393393
Every widget here is built on the `material_ui` package rather than the

‎doc/date-axis.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,35 @@ Like the price axis, the date axis selects round values before placing labels.
1616
`ChartStyle.gridColumns` controls label density, in the same way as `gridRows`
1717
on the price axis.
1818

19+
By default the grid's vertical lines rule wherever a candle crosses a time
20+
bucket (`GridColumnMode.dateTicks`), so each line lines up with a round time.
21+
With only a handful of candles in view — a sparse intraday window, say —
22+
there may be only one or two such crossings, leaving most of the chart
23+
without a line. Set `ChartStyle.gridColumnMode` to
24+
`GridColumnMode.evenlySpaced` to divide the chart width into `gridColumns`
25+
equal bands instead, so the grid always spans the chart regardless of candle
26+
count — at the cost of the lines no longer landing on round times:
27+
28+
```dart
29+
ChartStyle(
30+
gridColumnMode: GridColumnMode.evenlySpaced,
31+
);
32+
```
33+
34+
The grid can also be trimmed or dashed, each part on its own:
35+
36+
```dart
37+
ChartStyle(
38+
showGridRows: false, // vertical lines only
39+
showGridColumns: true,
40+
gridDashPattern: [4, 3], // 4px dash, 3px gap; null is solid
41+
);
42+
```
43+
44+
`hideGrid` on the widget still hides the whole grid. The date labels keep
45+
their round times in either column mode, so with `evenlySpaced` a line and a
46+
label need not meet.
47+
1948
To control formatting, use `ChartStyle.dateTimeFormat` for a fixed pattern, or
2049
`dateFormatter` for full control. `dateFormatter` receives each candle and a
2150
flag indicating whether the long form (used by the crosshair) is required:

‎doc/panes.md‎

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,157 @@ KChartWidget(
2929

3030
## Height and order
3131

32-
Pane heights are managed by the chart, constrained between
33-
`ChartStyle.minPaneHeight` and `maxPaneHeight`, and reset when the set of panes
34-
changes.
32+
By default pane heights are managed by the chart: 100 each, or whatever the user
33+
dragged them to, constrained between `ChartStyle.minPaneHeight` and
34+
`maxPaneHeight`, and reset when panes are added or removed. A reorder carries
35+
each height along with its pane.
3536

3637
Pane order is owned by your indicator list. The chart reports the move through
3738
`onReorderPane`, and your code applies it.
3839

40+
## Sizing panes from your code
41+
42+
Two ways to set heights from outside — a "maximize this indicator" button is
43+
the usual reason.
44+
45+
**`KChartController`** takes commands:
46+
47+
```dart
48+
chart.maximizePane(1); // pane 1 fills the chart, candles shrink to a strip
49+
chart.restorePanes(); // …and back
50+
chart.toggleMaximizePane(1); // one button for both
51+
chart.maximizeVolume(); // the volume pane instead
52+
chart.setPaneHeight(0, 220); // any single pane, any positive height
53+
chart.resetPaneHeights(); // every pane back to the standard height
54+
chart.paneHeights; // what is drawn now
55+
```
56+
57+
![Three panes at the standard height](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-default.jpg)
58+
![The RSI pane maximized](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-maximized-rsi.jpg)
59+
![The OBV pane maximized](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-maximized-obv.jpg)
60+
61+
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
62+
reorder; dragging any pane lets go of it. It needs the chart to size its own
63+
candle area, so it has no effect when `mBaseHeight` is set.
64+
65+
**`KChartWidget.paneHeights`** is the same thing as state:
66+
67+
```dart
68+
KChartWidget(
69+
candles,
70+
ChartColors(),
71+
indicators: indicators,
72+
paneHeights: maximized ? [60, 500] : null, // null: the chart's own layout
73+
volumeHeight: 80, // the volume pane, default 60
74+
onPaneHeightsChanged: (heights) => setState(() => saved = heights),
75+
);
76+
```
77+
78+
- Heights set from code are used as given, not held to `minPaneHeight` and
79+
`maxPaneHeight`, so a pane can be taller than a user could drag it. A height
80+
too big for the box is cut back so the candles keep a strip above it.
81+
- A pane missing from a shorter list, or given a height that is not a positive
82+
number, gets the standard height.
83+
- While a list is given you own the heights, like a controlled text field. A
84+
drag (with `resizablePanes` on) or `setPaneHeight` does not move a pane by
85+
itself: it is reported through `onPaneHeightsChanged`, and the pane moves when
86+
you pass the new list back. Pass `null` to give the heights back to the chart.
87+
Maximizing through the controller still works over a list, and lets go of
88+
whenever a height changes.
89+
- Panes in `paneHeights` are matched by position. When `onReorderPane` moves
90+
one, move its height with it.
91+
3992
`ChartStyle.paneResizeTolerance` and `paneGrabHeight` set the size of the
4093
resize and reorder hit areas.
4194

95+
## Sizing modes
96+
97+
`paneSizeMode` chooses how the heights of the candles, the volume pane and the
98+
indicator panes are worked out:
99+
100+
| Mode | Heights come from | Who changes them |
101+
|---|---|---|
102+
| `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` |
103+
| `PaneSizeMode.ratios` | proportions in `paneRatios` | you |
104+
| `PaneSizeMode.custom` | the user's own layout | the user, dragging lines in edit mode |
105+
106+
`ratios` and `custom` need the box to bound the chart's height and `mBaseHeight`
107+
to be left off; otherwise the pixel heights are used.
108+
109+
### Proportions
110+
111+
```dart
112+
KChartWidget(
113+
candles,
114+
ChartColors(),
115+
indicators: [RsiIndicator()],
116+
paneSizeMode: PaneSizeMode.ratios,
117+
paneRatios: [3, 1, 2], // 6 units: candles 3, volume 1, RSI 2
118+
);
119+
```
120+
121+
![Candles 3, volume 1, MACD 2 and RSI 2](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-ratios.jpg)
122+
123+
- The list runs top to bottom: the candles, the volume pane, then each indicator
124+
pane. The height left after the legend rows above the candles is split by
125+
those units, and the split holds as the box is resized.
126+
- With `volHidden` the volume's number is left out, so the list is one shorter.
127+
- A part missing from a shorter list, or given a number that is not above zero,
128+
counts as 1. Without `paneRatios` the mode has nothing to split by, and the
129+
pixel heights are used.
130+
- You own the proportions, as with `paneHeights`. With `resizablePanes` on, a
131+
drag moves room between the two parts either side of the edge — the last pane
132+
takes from the part above — and `onPaneRatiosChanged` reports the new list,
133+
keeping its total. Pass it back for the drag to take effect:
134+
135+
```dart
136+
paneRatios: ratios,
137+
resizablePanes: true,
138+
onPaneRatiosChanged: (next) => setState(() => ratios = next),
139+
```
140+
141+
### Custom layout
142+
143+
The user lays the chart out themselves. Put it in `custom` mode and turn editing
144+
on from the controller; a line appears between each two parts, the candles and
145+
volume included, to drag up or down:
146+
147+
```dart
148+
final chart = KChartController();
149+
150+
KChartWidget(
151+
candles,
152+
ChartColors(),
153+
controller: chart,
154+
indicators: [RsiIndicator()],
155+
paneSizeMode: PaneSizeMode.custom,
156+
onPaneRatiosChanged: (ratios) => saved = ratios, // optional
157+
);
158+
159+
chart.editPanes(); // show the lines
160+
chart.finishEditingPanes(); // put them away, keeping the layout
161+
chart.toggleEditPanes(); // one button for both
162+
chart.resetPaneHeights(); // back to the standard layout
163+
```
164+
165+
![Edit mode: a line between each two parts](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-custom-edit.jpg)
166+
![The line between MACD and RSI dragged up](https://raw.githubusercontent.com/CtrlAltDevelop/ohlcv_chart/main/screenshots/panes-custom-dragged.jpg)
167+
168+
- A line moves room between the two parts either side of it, and stops where
169+
either reaches its limit: `ChartStyle.minPaneHeight` and `maxPaneHeight` for
170+
the volume and the panes, a 60 px floor for the candles, which have no
171+
ceiling because they are what is left.
172+
- However small `minPaneHeight` is set, a pane or the volume pane is held at 32
173+
px or more when dragged or maximized around, so there is always an edge or
174+
line left to pull it back out.
175+
- The chart owns the layout and keeps it as proportions, so it holds through a
176+
resize. It starts from `paneRatios` when you give some, or from the standard
177+
heights, and passing a different `paneRatios` later starts it over from that.
178+
- `onPaneRatiosChanged` reports every change in `paneRatios` order, which is
179+
what to save and pass back as `paneRatios` next time.
180+
- The lines are not shown while a pane is maximized, and `resizablePanes` has no
181+
effect in this mode; the lines replace it.
182+
42183
---
43184

44185
[← All docs](README.md) · [Package README](../README.md)

‎example/lib/src/chart_page.dart‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ class _Chart extends StatelessWidget {
128128
crosshairOnHover: state.crosshairOnHover,
129129
timeZoneOffset: state.timeZoneOffset,
130130
resizablePanes: state.resizablePanes,
131+
paneSizeMode: state.paneMode,
132+
paneRatios: state.paneRatios,
133+
onPaneRatiosChanged: (ratios) =>
134+
state.update(() => state.paneRatios = ratios),
131135
reorderablePanes: state.reorderablePanes,
132136
onReorderPane: state.reorderPane,
133137
selectAfterDrawing: !state.keepToolArmed,

0 commit comments

Comments
 (0)