|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
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 |
10 | 10 |
|
11 | 11 |
|
| 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 | + |
12 | 30 | # Data - bivariate distribution with correlation |
13 | 31 | np.random.seed(42) |
14 | 32 | n = 150 |
|
21 | 39 | x_domain = [df["X Value"].min() - 2, df["X Value"].max() + 2] |
22 | 40 | y_domain = [df["Y Value"].min() - 2, df["Y Value"].max() + 2] |
23 | 41 |
|
24 | | -# Interactive brush selection |
25 | | -brush = alt.param(name="brush", select="interval") |
26 | | - |
27 | 42 | # Base chart |
28 | 43 | base = alt.Chart(df) |
29 | 44 |
|
30 | | -# Main scatter plot with selection |
| 45 | +# Main scatter plot with grid |
31 | 46 | scatter = ( |
32 | | - base.mark_circle(size=120, opacity=0.65) |
| 47 | + base.mark_circle(size=120, opacity=0.65, color=BRAND) |
33 | 48 | .encode( |
34 | 49 | x=alt.X("X Value:Q", title="X Value (units)", scale=alt.Scale(domain=x_domain)), |
35 | 50 | 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")), |
37 | 51 | tooltip=["X Value:Q", "Y Value:Q"], |
38 | 52 | ) |
39 | 53 | .properties(width=1000, height=600) |
40 | | - .add_params(brush) |
41 | 54 | ) |
42 | 55 |
|
43 | 56 | # Top marginal histogram with matching X scale |
44 | 57 | top_hist = ( |
45 | | - base.mark_bar(color="#306998", opacity=0.5) |
| 58 | + base.mark_bar(color=BRAND, opacity=0.5) |
46 | 59 | .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 | + ), |
48 | 67 | y=alt.Y("count()", title=None, axis=alt.Axis(labels=False, ticks=False)), |
49 | 68 | ) |
50 | 69 | .properties(width=1000, height=120) |
51 | 70 | ) |
52 | 71 |
|
53 | 72 | # Right marginal histogram with matching Y scale |
54 | 73 | right_hist = ( |
55 | | - base.mark_bar(color="#306998", opacity=0.5) |
| 74 | + base.mark_bar(color=BRAND, opacity=0.5) |
56 | 75 | .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 | + ), |
58 | 83 | x=alt.X("count()", title=None, axis=alt.Axis(labels=False, ticks=False)), |
59 | 84 | ) |
60 | 85 | .properties(width=120, height=600) |
|
63 | 88 | # Combine: top histogram above, scatter with right histogram below |
64 | 89 | combined = ( |
65 | 90 | 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) |
70 | 106 | .configure_concat(spacing=5) |
71 | 107 | ) |
72 | 108 |
|
73 | 109 | # 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