Skip to content

Commit 4bc80d7

Browse files
feat(altair): implement waterfall-basic (#5743)
## Implementation: `waterfall-basic` - python/altair Implements the **python/altair** version of `waterfall-basic`. **File:** `plots/waterfall-basic/implementations/python/altair.py` **Parent Issue:** #777 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25410774625)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 55f9b9f commit 4bc80d7

2 files changed

Lines changed: 177 additions & 148 deletions

File tree

plots/waterfall-basic/implementations/python/altair.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
waterfall-basic: Basic Waterfall Chart
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-24
3+
Library: altair 6.1.0 | Python 3.13.13
4+
Quality: 92/100 | Updated: 2026-05-06
55
"""
66

7+
import os
8+
79
import altair as alt
810
import pandas as pd
911

1012

13+
# Theme tokens (see prompts/default-style-guide.md)
14+
THEME = os.getenv("ANYPLOT_THEME", "light")
15+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
16+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
17+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
18+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
19+
20+
# Okabe-Ito palette for waterfall types
21+
POSITIVE_COLOR = "#009E73" # Position 1: brand green for positive
22+
NEGATIVE_COLOR = "#D55E00" # Position 2: vermillion/red for negative
23+
TOTAL_COLOR = "#0072B2" # Position 3: blue for totals
24+
1125
# Data: Quarterly financial breakdown from revenue to net income
1226
categories = ["Revenue", "Cost of Goods", "Gross Profit", "Operating Expenses", "Other Income", "Taxes", "Net Income"]
13-
# Values: First and last are totals (None), middle values are changes
1427
values = [500, -200, None, -150, 25, -45, None]
1528

1629
# Calculate running totals and bar positions
1730
n = len(categories)
1831
running_total = [0] * n
1932
bar_bottom = [0] * n
2033
bar_top = [0] * n
21-
bar_types = [] # 'total', 'positive', 'negative'
34+
bar_types = []
2235

2336
running_total[0] = values[0]
2437
bar_bottom[0] = 0
@@ -28,13 +41,11 @@
2841
current = values[0]
2942
for i in range(1, n):
3043
if values[i] is None:
31-
# Subtotal bar (Gross Profit or Net Income)
3244
running_total[i] = current
3345
bar_bottom[i] = 0
3446
bar_top[i] = current
3547
bar_types.append("total")
3648
else:
37-
# Change bar
3849
running_total[i] = current + values[i]
3950
if values[i] >= 0:
4051
bar_bottom[i] = current
@@ -46,7 +57,7 @@
4657
bar_types.append("negative")
4758
current = running_total[i]
4859

49-
# Create display values for labels (show change for non-totals, running total for totals)
60+
# Create display values for labels
5061
display_values = []
5162
for i, val in enumerate(values):
5263
if val is None:
@@ -70,16 +81,16 @@
7081
}
7182
)
7283

73-
# Color scale: Python Blue for totals, green for positive, red for negative
74-
color_scale = alt.Scale(domain=["total", "positive", "negative"], range=["#306998", "#4CAF50", "#E53935"])
84+
# Color scale using Okabe-Ito palette
85+
color_scale = alt.Scale(domain=["total", "positive", "negative"], range=[TOTAL_COLOR, POSITIVE_COLOR, NEGATIVE_COLOR])
7586

7687
# Sort by order field
7788
sort_order = alt.EncodingSortField(field="order", order="ascending")
7889

7990
# Create bar chart using bar marks with y and y2
8091
bars = (
8192
alt.Chart(df)
82-
.mark_bar(size=65, stroke="white", strokeWidth=2)
93+
.mark_bar(size=65, stroke=INK_SOFT, strokeWidth=2)
8394
.encode(
8495
x=alt.X(
8596
"category:N",
@@ -96,7 +107,7 @@
96107
# Value labels on bars
97108
labels = (
98109
alt.Chart(df)
99-
.mark_text(fontSize=18, fontWeight="bold", color="white")
110+
.mark_text(fontSize=18, fontWeight="bold", color=INK)
100111
.encode(x=alt.X("category:N", sort=sort_order), y=alt.Y("label_y:Q"), text="display_value:N")
101112
)
102113

@@ -112,18 +123,26 @@
112123
# Connector lines using rule mark
113124
connectors = (
114125
alt.Chart(df_connectors)
115-
.mark_rule(color="#666666", strokeDash=[6, 4], strokeWidth=2)
126+
.mark_rule(color=INK_SOFT, strokeDash=[6, 4], strokeWidth=2)
116127
.encode(x=alt.X("x:N", sort=sort_order), x2=alt.X2("x2:N"), y=alt.Y("y:Q"))
117128
)
118129

119130
# Combine all layers
120131
chart = (
121132
alt.layer(connectors, bars, labels)
122-
.properties(width=1600, height=900, title=alt.Title("waterfall-basic · altair · pyplots.ai", fontSize=28))
123-
.configure_axis(grid=True, gridOpacity=0.3, gridDash=[4, 4])
124-
.configure_view(strokeWidth=0)
133+
.properties(
134+
width=1600,
135+
height=900,
136+
background=PAGE_BG,
137+
title=alt.Title("waterfall-basic · altair · anyplot.ai", fontSize=28, color=INK),
138+
)
139+
.configure_view(fill=PAGE_BG, stroke=INK_SOFT, continuousWidth=1600, continuousHeight=900)
140+
.configure_axis(
141+
domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.10, labelColor=INK_SOFT, titleColor=INK
142+
)
143+
.configure_title(color=INK)
125144
)
126145

127-
# Save as PNG and HTML
128-
chart.save("plot.png", scale_factor=3.0)
129-
chart.save("plot.html")
146+
# Save as PNG and HTML with theme suffix
147+
chart.save(f"plot-{THEME}.png", scale_factor=3.0)
148+
chart.save(f"plot-{THEME}.html")

0 commit comments

Comments
 (0)