Skip to content

Commit cab1f24

Browse files
feat(altair): implement funnel-basic (#5428)
## Implementation: `funnel-basic` - python/altair Implements the **python/altair** version of `funnel-basic`. **File:** `plots/funnel-basic/implementations/python/altair.py` **Parent Issue:** #789 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24949210482)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 6c8bee5 commit cab1f24

2 files changed

Lines changed: 220 additions & 157 deletions

File tree

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,87 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
funnel-basic: Basic Funnel Chart
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: altair 6.1.0 | Python 3.14.4
4+
Quality: 87/100 | Updated: 2026-04-26
55
"""
66

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

1012

11-
# Data - Sales funnel example
13+
# Theme-adaptive chrome
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 categorical palette — first stage is brand green (#009E73)
21+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00"]
22+
23+
# Data — sales funnel example from the specification
1224
stages = ["Awareness", "Interest", "Consideration", "Intent", "Purchase"]
1325
values = [1000, 600, 400, 200, 100]
1426

15-
# Create DataFrame with centered positioning for funnel shape
16-
df = pd.DataFrame({"stage": stages, "value": values, "order": range(len(stages))})
17-
18-
# Calculate percentage of initial value for display
27+
df = pd.DataFrame({"stage": stages, "value": values})
1928
df["percentage"] = (df["value"] / df["value"].iloc[0] * 100).round(1)
20-
df["label"] = df["value"].astype(str) + " (" + df["percentage"].astype(str) + "%)"
29+
df["label"] = df["value"].astype(str) + " · " + df["percentage"].astype(str) + "%"
2130

22-
# For centered funnel: calculate x start and end positions
31+
# Centered funnel: bars span [-value/2, +value/2]; mid is always 0
2332
df["x_start"] = -df["value"] / 2
2433
df["x_end"] = df["value"] / 2
34+
df["x_mid"] = 0
2535

26-
# Colors for each stage (Python Blue gradient to Yellow)
27-
colors = ["#306998", "#3D7AAF", "#4A8BC6", "#579CDD", "#FFD43B"]
36+
x_scale = alt.Scale(domain=[-620, 620])
2837

29-
# Create centered horizontal bars to form funnel shape
38+
# Funnel bars (centered, narrowing)
3039
bars = (
3140
alt.Chart(df)
32-
.mark_bar(cornerRadius=4, height=70)
41+
.mark_bar(cornerRadius=6, height=110, stroke=PAGE_BG, strokeWidth=2)
3342
.encode(
34-
x=alt.X("x_start:Q", axis=None, scale=alt.Scale(domain=[-600, 600])),
43+
x=alt.X("x_start:Q", axis=None, scale=x_scale),
3544
x2="x_end:Q",
36-
y=alt.Y(
37-
"stage:N", sort=stages, axis=alt.Axis(labelFontSize=20, labelFontWeight="bold", title=None, labelPadding=15)
38-
),
39-
color=alt.Color("stage:N", scale=alt.Scale(domain=stages, range=colors), legend=None),
45+
y=alt.Y("stage:N", sort=stages, axis=alt.Axis(title=None, labelPadding=18, ticks=False, domain=False)),
46+
color=alt.Color("stage:N", scale=alt.Scale(domain=stages, range=OKABE_ITO), legend=None),
47+
tooltip=[
48+
alt.Tooltip("stage:N", title="Stage"),
49+
alt.Tooltip("value:Q", title="Count", format=","),
50+
alt.Tooltip("percentage:Q", title="% of top", format=".1f"),
51+
],
4052
)
4153
)
4254

43-
# Add value labels to the right of bars
44-
text = (
55+
# Value + percentage labels positioned to the right of each bar
56+
labels = (
4557
alt.Chart(df)
46-
.mark_text(align="left", baseline="middle", dx=15, fontSize=18, fontWeight="bold")
47-
.encode(x=alt.X("x_end:Q"), y=alt.Y("stage:N", sort=stages), text=alt.Text("label:N"), color=alt.value("#333333"))
58+
.mark_text(align="left", baseline="middle", dx=14, fontSize=20, fontWeight="bold", color=INK)
59+
.encode(x=alt.X("x_end:Q", scale=x_scale, axis=None), y=alt.Y("stage:N", sort=stages), text="label:N")
4860
)
4961

50-
# Combine bars and text
5162
chart = (
52-
(bars + text)
63+
(bars + labels)
5364
.properties(
54-
width=1600, height=900, title=alt.Title(text="funnel-basic · altair · pyplots.ai", fontSize=28, anchor="middle")
65+
width=1400,
66+
height=900,
67+
background=PAGE_BG,
68+
title=alt.Title(
69+
"Sales Funnel · funnel-basic · altair · anyplot.ai", fontSize=28, color=INK, anchor="middle", offset=20
70+
),
71+
)
72+
.configure_view(fill=PAGE_BG, stroke=None)
73+
.configure_axis(
74+
labelFontSize=20,
75+
labelFontWeight="bold",
76+
labelColor=INK,
77+
titleColor=INK,
78+
domainColor=INK_SOFT,
79+
tickColor=INK_SOFT,
80+
grid=False,
5581
)
56-
.configure_view(strokeWidth=0)
57-
.configure_axis(grid=False)
82+
.configure_title(color=INK)
83+
.configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)
5884
)
5985

60-
# Save as PNG (target 4800x2700) and HTML
61-
chart.save("plot.png", scale_factor=3.0)
62-
chart.save("plot.html")
86+
chart.save(f"plot-{THEME}.png", scale_factor=3.0)
87+
chart.save(f"plot-{THEME}.html")

0 commit comments

Comments
 (0)