|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | slope-basic: Basic Slope Chart (Slopegraph) |
3 | | -Library: altair 6.0.0 | Python 3.13.11 |
4 | | -Quality: 94/100 | Created: 2025-12-17 |
| 3 | +Library: altair 6.1.0 | Python 3.13.13 |
| 4 | +Quality: 86/100 | Created: 2026-04-30 |
5 | 5 | """ |
6 | 6 |
|
7 | | -import altair as alt |
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
8 | 10 | import pandas as pd |
9 | 11 |
|
10 | 12 |
|
11 | | -# Data - Product sales comparing Q1 vs Q4 (10 products) |
| 13 | +_script_dir = os.path.dirname(os.path.abspath(__file__)) |
| 14 | +if _script_dir in sys.path: |
| 15 | + sys.path.remove(_script_dir) |
| 16 | + |
| 17 | +import altair as alt # noqa: E402 |
| 18 | + |
| 19 | + |
| 20 | +# Theme tokens |
| 21 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 22 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 23 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 24 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 25 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 26 | + |
| 27 | +# Okabe-Ito: position 1 = Increase, position 2 = Decrease |
| 28 | +COLOR_INCREASE = "#009E73" |
| 29 | +COLOR_DECREASE = "#D55E00" |
| 30 | + |
| 31 | +# Data |
12 | 32 | data = pd.DataFrame( |
13 | 33 | { |
14 | 34 | "Product": [ |
|
28 | 48 | } |
29 | 49 | ) |
30 | 50 |
|
31 | | -# Reshape data to long format for slope chart |
32 | 51 | df_long = pd.melt(data, id_vars=["Product"], value_vars=["Q1 Sales", "Q4 Sales"], var_name="Period", value_name="Sales") |
33 | | - |
34 | | -# Determine direction of change for color coding |
35 | 52 | data["Direction"] = data.apply(lambda row: "Increase" if row["Q4 Sales"] > row["Q1 Sales"] else "Decrease", axis=1) |
36 | 53 | df_long = df_long.merge(data[["Product", "Direction"]], on="Product") |
37 | 54 |
|
38 | | -# Create slope chart |
| 55 | +color_scale = alt.Scale(domain=["Increase", "Decrease"], range=[COLOR_INCREASE, COLOR_DECREASE]) |
| 56 | + |
| 57 | +# Plot |
39 | 58 | lines = ( |
40 | 59 | alt.Chart(df_long) |
41 | 60 | .mark_line(strokeWidth=3, opacity=0.8) |
42 | 61 | .encode( |
43 | | - x=alt.X("Period:N", axis=alt.Axis(labelFontSize=20, titleFontSize=24, title=None, labelAngle=0)), |
| 62 | + x=alt.X("Period:N", axis=alt.Axis(labelFontSize=20, title=None, labelAngle=0)), |
44 | 63 | y=alt.Y( |
45 | 64 | "Sales:Q", |
46 | 65 | axis=alt.Axis(labelFontSize=18, titleFontSize=22, title="Sales (units)"), |
47 | 66 | scale=alt.Scale(zero=False), |
48 | 67 | ), |
49 | 68 | color=alt.Color( |
50 | | - "Direction:N", |
51 | | - scale=alt.Scale(domain=["Increase", "Decrease"], range=["#306998", "#FFD43B"]), |
52 | | - legend=alt.Legend(titleFontSize=20, labelFontSize=18, orient="top-right"), |
| 69 | + "Direction:N", scale=color_scale, legend=alt.Legend(titleFontSize=20, labelFontSize=18, orient="top-right") |
53 | 70 | ), |
54 | 71 | detail="Product:N", |
55 | 72 | ) |
56 | 73 | ) |
57 | 74 |
|
58 | | -# Add points at endpoints |
59 | 75 | points = ( |
60 | 76 | alt.Chart(df_long) |
61 | 77 | .mark_circle(size=200, opacity=0.9) |
62 | | - .encode( |
63 | | - x="Period:N", |
64 | | - y="Sales:Q", |
65 | | - color=alt.Color( |
66 | | - "Direction:N", scale=alt.Scale(domain=["Increase", "Decrease"], range=["#306998", "#FFD43B"]), legend=None |
67 | | - ), |
68 | | - ) |
| 78 | + .encode(x="Period:N", y="Sales:Q", color=alt.Color("Direction:N", scale=color_scale, legend=None)) |
69 | 79 | ) |
70 | 80 |
|
71 | | -# Add labels at left endpoint (Q1) |
72 | 81 | labels_left = ( |
73 | 82 | alt.Chart(df_long[df_long["Period"] == "Q1 Sales"]) |
74 | 83 | .mark_text(align="right", dx=-15, fontSize=16) |
75 | | - .encode( |
76 | | - x="Period:N", |
77 | | - y="Sales:Q", |
78 | | - text="Product:N", |
79 | | - color=alt.Color( |
80 | | - "Direction:N", scale=alt.Scale(domain=["Increase", "Decrease"], range=["#306998", "#FFD43B"]), legend=None |
81 | | - ), |
82 | | - ) |
| 84 | + .encode(x="Period:N", y="Sales:Q", text="Product:N", color=alt.Color("Direction:N", scale=color_scale, legend=None)) |
83 | 85 | ) |
84 | 86 |
|
85 | | -# Add labels at right endpoint (Q4) |
86 | 87 | labels_right = ( |
87 | 88 | alt.Chart(df_long[df_long["Period"] == "Q4 Sales"]) |
88 | 89 | .mark_text(align="left", dx=15, fontSize=16) |
89 | | - .encode( |
90 | | - x="Period:N", |
91 | | - y="Sales:Q", |
92 | | - text="Product:N", |
93 | | - color=alt.Color( |
94 | | - "Direction:N", scale=alt.Scale(domain=["Increase", "Decrease"], range=["#306998", "#FFD43B"]), legend=None |
95 | | - ), |
96 | | - ) |
| 90 | + .encode(x="Period:N", y="Sales:Q", text="Product:N", color=alt.Color("Direction:N", scale=color_scale, legend=None)) |
97 | 91 | ) |
98 | 92 |
|
99 | | -# Combine layers |
| 93 | +# Style |
100 | 94 | chart = ( |
101 | 95 | (lines + points + labels_left + labels_right) |
102 | | - .properties( |
103 | | - width=1400, height=850, title=alt.Title(text="slope-basic · altair · pyplots.ai", fontSize=28, anchor="middle") |
| 96 | + .properties(width=1400, height=850, background=PAGE_BG, title="slope-basic · altair · anyplot.ai") |
| 97 | + .configure_title(color=INK, fontSize=28, anchor="middle") |
| 98 | + .configure_axis( |
| 99 | + domainColor=INK_SOFT, |
| 100 | + tickColor=INK_SOFT, |
| 101 | + grid=True, |
| 102 | + gridColor=INK, |
| 103 | + gridOpacity=0.10, |
| 104 | + gridDash=[4, 4], |
| 105 | + labelColor=INK_SOFT, |
| 106 | + titleColor=INK, |
104 | 107 | ) |
105 | | - .configure_axis(grid=True, gridOpacity=0.3, gridDash=[4, 4]) |
106 | | - .configure_view(strokeWidth=0) |
| 108 | + .configure_view(fill=PAGE_BG, stroke=INK_SOFT) |
| 109 | + .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) |
107 | 110 | ) |
108 | 111 |
|
109 | | -# Save as PNG (4800 × 2700 px) and HTML |
110 | | -chart.save("plot.png", scale_factor=3.0) |
111 | | -chart.save("plot.html") |
| 112 | +# Save |
| 113 | +chart.save(f"plot-{THEME}.png", scale_factor=3.0) |
| 114 | +chart.save(f"plot-{THEME}.html") |
0 commit comments