plotlywaterfall 0.9.3__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. plotlywaterfall-1.1.0/CHANGELOG.md +119 -0
  2. {plotlywaterfall-0.9.3 → plotlywaterfall-1.1.0}/LICENSE +3 -1
  3. plotlywaterfall-1.1.0/PKG-INFO +235 -0
  4. plotlywaterfall-1.1.0/README.md +203 -0
  5. plotlywaterfall-1.1.0/pyproject.toml +179 -0
  6. plotlywaterfall-1.1.0/src/plotlywaterfall/__about__.py +1 -0
  7. plotlywaterfall-1.1.0/src/plotlywaterfall/__init__.py +6 -0
  8. plotlywaterfall-1.1.0/src/plotlywaterfall/_coloring.py +204 -0
  9. plotlywaterfall-1.1.0/src/plotlywaterfall/_format.py +84 -0
  10. plotlywaterfall-1.1.0/src/plotlywaterfall/waterfall.py +798 -0
  11. plotlywaterfall-1.1.0/tests/test_basic.py +354 -0
  12. plotlywaterfall-1.1.0/tests/test_colors.py +266 -0
  13. plotlywaterfall-1.1.0/tests/test_function.py +69 -0
  14. plotlywaterfall-1.1.0/tests/test_labels.py +199 -0
  15. plotlywaterfall-1.1.0/tests/test_mixed_signs.py +200 -0
  16. plotlywaterfall-1.1.0/tests/test_order_and_format.py +209 -0
  17. plotlywaterfall-1.1.0/tests/test_readme.py +25 -0
  18. plotlywaterfall-1.1.0/tests/test_regressions.py +161 -0
  19. plotlywaterfall-0.9.3/CHANGELOG.md +0 -10
  20. plotlywaterfall-0.9.3/PKG-INFO +0 -85
  21. plotlywaterfall-0.9.3/README.md +0 -63
  22. plotlywaterfall-0.9.3/examples/example.png +0 -0
  23. plotlywaterfall-0.9.3/examples/example_df.png +0 -0
  24. plotlywaterfall-0.9.3/examples/examples.ipynb +0 -11381
  25. plotlywaterfall-0.9.3/pyproject.toml +0 -99
  26. plotlywaterfall-0.9.3/requirements.txt +0 -2
  27. plotlywaterfall-0.9.3/src/plotlywaterfall/__about__.py +0 -1
  28. plotlywaterfall-0.9.3/src/plotlywaterfall/waterfall.py +0 -211
  29. plotlywaterfall-0.9.3/tests/test_basic.py +0 -264
  30. {plotlywaterfall-0.9.3 → plotlywaterfall-1.1.0}/.gitignore +0 -0
  31. /plotlywaterfall-0.9.3/src/plotlywaterfall/__init__.py → /plotlywaterfall-1.1.0/src/plotlywaterfall/py.typed +0 -0
@@ -0,0 +1,119 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [1.1.0] - 2026-11-05
6
+
7
+ ### Fixed
8
+ - **Wrong levels with groups whose rows are in a different order**: running levels were accumulated
9
+ in each group's own row order instead of the x-axis order, so such a group's bars started at the
10
+ wrong height. They now always follow the x-axis.
11
+ - **Dates on the x-axis** were labelled `Timestamp('2026-01-31 00:00:00')`; they are now shown as
12
+ `Jan 2026` (dates in different months) or `2026-01-31`, in tick labels and hover.
13
+ - **The order of a pandas `Categorical` x column** was ignored; it now sets the order of the bars
14
+ (and of groups and categories).
15
+
16
+ ### Added
17
+ - `category_orders` (as in plotly express) to set the order of x, group and category values;
18
+ the colour palette is assigned in that order.
19
+ - `x_format`: strftime pattern for date x-values or a function for any x-values.
20
+ - `number_unit` (`"k"`, `"M"`, `"bn"` or `"auto"` = one unit for the whole chart) and
21
+ `negative_style="parentheses"` for accounting-style labels such as `($0.73M)`.
22
+
23
+ ## [1.0.0] - 2026-11-05
24
+
25
+ First stable release: a one-call `waterfall()` function, mixed signs within a bar, a plotly-express
26
+ style colour API, value labels and hover texts, type hints, and an
27
+ [example gallery](https://docdru.github.io/plotlywaterfall/).
28
+
29
+ ### Upgrading from 0.9
30
+ - **`colors` was removed.** Use `color` and `color_discrete_map`:
31
+
32
+ | 0.9 `colors=` | 1.0 |
33
+ |---|---|
34
+ | `{"one": "red", "two": "blue"}` (per category) | `color_discrete_map={"one": "red", "two": "blue"}` |
35
+ | `{"Group1": "red", "Group2": "blue"}` (per group) | `color="group", color_discrete_map={"Group1": "red", "Group2": "blue"}` (with your group column name) |
36
+ | `{"Group1": {"one": "red"}}` (per group and category) | `color_discrete_map={("Group1", "one"): "red"}` |
37
+
38
+ - **Charts look different by default:** bars carry value labels (`value_labels=None` turns them
39
+ off), charts without category and group are coloured by direction (green / red / grey), a category
40
+ keeps its colour across groups, and the y-axis is titled with the y column.
41
+ - **Mixed signs within one bar are drawn** as sub-steps instead of raising `NotImplementedError`;
42
+ `mixed_signs="error"` restores the old behaviour.
43
+ - **Python 3.11 or newer** is required.
44
+
45
+ ### Added
46
+ - **Type hints throughout** and a `py.typed` marker, so type checkers (mypy, pyright) use the
47
+ package's annotations.
48
+ - Options with a fixed set of values (`total_type`, `mixed_signs`, `step_order`, `value_labels`) are
49
+ typed as `Literal`s, so editors suggest the valid values and type checkers catch typos.
50
+ - **`waterfall(df, x=..., y=..., ...)`**: one call that returns the figure, like plotly express'
51
+ `px.bar`. Same arguments as `Waterfall` (keyword-only after `x` and `y`); `Waterfall` stays for
52
+ building subtotals step by step.
53
+ - **New colour API in plotly-express style**: `color` (category column, group column or
54
+ `"direction"`), `color_discrete_map` (value → colour; `(group, category)` tuples for one group's
55
+ category), `color_discrete_sequence`, `pattern_shape` and `pattern_shape_map`.
56
+ - `color="direction"` colours increases, decreases and totals (default green / red / grey) and adds
57
+ a legend for them.
58
+ - Keys of `color_discrete_map` / `pattern_shape_map` that match no value raise a `UserWarning`.
59
+ - **Value labels**: `value_labels` (`"bar"` default, `"segment"`, `"both"`, `None` / `False`), with one number
60
+ format for labels and hover: `number_format` (Python format spec or function), `number_prefix`,
61
+ `number_suffix`. Changes are signed (`+9`, `-7`), totals not.
62
+ - **Hover texts** show the x label, the segment's category and value, the step for split bars and the
63
+ running total (previously plotly's default hover showed the internal numeric x-position).
64
+ - `title` and `labels` (column display names, as in plotly express) arguments.
65
+ - **Mixed signs within one bar**: categories of one bar (at an x-value or in a total) may now have
66
+ different signs. The bar is split into narrow sub-steps that walk from its start to its end level,
67
+ with a "net ghost" bar behind them showing the net change. New arguments `mixed_signs`
68
+ (`"steps"` default, `"error"` for the previous behaviour), `net_ghost` and `step_order`.
69
+
70
+ ### Changed
71
+ - `import plotlywaterfall.waterfall as m` now gives the `waterfall` function (the package exports it under
72
+ that name). `from plotlywaterfall.waterfall import Waterfall` keeps working.
73
+ - **Value labels are shown by default** (`value_labels=None` restores unlabelled bars) and the y-axis is
74
+ titled with the y column name (or its `labels` entry). The axis range leaves room for the labels.
75
+ - **Minimum Python is now 3.11** (3.8–3.10 are end-of-life; the code never ran on 3.8/3.9).
76
+ - `numpy` is now declared as a dependency (it was imported but not declared); dependencies are declared in
77
+ `pyproject.toml` and `requirements.txt` was removed.
78
+ - **Default colours**: charts without category and group are coloured by direction. Charts with
79
+ categories or groups get one colour per category (or group) taken from the template's colorway,
80
+ so a category has the same colour in every group (previously plotly cycled colours per trace).
81
+ - Clear `ValueError`s for invalid input: unknown column names, missing values (NaN) in `y`,
82
+ unknown `total_type`, and subtotal keys that are not x-values (now also checked in the constructor).
83
+ - `Waterfall` can be imported directly: `from plotlywaterfall import Waterfall`
84
+ (`from plotlywaterfall.waterfall import Waterfall` keeps working); `plotlywaterfall.__version__` added.
85
+ - Internal cleanup: removed dead code in `add_subtotal`, one helper for bar offsets and one for colors.
86
+
87
+ ### Removed
88
+ - The `colors` argument, replaced by `color` / `color_discrete_map` (see "Upgrading from 0.9").
89
+
90
+ ### Fixed
91
+ - `total_type="sumtotal"` with groups raised `KeyError: 'Y'` unless the value column was named `Y`.
92
+ - Groups with opposite signs at the same x-value (or opposite-signed totals) raised
93
+ "mixed signed categories in totals"; the sign check now only compares stacked bars within one group.
94
+ - The `subtotals` dict passed by the caller is no longer modified (with `total=True` it used to gain
95
+ the total, which added an extra bar when the dict was reused).
96
+ - The `connector_line` default is no longer a dict shared between instances.
97
+
98
+ ### Development
99
+ - mypy runs with `disallow_untyped_defs` and `disallow_incomplete_defs`; CI checks that the wheel
100
+ contains `py.typed`.
101
+ - Example gallery: CI executes the example notebook and publishes it as HTML with interactive charts on
102
+ GitHub Pages (pull requests: `docs-site` artifact); the README image is rendered by CI as well.
103
+ - Modern development setup: uv with `uv.lock`, PEP 735 dependency groups, ruff (lint + format), mypy, pre-commit.
104
+ - GitHub Actions CI: lint, type check, tests on Python 3.11–3.15 (Linux/Windows/macOS), lowest-dependency and
105
+ pre-release dependency jobs, example-notebook execution, package build; weekly scheduled run.
106
+ - Automated PyPI releases on version tags via Trusted Publishing with attestations; Dependabot for actions and dependencies.
107
+ - Automated AI pull-request review (Mistral) posted as a PR comment; see README "AI pull-request review".
108
+
109
+ ## [0.9.3] - 2024-02-20
110
+ - Bugfix legend groups
111
+
112
+ ## [0.9.2] - 2024-02-20
113
+ - Bugfix line placement in case of empty bars at certain x in other then the first group
114
+
115
+ ## [0.9.1] - 2024-01-04
116
+ - Added CHANGELOG.md and using full URLs for images in README.md
117
+
118
+ ## [0.9.0] - 2024-01-04
119
+ - Initial release on pypi.org
@@ -1,4 +1,6 @@
1
- Copyright (c) 2012-2024 Scott Chacon and others
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2026 Daniel Rüffer
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining
4
6
  a copy of this software and associated documentation files (the
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: plotlywaterfall
3
+ Version: 1.1.0
4
+ Summary: Plotly-express-style waterfall charts with groups, stacked categories, subtotals and custom colors.
5
+ Project-URL: Homepage, https://github.com/docdru/plotlywaterfall
6
+ Project-URL: Source, https://github.com/docdru/plotlywaterfall
7
+ Project-URL: Issues, https://github.com/docdru/plotlywaterfall/issues
8
+ Project-URL: Changelog, https://github.com/docdru/plotlywaterfall/blob/main/CHANGELOG.md
9
+ Author-email: Daniel Rüffer <docdru.plotlywaterfall@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: cashflow,chart,finance,jupyter,plotly,visualization,waterfall
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Framework :: Jupyter
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
25
+ Classifier: Programming Language :: Python :: Implementation :: CPython
26
+ Classifier: Topic :: Scientific/Engineering :: Visualization
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: numpy>=1.26
29
+ Requires-Dist: pandas>=2.2
30
+ Requires-Dist: plotly>=5.24
31
+ Description-Content-Type: text/markdown
32
+
33
+ # plotlywaterfall
34
+
35
+ [![CI](https://github.com/docdru/plotlywaterfall/actions/workflows/ci.yml/badge.svg)](https://github.com/docdru/plotlywaterfall/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/plotlywaterfall)](https://pypi.org/project/plotlywaterfall/)
37
+ [![Python versions](https://img.shields.io/pypi/pyversions/plotlywaterfall)](https://pypi.org/project/plotlywaterfall/)
38
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
39
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
40
+ [![Examples](https://img.shields.io/badge/examples-gallery-2a78d6)](https://docdru.github.io/plotlywaterfall/)
41
+
42
+ ## Description
43
+
44
+ A small package adding simple waterfall plotting capabilities on top of the plotly graphing package. Waterfall graphs are useful to display e.g. financial data such as cashflow statements in an easily digestible format.
45
+
46
+ While plotly includes basic capabilities to plot waterfall graphs, it ships only limited features. The *plotlywaterfall* package provides simple, plotly-express-style waterfall graphs with grouping, multiple stacked categories and variable colors.
47
+
48
+ ## Installation
49
+
50
+ Requires Python 3.11 or newer.
51
+
52
+ ```bash
53
+ pip install plotlywaterfall
54
+ # or
55
+ uv add plotlywaterfall
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ **[Example gallery](https://docdru.github.io/plotlywaterfall/)** with interactive charts for every feature (rendered from
61
+ [`examples/examples.ipynb`](https://github.com/docdru/plotlywaterfall/blob/main/examples/examples.ipynb) by CI).
62
+
63
+ `waterfall(df, x=..., y=..., ...)` returns a plotly figure, like plotly express' `px.bar`. The class
64
+ `Waterfall` takes the same arguments (`Waterfall(...).get_fig()`) and also lets you add subtotals step by
65
+ step with `add_subtotal`.
66
+
67
+ A complete example with groups, stacked categories, a subtotal and a total:
68
+
69
+ ```python
70
+ import pandas as pd
71
+
72
+ from plotlywaterfall import waterfall
73
+
74
+ df = pd.DataFrame(
75
+ {
76
+ "X": ["A", "B", "C"] * 4 + ["D", "D"],
77
+ "Y": [4, 1, 8, 7, 3, 2] + [i - 1 for i in [4, 1, 8, 7, 3, 2]] + [8, 5],
78
+ "category": ["one"] * 3 + ["two"] * 3 + ["one"] * 3 + ["two"] * 3 + ["two", "three"],
79
+ "group": ["Group1"] * 6 + ["Group2"] * 6 + ["Group2", "Group3"],
80
+ }
81
+ )
82
+
83
+ fig = waterfall(
84
+ df,
85
+ x="X",
86
+ y="Y",
87
+ category="category",
88
+ group="group",
89
+ total=True,
90
+ subtotals={"C": "Subtotal"},
91
+ color_discrete_map={"one": "#2a78d6", "two": "#eb6834", "three": "#1baf7a"},
92
+ pattern_shape="group",
93
+ )
94
+ fig.update_layout(template="plotly_white")
95
+ fig
96
+ ```
97
+
98
+ ![Example waterfall chart with three groups, stacked categories, a subtotal and a total](https://docdru.github.io/plotlywaterfall/images/readme-example.png)
99
+
100
+ ### Labels, number format and hover
101
+
102
+ Every bar is labelled with its change (signed, e.g. `+9`) or, for totals, its value; hovering shows
103
+ the step, its category and the running total.
104
+
105
+ | Argument | Meaning | Default |
106
+ |---|---|---|
107
+ | `value_labels` | `"bar"` (one label per bar, outside), `"segment"` (inside each stacked segment), `"both"`, or `None` / `False` for no labels | `"bar"` |
108
+ | `number_format` | Python format spec for the absolute value (e.g. `",.1f"`) or a function | thousands separators, up to two decimals |
109
+ | `number_prefix` / `number_suffix` | Units around the number; the sign stays in front (`-$5M`) | `""` |
110
+ | `number_unit` | `"k"`, `"M"`, `"bn"` (e.g. 12500000 → `12.5M`), or `"auto"` for one unit chosen from the chart's largest value | `None` |
111
+ | `negative_style` | `"minus"` (`-7`, changes `+9`) or `"parentheses"` (accounting: `(7)`, changes unsigned) | `"minus"` |
112
+ | `title` | Figure title | `None` |
113
+ | `labels` | Display names for columns, like plotly express (`{"Y": "Cashflow [MUSD]"}`); titles the axes | y-axis titled with the y column |
114
+ | `category_orders` | Order of x, group or category values, like plotly express (`{"X": ["Revenue", "Costs"]}`) | order of a pandas `Categorical`, else of appearance |
115
+ | `x_format` | Labels of the x-values: a strftime pattern for dates (`"%b %Y"`) or a function | dates as `Jan 2026` (one per month) or `2026-01-31` |
116
+
117
+ waterfall(df, x="X", y="Y", total=True, number_prefix="$", number_suffix="M",
118
+ title="Cash bridge", labels={"Y": "Cashflow [MUSD]"})
119
+
120
+ For financial statements: one unit for all labels and accounting negatives,
121
+
122
+ waterfall(df, x="X", y="Y", total=True, number_unit="auto", negative_style="parentheses",
123
+ number_prefix="$") # $1.25M, ($0.73M), …
124
+
125
+ ### Colors
126
+
127
+ Colours work like in plotly express:
128
+
129
+ | Argument | Meaning |
130
+ |---|---|
131
+ | `color` | What drives the colour: the category column, the group column, or `"direction"` (increase / decrease / total). Default: the category column if given, else the group column, else `"direction"`. |
132
+ | `color_discrete_map` | Colour per value, e.g. `{"one": "red"}`. `(group, category)` tuples colour one group's category. With `color="direction"` the keys are `"increase"`, `"decrease"`, `"total"` (default green / red / grey). Keys that match no value raise a warning. |
133
+ | `color_discrete_sequence` | Colours for values missing from the map, assigned in order. Default: the colorway of the active plotly template. |
134
+ | `pattern_shape` | The group or category column, to give its values different fill patterns, e.g. colour by category and pattern by group. |
135
+ | `pattern_shape_map` | Pattern per value, e.g. `{"Actual": "/"}`. |
136
+
137
+ Colours are assigned per value, so a category has the same colour in every group:
138
+
139
+ waterfall(df, x="X", y="Y", category="category", group="group", total=True,
140
+ pattern_shape="group")
141
+
142
+ Coming from version 0.9? The `colors` argument was replaced by `color` / `color_discrete_map`; see
143
+ [Upgrading from 0.9](https://github.com/docdru/plotlywaterfall/blob/main/CHANGELOG.md#upgrading-from-09).
144
+
145
+ ### Mixed signs
146
+
147
+ Categories of one bar may have different signs, e.g. a subsidy (+1) and a restructuring charge (−4)
148
+ at the same x-value, or a total that combines positive and negative categories. Such a bar is split
149
+ into one narrow sub-step per category that walks from the level where the bar starts to where it
150
+ ends, with a light "net ghost" bar behind it showing the net change:
151
+
152
+ df = pd.DataFrame({
153
+ "X": ["Revenue", "Costs", "Other", "Other"],
154
+ "Y": [9, -7, 1, -4],
155
+ "category": ["Operating", "Operating", "Operating", "One-off"],
156
+ })
157
+ waterfall(df, x="X", y="Y", category="category", total=True)
158
+
159
+ Options:
160
+
161
+ | Argument | Values | Default |
162
+ |---|---|---|
163
+ | `mixed_signs` | `"steps"` (split into sub-steps), `"error"` (raise `NotImplementedError`, the behaviour before 1.0) | `"steps"` |
164
+ | `net_ghost` | `True`, `False`, or a dict of plotly marker properties for the net bar | `True` |
165
+ | `step_order` | `"data"` (order of the categories in the DataFrame), `"positive_first"`, `"largest_first"` | `"data"` |
166
+
167
+ Bars whose categories share a sign are drawn as normal stacked bars. With several groups each group's
168
+ bar walks on its own; with many groups and categories the sub-steps get narrow, so increase `barwidth`.
169
+
170
+
171
+ ## Disclaimer
172
+
173
+ I might maintain and improve the package. I might also not.
174
+
175
+
176
+ ## Development
177
+
178
+ The project uses [uv](https://docs.astral.sh/uv/) for environments and dependency locking.
179
+
180
+ ```bash
181
+ git clone https://github.com/docdru/plotlywaterfall.git
182
+ cd plotlywaterfall
183
+ uv sync # creates .venv with the package + all dev tools
184
+ uv run pre-commit install # optional: run checks on every commit
185
+ ```
186
+
187
+ Common tasks:
188
+
189
+ | Task | Command |
190
+ |---|---|
191
+ | Run tests | `uv run pytest` |
192
+ | Tests with coverage | `uv run pytest --cov` |
193
+ | Test another Python | `uv run --python 3.11 pytest` |
194
+ | Lint / auto-fix | `uv run ruff check --fix` |
195
+ | Format | `uv run ruff format` |
196
+ | Type check | `uv run mypy` |
197
+ | Execute example notebook | `uv run pytest --nbmake examples/ -p no:warnings` |
198
+ | Build the docs site (gallery + README image) | `uv run --group notebook --with nbconvert --with kaleido python scripts/build_docs.py --out site` |
199
+ | Open notebooks | `uv run --with jupyterlab jupyter lab` |
200
+ | Build sdist + wheel | `uv build` |
201
+
202
+ CI (GitHub Actions) runs lint, type checks, the test suite on Python 3.11–3.15 (Linux, plus Windows; macOS on `main` and the weekly run),
203
+ a test against the lowest supported dependency versions, the docs site, and a package build.
204
+
205
+ The **docs site** job executes `examples/examples.ipynb`, renders it to HTML with interactive charts and
206
+ renders the README image from `examples/readme_example.py` (keep it in sync with the README code).
207
+ Pull requests get the result as the `docs-site` artifact of the CI run; on `main` it is published to
208
+ [GitHub Pages](https://docdru.github.io/plotlywaterfall/). Nothing generated is committed. One-time setup: *Settings → Pages →
209
+ Build and deployment → Source: GitHub Actions*.
210
+
211
+ ### AI pull-request review
212
+
213
+ Pull requests get an automated review comment (correctness and tests, API/docs consistency,
214
+ security and supply chain, surprising changes) from `.github/workflows/ai-review.yml`, which runs
215
+ `scripts/ai_review.py` against the Mistral API. The comment is updated on every push.
216
+
217
+ - **Setup:** add the repository secret `MISTRAL_API_KEY` (*Settings → Secrets and variables → Actions*).
218
+ Optionally set the repository variable `AI_REVIEW_MODEL` to override the default model (`mistral-large-2512`).
219
+ - **Scope:** runs for non-draft PRs from branches in this repository by authors with write access;
220
+ fork, bot and draft PRs are skipped. Mark a draft "Ready for review" to trigger it.
221
+ - **Try locally** (prints the prompt, calls nothing, posts nothing):
222
+ `GITHUB_REPOSITORY=docdru/plotlywaterfall GITHUB_TOKEN=$(gh auth token) python scripts/ai_review.py --pr 1 --dry-run`
223
+
224
+ The review is advisory: it is not part of the required checks, and its findings should be verified.
225
+
226
+ ### Releasing
227
+
228
+ 1. Bump `__version__` in `src/plotlywaterfall/__about__.py` and add a `CHANGELOG.md` entry.
229
+ 2. Commit, then tag and push: `git tag v0.9.4 && git push origin v0.9.4`.
230
+ 3. The `Release` workflow builds the package and publishes it to PyPI via
231
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (no API token needed).
232
+
233
+ ## Changelog
234
+
235
+ Refer to [CHANGELOG.md](https://github.com/docdru/plotlywaterfall/blob/main/CHANGELOG.md)
@@ -0,0 +1,203 @@
1
+ # plotlywaterfall
2
+
3
+ [![CI](https://github.com/docdru/plotlywaterfall/actions/workflows/ci.yml/badge.svg)](https://github.com/docdru/plotlywaterfall/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/plotlywaterfall)](https://pypi.org/project/plotlywaterfall/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/plotlywaterfall)](https://pypi.org/project/plotlywaterfall/)
6
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
7
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
8
+ [![Examples](https://img.shields.io/badge/examples-gallery-2a78d6)](https://docdru.github.io/plotlywaterfall/)
9
+
10
+ ## Description
11
+
12
+ A small package adding simple waterfall plotting capabilities on top of the plotly graphing package. Waterfall graphs are useful to display e.g. financial data such as cashflow statements in an easily digestible format.
13
+
14
+ While plotly includes basic capabilities to plot waterfall graphs, it ships only limited features. The *plotlywaterfall* package provides simple, plotly-express-style waterfall graphs with grouping, multiple stacked categories and variable colors.
15
+
16
+ ## Installation
17
+
18
+ Requires Python 3.11 or newer.
19
+
20
+ ```bash
21
+ pip install plotlywaterfall
22
+ # or
23
+ uv add plotlywaterfall
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ **[Example gallery](https://docdru.github.io/plotlywaterfall/)** with interactive charts for every feature (rendered from
29
+ [`examples/examples.ipynb`](https://github.com/docdru/plotlywaterfall/blob/main/examples/examples.ipynb) by CI).
30
+
31
+ `waterfall(df, x=..., y=..., ...)` returns a plotly figure, like plotly express' `px.bar`. The class
32
+ `Waterfall` takes the same arguments (`Waterfall(...).get_fig()`) and also lets you add subtotals step by
33
+ step with `add_subtotal`.
34
+
35
+ A complete example with groups, stacked categories, a subtotal and a total:
36
+
37
+ ```python
38
+ import pandas as pd
39
+
40
+ from plotlywaterfall import waterfall
41
+
42
+ df = pd.DataFrame(
43
+ {
44
+ "X": ["A", "B", "C"] * 4 + ["D", "D"],
45
+ "Y": [4, 1, 8, 7, 3, 2] + [i - 1 for i in [4, 1, 8, 7, 3, 2]] + [8, 5],
46
+ "category": ["one"] * 3 + ["two"] * 3 + ["one"] * 3 + ["two"] * 3 + ["two", "three"],
47
+ "group": ["Group1"] * 6 + ["Group2"] * 6 + ["Group2", "Group3"],
48
+ }
49
+ )
50
+
51
+ fig = waterfall(
52
+ df,
53
+ x="X",
54
+ y="Y",
55
+ category="category",
56
+ group="group",
57
+ total=True,
58
+ subtotals={"C": "Subtotal"},
59
+ color_discrete_map={"one": "#2a78d6", "two": "#eb6834", "three": "#1baf7a"},
60
+ pattern_shape="group",
61
+ )
62
+ fig.update_layout(template="plotly_white")
63
+ fig
64
+ ```
65
+
66
+ ![Example waterfall chart with three groups, stacked categories, a subtotal and a total](https://docdru.github.io/plotlywaterfall/images/readme-example.png)
67
+
68
+ ### Labels, number format and hover
69
+
70
+ Every bar is labelled with its change (signed, e.g. `+9`) or, for totals, its value; hovering shows
71
+ the step, its category and the running total.
72
+
73
+ | Argument | Meaning | Default |
74
+ |---|---|---|
75
+ | `value_labels` | `"bar"` (one label per bar, outside), `"segment"` (inside each stacked segment), `"both"`, or `None` / `False` for no labels | `"bar"` |
76
+ | `number_format` | Python format spec for the absolute value (e.g. `",.1f"`) or a function | thousands separators, up to two decimals |
77
+ | `number_prefix` / `number_suffix` | Units around the number; the sign stays in front (`-$5M`) | `""` |
78
+ | `number_unit` | `"k"`, `"M"`, `"bn"` (e.g. 12500000 → `12.5M`), or `"auto"` for one unit chosen from the chart's largest value | `None` |
79
+ | `negative_style` | `"minus"` (`-7`, changes `+9`) or `"parentheses"` (accounting: `(7)`, changes unsigned) | `"minus"` |
80
+ | `title` | Figure title | `None` |
81
+ | `labels` | Display names for columns, like plotly express (`{"Y": "Cashflow [MUSD]"}`); titles the axes | y-axis titled with the y column |
82
+ | `category_orders` | Order of x, group or category values, like plotly express (`{"X": ["Revenue", "Costs"]}`) | order of a pandas `Categorical`, else of appearance |
83
+ | `x_format` | Labels of the x-values: a strftime pattern for dates (`"%b %Y"`) or a function | dates as `Jan 2026` (one per month) or `2026-01-31` |
84
+
85
+ waterfall(df, x="X", y="Y", total=True, number_prefix="$", number_suffix="M",
86
+ title="Cash bridge", labels={"Y": "Cashflow [MUSD]"})
87
+
88
+ For financial statements: one unit for all labels and accounting negatives,
89
+
90
+ waterfall(df, x="X", y="Y", total=True, number_unit="auto", negative_style="parentheses",
91
+ number_prefix="$") # $1.25M, ($0.73M), …
92
+
93
+ ### Colors
94
+
95
+ Colours work like in plotly express:
96
+
97
+ | Argument | Meaning |
98
+ |---|---|
99
+ | `color` | What drives the colour: the category column, the group column, or `"direction"` (increase / decrease / total). Default: the category column if given, else the group column, else `"direction"`. |
100
+ | `color_discrete_map` | Colour per value, e.g. `{"one": "red"}`. `(group, category)` tuples colour one group's category. With `color="direction"` the keys are `"increase"`, `"decrease"`, `"total"` (default green / red / grey). Keys that match no value raise a warning. |
101
+ | `color_discrete_sequence` | Colours for values missing from the map, assigned in order. Default: the colorway of the active plotly template. |
102
+ | `pattern_shape` | The group or category column, to give its values different fill patterns, e.g. colour by category and pattern by group. |
103
+ | `pattern_shape_map` | Pattern per value, e.g. `{"Actual": "/"}`. |
104
+
105
+ Colours are assigned per value, so a category has the same colour in every group:
106
+
107
+ waterfall(df, x="X", y="Y", category="category", group="group", total=True,
108
+ pattern_shape="group")
109
+
110
+ Coming from version 0.9? The `colors` argument was replaced by `color` / `color_discrete_map`; see
111
+ [Upgrading from 0.9](https://github.com/docdru/plotlywaterfall/blob/main/CHANGELOG.md#upgrading-from-09).
112
+
113
+ ### Mixed signs
114
+
115
+ Categories of one bar may have different signs, e.g. a subsidy (+1) and a restructuring charge (−4)
116
+ at the same x-value, or a total that combines positive and negative categories. Such a bar is split
117
+ into one narrow sub-step per category that walks from the level where the bar starts to where it
118
+ ends, with a light "net ghost" bar behind it showing the net change:
119
+
120
+ df = pd.DataFrame({
121
+ "X": ["Revenue", "Costs", "Other", "Other"],
122
+ "Y": [9, -7, 1, -4],
123
+ "category": ["Operating", "Operating", "Operating", "One-off"],
124
+ })
125
+ waterfall(df, x="X", y="Y", category="category", total=True)
126
+
127
+ Options:
128
+
129
+ | Argument | Values | Default |
130
+ |---|---|---|
131
+ | `mixed_signs` | `"steps"` (split into sub-steps), `"error"` (raise `NotImplementedError`, the behaviour before 1.0) | `"steps"` |
132
+ | `net_ghost` | `True`, `False`, or a dict of plotly marker properties for the net bar | `True` |
133
+ | `step_order` | `"data"` (order of the categories in the DataFrame), `"positive_first"`, `"largest_first"` | `"data"` |
134
+
135
+ Bars whose categories share a sign are drawn as normal stacked bars. With several groups each group's
136
+ bar walks on its own; with many groups and categories the sub-steps get narrow, so increase `barwidth`.
137
+
138
+
139
+ ## Disclaimer
140
+
141
+ I might maintain and improve the package. I might also not.
142
+
143
+
144
+ ## Development
145
+
146
+ The project uses [uv](https://docs.astral.sh/uv/) for environments and dependency locking.
147
+
148
+ ```bash
149
+ git clone https://github.com/docdru/plotlywaterfall.git
150
+ cd plotlywaterfall
151
+ uv sync # creates .venv with the package + all dev tools
152
+ uv run pre-commit install # optional: run checks on every commit
153
+ ```
154
+
155
+ Common tasks:
156
+
157
+ | Task | Command |
158
+ |---|---|
159
+ | Run tests | `uv run pytest` |
160
+ | Tests with coverage | `uv run pytest --cov` |
161
+ | Test another Python | `uv run --python 3.11 pytest` |
162
+ | Lint / auto-fix | `uv run ruff check --fix` |
163
+ | Format | `uv run ruff format` |
164
+ | Type check | `uv run mypy` |
165
+ | Execute example notebook | `uv run pytest --nbmake examples/ -p no:warnings` |
166
+ | Build the docs site (gallery + README image) | `uv run --group notebook --with nbconvert --with kaleido python scripts/build_docs.py --out site` |
167
+ | Open notebooks | `uv run --with jupyterlab jupyter lab` |
168
+ | Build sdist + wheel | `uv build` |
169
+
170
+ CI (GitHub Actions) runs lint, type checks, the test suite on Python 3.11–3.15 (Linux, plus Windows; macOS on `main` and the weekly run),
171
+ a test against the lowest supported dependency versions, the docs site, and a package build.
172
+
173
+ The **docs site** job executes `examples/examples.ipynb`, renders it to HTML with interactive charts and
174
+ renders the README image from `examples/readme_example.py` (keep it in sync with the README code).
175
+ Pull requests get the result as the `docs-site` artifact of the CI run; on `main` it is published to
176
+ [GitHub Pages](https://docdru.github.io/plotlywaterfall/). Nothing generated is committed. One-time setup: *Settings → Pages →
177
+ Build and deployment → Source: GitHub Actions*.
178
+
179
+ ### AI pull-request review
180
+
181
+ Pull requests get an automated review comment (correctness and tests, API/docs consistency,
182
+ security and supply chain, surprising changes) from `.github/workflows/ai-review.yml`, which runs
183
+ `scripts/ai_review.py` against the Mistral API. The comment is updated on every push.
184
+
185
+ - **Setup:** add the repository secret `MISTRAL_API_KEY` (*Settings → Secrets and variables → Actions*).
186
+ Optionally set the repository variable `AI_REVIEW_MODEL` to override the default model (`mistral-large-2512`).
187
+ - **Scope:** runs for non-draft PRs from branches in this repository by authors with write access;
188
+ fork, bot and draft PRs are skipped. Mark a draft "Ready for review" to trigger it.
189
+ - **Try locally** (prints the prompt, calls nothing, posts nothing):
190
+ `GITHUB_REPOSITORY=docdru/plotlywaterfall GITHUB_TOKEN=$(gh auth token) python scripts/ai_review.py --pr 1 --dry-run`
191
+
192
+ The review is advisory: it is not part of the required checks, and its findings should be verified.
193
+
194
+ ### Releasing
195
+
196
+ 1. Bump `__version__` in `src/plotlywaterfall/__about__.py` and add a `CHANGELOG.md` entry.
197
+ 2. Commit, then tag and push: `git tag v0.9.4 && git push origin v0.9.4`.
198
+ 3. The `Release` workflow builds the package and publishes it to PyPI via
199
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (no API token needed).
200
+
201
+ ## Changelog
202
+
203
+ Refer to [CHANGELOG.md](https://github.com/docdru/plotlywaterfall/blob/main/CHANGELOG.md)