Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 50 additions & 22 deletions plots/dumbbell-basic/implementations/python/altair.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
""" anyplot.ai
dumbbell-basic: Basic Dumbbell Chart
Library: altair 6.1.0 | Python 3.14.4
Quality: 89/100 | Updated: 2026-04-26
Library: altair 6.2.2 | Python 3.13.14
Quality: 92/100 | Updated: 2026-06-30
"""

import os

import altair as alt
import pandas as pd
from PIL import Image


# Theme-adaptive chrome
Expand All @@ -17,7 +18,7 @@
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Okabe-Ito palette positions 1 and 2
# Imprint palette positions 1 and 2
COLOR_BEFORE = "#009E73"
COLOR_AFTER = "#C475FD"

Expand All @@ -41,67 +42,94 @@
}
)
data["difference"] = data["After"] - data["Before"]
data = data.sort_values("difference", ascending=True)
data = data.sort_values("difference", ascending=True).reset_index(drop=True)

# Long-form data for the two dot series
dots_data = pd.melt(
data, id_vars=["category", "difference"], value_vars=["Before", "After"], var_name="period", value_name="score"
)

x_scale = alt.Scale(domain=[45, 90])
title = "Employee Satisfaction · dumbbell-basic · python · altair · anyplot.ai"
n = len(title)
title_fontsize = round(16 * (67 / n)) if n > 67 else 16

x_scale = alt.Scale(domain=[45, 92])
y_sort = alt.EncodingSortField(field="difference", order="ascending")

# Connecting lines (theme-adaptive subtle ink)
lines = (
alt.Chart(data)
.mark_rule(strokeWidth=3, color=INK_SOFT, opacity=0.55)
.mark_rule(strokeWidth=3, color=INK_SOFT, opacity=0.45)
.encode(y=alt.Y("category:N", sort=y_sort, title=None), x=alt.X("Before:Q", scale=x_scale), x2=alt.X2("After:Q"))
)

# Dots for Before / After values
dots = (
alt.Chart(dots_data)
.mark_circle(size=420, opacity=1.0, stroke=PAGE_BG, strokeWidth=2)
.mark_circle(size=350, opacity=1.0, stroke=PAGE_BG, strokeWidth=2)
.encode(
y=alt.Y("category:N", sort=y_sort, title=None),
x=alt.X("score:Q", scale=x_scale, title="Employee Satisfaction Score (%)"),
color=alt.Color(
"period:N",
scale=alt.Scale(domain=["Before", "After"], range=[COLOR_BEFORE, COLOR_AFTER]),
legend=alt.Legend(title="Policy Change", labelFontSize=16, titleFontSize=18),
legend=alt.Legend(title="Policy Change", labelFontSize=10, titleFontSize=12),
),
tooltip=["category:N", "period:N", "score:Q"],
)
)

# Difference labels via transform_calculate — shows the gain at a glance
diff_labels = (
alt.Chart(data)
.transform_calculate(label="'+' + toString(datum.difference) + ' pts'")
.mark_text(align="left", dx=8, fontSize=11, fontWeight="bold", clip=False)
.encode(
y=alt.Y("category:N", sort=y_sort, title=None),
x=alt.X("After:Q", scale=x_scale),
text=alt.Text("label:N"),
color=alt.value(INK_SOFT),
)
)

chart = (
(lines + dots)
(lines + dots + diff_labels)
.properties(
width=1600,
height=900,
title=alt.Title(
"Employee Satisfaction · dumbbell-basic · altair · anyplot.ai",
fontSize=28,
color=INK,
anchor="start",
offset=20,
),
width=576,
height=374,
title=alt.Title(title, fontSize=title_fontsize, color=INK, anchor="start", offset=16),
background=PAGE_BG,
)
.configure_view(fill=PAGE_BG, stroke=None)
.configure_axis(
labelFontSize=18,
titleFontSize=22,
labelFontSize=10,
titleFontSize=12,
domainColor=INK_SOFT,
domainOpacity=0,
tickColor=INK_SOFT,
gridColor=INK,
gridOpacity=0.10,
labelColor=INK_SOFT,
titleColor=INK,
)
.configure_title(color=INK)
.configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK, padding=12)
.configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK, padding=10)
)

chart.save(f"plot-{THEME}.png", scale_factor=3.0)
chart.save(f"plot-{THEME}.png", scale_factor=4.0)

# Pad to exact 3200×1800 canvas (vl-convert pads outside width/height)
TW, TH = 3200, 1800
_img = Image.open(f"plot-{THEME}.png").convert("RGB")
_w, _h = _img.size
if _w > TW or _h > TH:
raise SystemExit(
f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. "
f"Shrink chart .properties(width=, height=) values and re-render."
)
if _w < TW or _h < TH:
_canvas = Image.new("RGB", (TW, TH), PAGE_BG)
_canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))
_canvas.save(f"plot-{THEME}.png")

chart.save(f"plot-{THEME}.html")
Loading
Loading