Skip to content

Commit 9cc8e0a

Browse files
feat(plotnine): implement waterfall-basic (#5744)
## Implementation: `waterfall-basic` - python/plotnine Implements the **python/plotnine** version of `waterfall-basic`. **File:** `plots/waterfall-basic/implementations/python/plotnine.py` **Parent Issue:** #777 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25410836349)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 4fdf648 commit 9cc8e0a

2 files changed

Lines changed: 277 additions & 54 deletions

File tree

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
waterfall-basic: Basic Waterfall Chart
3-
Library: plotnine 0.15.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-14
3+
Library: plotnine 0.15.4 | Python 3.13.13
4+
Quality: 84/100 | Created: 2026-05-06
55
"""
66

7+
import os
8+
79
import pandas as pd
810
from plotnine import (
911
aes,
1012
element_blank,
13+
element_line,
14+
element_rect,
1115
element_text,
1216
geom_rect,
1317
geom_segment,
@@ -21,9 +25,15 @@
2125
)
2226

2327

28+
# Theme tokens
29+
THEME = os.getenv("ANYPLOT_THEME", "light")
30+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
31+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
32+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
33+
2434
# Data - quarterly financial breakdown
25-
categories = ["Revenue", "Product Costs", "Marketing", "Operations", "R&D", "Admin", "Taxes", "Net Profit"]
26-
values = [500, -150, -60, -45, -35, -30, -40, 140]
35+
categories = ["Starting Balance", "Q1 Sales", "Operating Costs", "R&D Investment", "Tax Payment", "Ending Balance"]
36+
values = [1000, 450, -280, -120, -150, 900]
2737

2838
# Calculate waterfall positions
2939
df = pd.DataFrame({"category": categories, "value": values})
@@ -35,20 +45,17 @@
3545
ends = []
3646
bar_types = []
3747

38-
for i, (_cat, val) in enumerate(zip(categories, values, strict=True)):
48+
for i, val in enumerate(values):
3949
if i == 0:
40-
# First bar: starts at 0, ends at value
4150
starts.append(0)
4251
ends.append(val)
4352
bar_types.append("total")
4453
running_total = val
45-
elif i == len(categories) - 1:
46-
# Last bar: total bar starting from 0
54+
elif i == len(values) - 1:
4755
starts.append(0)
4856
ends.append(running_total)
4957
bar_types.append("total")
5058
else:
51-
# Middle bars: changes
5259
if val >= 0:
5360
starts.append(running_total)
5461
ends.append(running_total + val)
@@ -64,52 +71,54 @@
6471
df["bar_type"] = bar_types
6572
df["x_pos"] = range(len(categories))
6673

67-
# Create connector line data (from end of one bar to start of next)
74+
# Create connector lines
6875
connectors = []
6976
for i in range(len(df) - 1):
70-
if i < len(df) - 2: # Don't connect to the final total bar
77+
if i < len(df) - 2:
7178
connectors.append(
7279
{"x_start": df.iloc[i]["x_pos"] + 0.4, "x_end": df.iloc[i + 1]["x_pos"] - 0.4, "y": df.iloc[i]["end"]}
7380
)
74-
connector_df = pd.DataFrame(connectors)
81+
connector_df = pd.DataFrame(connectors) if connectors else pd.DataFrame()
82+
83+
# Colors: green for positive, red for negative, gray for totals
84+
colors = {"total": INK_SOFT, "positive": "#2ecc71", "negative": "#e74c3c"}
7585

76-
# Add labels for values
77-
df["label_y"] = df.apply(lambda row: row["end"] + 10 if row["end"] >= row["start"] else row["start"] + 10, axis=1)
78-
df["label_text"] = df.apply(
79-
lambda row: f"+{int(row['value'])}" if row["value"] > 0 and row["bar_type"] != "total" else str(int(row["value"])),
80-
axis=1,
86+
# Create plot
87+
plot = ggplot() + geom_rect(
88+
df, aes(xmin="x_pos - 0.35", xmax="x_pos + 0.35", ymin="start", ymax="end", fill="bar_type")
8189
)
8290

83-
# Colors: blue for totals, green for positive, red for negative
84-
colors = {"total": "#306998", "positive": "#2ECC71", "negative": "#E74C3C"}
91+
if not connector_df.empty:
92+
plot = plot + geom_segment(
93+
connector_df, aes(x="x_start", xend="x_end", y="y", yend="y"), color=INK_SOFT, size=0.5, alpha=0.5
94+
)
8595

86-
# Create plot
8796
plot = (
88-
ggplot()
89-
+ geom_rect(df, aes(xmin="x_pos - 0.4", xmax="x_pos + 0.4", ymin="start", ymax="end", fill="bar_type"))
90-
+ geom_segment(
91-
connector_df, aes(x="x_start", xend="x_end", y="y", yend="y"), color="#666666", size=0.5, linetype="dashed"
92-
)
93-
+ geom_text(df, aes(x="x_pos", y="label_y", label="label_text"), size=12, color="#333333")
97+
plot
98+
+ geom_text(df, aes(x="x_pos", y="end", label="value"), size=10, color=INK)
9499
+ scale_fill_manual(
95-
values=colors, name="Type", labels={"total": "Total", "positive": "Increase", "negative": "Decrease"}
100+
values=colors, name="Category", labels={"total": "Total", "positive": "Increase", "negative": "Decrease"}
96101
)
97-
+ labs(x="", y="Amount ($K)", title="Quarterly Financials · waterfall-basic · plotnine · pyplots.ai")
102+
+ scale_x_continuous(breaks=list(range(len(categories))), labels=categories, limits=(-0.6, len(categories) - 0.4))
103+
+ labs(x="", y="Amount ($1K)", title="Quarterly Financial Summary · waterfall-basic · plotnine · anyplot.ai")
98104
+ theme_minimal()
99105
+ theme(
100106
figure_size=(16, 9),
101-
text=element_text(size=14),
102-
axis_title=element_text(size=20),
103-
axis_text_x=element_text(size=16, rotation=30, ha="right"),
104-
axis_text_y=element_text(size=16),
105-
plot_title=element_text(size=24),
106-
legend_text=element_text(size=16),
107-
legend_position="top",
107+
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
108+
panel_background=element_rect(fill=PAGE_BG),
109+
panel_grid_major=element_line(color=INK_SOFT, size=0.3, alpha=0.1, linewidth=0.8),
108110
panel_grid_minor=element_blank(),
111+
panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),
112+
axis_title=element_text(size=20, color=INK),
113+
axis_text_x=element_text(size=16, color=INK_SOFT, angle=45, ha="right"),
114+
axis_text_y=element_text(size=16, color=INK_SOFT),
115+
axis_line=element_line(color=INK_SOFT, size=0.5),
116+
plot_title=element_text(size=24, color=INK, face="medium"),
117+
legend_background=element_rect(fill=PAGE_BG, color=INK_SOFT, size=0.5),
118+
legend_text=element_text(size=16, color=INK_SOFT),
119+
legend_title=element_text(size=16, color=INK),
120+
legend_position="top",
109121
)
110122
)
111123

112-
# Add x-axis labels
113-
plot = plot + scale_x_continuous(breaks=list(range(len(categories))), labels=categories)
114-
115-
plot.save("plot.png", dpi=300)
124+
plot.save(f"plot-{THEME}.png", dpi=300, width=16, height=9)

0 commit comments

Comments
 (0)