Skip to content

Commit 5685fde

Browse files
feat(altair): implement scatter-marginal (#6128)
## Implementation: `scatter-marginal` - python/altair Implements the **python/altair** version of `scatter-marginal`. **File:** `plots/scatter-marginal/implementations/python/altair.py` **Parent Issue:** #2005 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25592784974)* --------- 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 490ba3f commit 5685fde

2 files changed

Lines changed: 211 additions & 157 deletions

File tree

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
scatter-marginal: Scatter Plot with Marginal Distributions
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-26
3+
Library: altair 6.1.0 | Python 3.13.13
4+
Quality: 94/100 | Updated: 2026-05-09
55
"""
66

7-
import altair as alt
8-
import numpy as np
9-
import pandas as pd
7+
import os
8+
import sys
9+
from pathlib import Path
1010

1111

12+
current_dir = Path(__file__).parent
13+
sys.path = [p for p in sys.path if p != str(current_dir)]
14+
15+
import altair as alt # noqa: E402
16+
import numpy as np # noqa: E402
17+
import pandas as pd # 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 palette
28+
BRAND = "#009E73"
29+
1230
# Data - bivariate distribution with correlation
1331
np.random.seed(42)
1432
n = 150
@@ -21,40 +39,47 @@
2139
x_domain = [df["X Value"].min() - 2, df["X Value"].max() + 2]
2240
y_domain = [df["Y Value"].min() - 2, df["Y Value"].max() + 2]
2341

24-
# Interactive brush selection
25-
brush = alt.param(name="brush", select="interval")
26-
2742
# Base chart
2843
base = alt.Chart(df)
2944

30-
# Main scatter plot with selection
45+
# Main scatter plot with grid
3146
scatter = (
32-
base.mark_circle(size=120, opacity=0.65)
47+
base.mark_circle(size=120, opacity=0.65, color=BRAND)
3348
.encode(
3449
x=alt.X("X Value:Q", title="X Value (units)", scale=alt.Scale(domain=x_domain)),
3550
y=alt.Y("Y Value:Q", title="Y Value (units)", scale=alt.Scale(domain=y_domain)),
36-
color=alt.condition(brush, alt.value("#306998"), alt.value("lightgray")),
3751
tooltip=["X Value:Q", "Y Value:Q"],
3852
)
3953
.properties(width=1000, height=600)
40-
.add_params(brush)
4154
)
4255

4356
# Top marginal histogram with matching X scale
4457
top_hist = (
45-
base.mark_bar(color="#306998", opacity=0.5)
58+
base.mark_bar(color=BRAND, opacity=0.5)
4659
.encode(
47-
x=alt.X("X Value:Q", bin=alt.Bin(maxbins=25), title=None, scale=alt.Scale(domain=x_domain), axis=None),
60+
x=alt.X(
61+
"X Value:Q",
62+
bin=alt.Bin(maxbins=25),
63+
title=None,
64+
scale=alt.Scale(domain=x_domain),
65+
axis=alt.Axis(labels=False, ticks=False),
66+
),
4867
y=alt.Y("count()", title=None, axis=alt.Axis(labels=False, ticks=False)),
4968
)
5069
.properties(width=1000, height=120)
5170
)
5271

5372
# Right marginal histogram with matching Y scale
5473
right_hist = (
55-
base.mark_bar(color="#306998", opacity=0.5)
74+
base.mark_bar(color=BRAND, opacity=0.5)
5675
.encode(
57-
y=alt.Y("Y Value:Q", bin=alt.Bin(maxbins=25), title=None, scale=alt.Scale(domain=y_domain), axis=None),
76+
y=alt.Y(
77+
"Y Value:Q",
78+
bin=alt.Bin(maxbins=25),
79+
title=None,
80+
scale=alt.Scale(domain=y_domain),
81+
axis=alt.Axis(labels=False, ticks=False),
82+
),
5883
x=alt.X("count()", title=None, axis=alt.Axis(labels=False, ticks=False)),
5984
)
6085
.properties(width=120, height=600)
@@ -63,13 +88,24 @@
6388
# Combine: top histogram above, scatter with right histogram below
6489
combined = (
6590
alt.vconcat(top_hist, alt.hconcat(scatter, right_hist, spacing=5), spacing=5)
66-
.properties(title=alt.Title(text="scatter-marginal · altair · pyplots.ai", fontSize=28, anchor="middle"))
67-
.configure_axis(labelFontSize=16, titleFontSize=20)
68-
.configure_title(fontSize=28)
69-
.configure_view(strokeWidth=0)
91+
.properties(
92+
background=PAGE_BG,
93+
title=alt.Title(text="scatter-marginal · altair · anyplot.ai", fontSize=28, anchor="middle", color=INK),
94+
)
95+
.configure_axis(
96+
domainColor=INK_SOFT,
97+
tickColor=INK_SOFT,
98+
gridColor=INK,
99+
gridOpacity=0.10,
100+
labelColor=INK_SOFT,
101+
labelFontSize=16,
102+
titleColor=INK,
103+
titleFontSize=20,
104+
)
105+
.configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=1)
70106
.configure_concat(spacing=5)
71107
)
72108

73109
# Save outputs
74-
combined.save("plot.png", scale_factor=3.0)
75-
combined.save("plot.html")
110+
combined.save(f"plot-{THEME}.png", scale_factor=3.0)
111+
combined.save(f"plot-{THEME}.html")

0 commit comments

Comments
 (0)