|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import altair as alt |
8 | 10 | import pandas as pd |
9 | 11 |
|
10 | 12 |
|
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 |
12 | 24 | stages = ["Awareness", "Interest", "Consideration", "Intent", "Purchase"] |
13 | 25 | values = [1000, 600, 400, 200, 100] |
14 | 26 |
|
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}) |
19 | 28 | 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) + "%" |
21 | 30 |
|
22 | | -# For centered funnel: calculate x start and end positions |
| 31 | +# Centered funnel: bars span [-value/2, +value/2]; mid is always 0 |
23 | 32 | df["x_start"] = -df["value"] / 2 |
24 | 33 | df["x_end"] = df["value"] / 2 |
| 34 | +df["x_mid"] = 0 |
25 | 35 |
|
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]) |
28 | 37 |
|
29 | | -# Create centered horizontal bars to form funnel shape |
| 38 | +# Funnel bars (centered, narrowing) |
30 | 39 | bars = ( |
31 | 40 | alt.Chart(df) |
32 | | - .mark_bar(cornerRadius=4, height=70) |
| 41 | + .mark_bar(cornerRadius=6, height=110, stroke=PAGE_BG, strokeWidth=2) |
33 | 42 | .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), |
35 | 44 | 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 | + ], |
40 | 52 | ) |
41 | 53 | ) |
42 | 54 |
|
43 | | -# Add value labels to the right of bars |
44 | | -text = ( |
| 55 | +# Value + percentage labels positioned to the right of each bar |
| 56 | +labels = ( |
45 | 57 | 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") |
48 | 60 | ) |
49 | 61 |
|
50 | | -# Combine bars and text |
51 | 62 | chart = ( |
52 | | - (bars + text) |
| 63 | + (bars + labels) |
53 | 64 | .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, |
55 | 81 | ) |
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) |
58 | 84 | ) |
59 | 85 |
|
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