Skip to content

Commit 76debfd

Browse files
github-actions[bot]claude[bot]MarkusNeusinger
authored
feat(altair): implement slope-basic (#5642)
## Implementation: `slope-basic` - python/altair Implements the **python/altair** version of `slope-basic`. **File:** `plots/slope-basic/implementations/python/altair.py` **Parent Issue:** #981 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25177483606)* --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> 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 abd3785 commit 76debfd

2 files changed

Lines changed: 266 additions & 71 deletions

File tree

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
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
55
"""
66

7-
import altair as alt
7+
import os
8+
import sys
9+
810
import pandas as pd
911

1012

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
1232
data = pd.DataFrame(
1333
{
1434
"Product": [
@@ -28,84 +48,67 @@
2848
}
2949
)
3050

31-
# Reshape data to long format for slope chart
3251
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
3552
data["Direction"] = data.apply(lambda row: "Increase" if row["Q4 Sales"] > row["Q1 Sales"] else "Decrease", axis=1)
3653
df_long = df_long.merge(data[["Product", "Direction"]], on="Product")
3754

38-
# Create slope chart
55+
color_scale = alt.Scale(domain=["Increase", "Decrease"], range=[COLOR_INCREASE, COLOR_DECREASE])
56+
57+
# Plot
3958
lines = (
4059
alt.Chart(df_long)
4160
.mark_line(strokeWidth=3, opacity=0.8)
4261
.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)),
4463
y=alt.Y(
4564
"Sales:Q",
4665
axis=alt.Axis(labelFontSize=18, titleFontSize=22, title="Sales (units)"),
4766
scale=alt.Scale(zero=False),
4867
),
4968
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")
5370
),
5471
detail="Product:N",
5572
)
5673
)
5774

58-
# Add points at endpoints
5975
points = (
6076
alt.Chart(df_long)
6177
.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))
6979
)
7080

71-
# Add labels at left endpoint (Q1)
7281
labels_left = (
7382
alt.Chart(df_long[df_long["Period"] == "Q1 Sales"])
7483
.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))
8385
)
8486

85-
# Add labels at right endpoint (Q4)
8687
labels_right = (
8788
alt.Chart(df_long[df_long["Period"] == "Q4 Sales"])
8889
.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))
9791
)
9892

99-
# Combine layers
93+
# Style
10094
chart = (
10195
(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,
104107
)
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)
107110
)
108111

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

Comments
 (0)