|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | errorbar-basic: Basic Error Bar Plot |
3 | | -Library: altair 6.0.0 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: altair 6.1.0 | Python 3.14.4 |
| 4 | +Quality: 88/100 | Updated: 2026-04-25 |
5 | 5 | """ |
6 | 6 |
|
7 | | -import altair as alt |
8 | | -import numpy as np |
9 | | -import pandas as pd |
| 7 | +import importlib |
| 8 | +import os |
| 9 | +import sys |
10 | 10 |
|
11 | 11 |
|
12 | | -# Data - experimental measurements with associated uncertainties |
| 12 | +# Drop script directory from sys.path so the `altair` package resolves, not this file |
| 13 | +sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != os.path.dirname(os.path.abspath(__file__))] |
| 14 | +alt = importlib.import_module("altair") |
| 15 | +np = importlib.import_module("numpy") |
| 16 | +pd = importlib.import_module("pandas") |
| 17 | + |
| 18 | + |
| 19 | +# Theme tokens |
| 20 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 21 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 22 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 23 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 24 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 25 | +BRAND = "#009E73" # Okabe-Ito position 1 |
| 26 | + |
| 27 | +# Data |
13 | 28 | np.random.seed(42) |
14 | 29 | categories = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"] |
15 | 30 | y_values = [25.3, 38.7, 42.1, 35.8, 48.2, 31.5] |
16 | 31 |
|
17 | | -# Asymmetric errors: Treatment C and D have notably different lower/upper bounds |
| 32 | +# Asymmetric errors: Treatment C and D show notably different lower/upper bounds |
18 | 33 | asymmetric_lower = [2.1, 3.5, 2.8, 6.5, 4.8, 2.5] |
19 | 34 | asymmetric_upper = [2.1, 3.5, 2.8, 2.8, 2.2, 2.5] |
20 | 35 |
|
21 | | -# Create DataFrame with error bounds |
22 | 36 | df = pd.DataFrame( |
23 | 37 | { |
24 | 38 | "category": categories, |
|
28 | 42 | } |
29 | 43 | ) |
30 | 44 |
|
31 | | -# Base chart with shared properties |
32 | | -base = alt.Chart(df).encode( |
33 | | - x=alt.X( |
34 | | - "category:N", |
35 | | - title="Experimental Group", |
36 | | - sort=categories, |
37 | | - axis=alt.Axis(labelFontSize=18, titleFontSize=22, labelAngle=0), |
38 | | - ) |
39 | | -) |
| 45 | +# Plot |
| 46 | +y_scale = alt.Scale(domain=[15, 55], nice=False) |
| 47 | +y_title = "Response Value (units)" |
40 | 48 |
|
41 | | -# Points for the central values |
42 | | -points = base.mark_circle(size=300, color="#306998").encode( |
43 | | - y=alt.Y( |
44 | | - "value:Q", |
45 | | - title="Response Value (units)", |
46 | | - scale=alt.Scale(domain=[0, 55]), |
47 | | - axis=alt.Axis(labelFontSize=18, titleFontSize=22, grid=True, gridOpacity=0.3, gridDash=[4, 4]), |
48 | | - ) |
49 | | -) |
| 49 | +base = alt.Chart(df).encode(x=alt.X("category:N", title="Experimental Group", sort=categories)) |
50 | 50 |
|
51 | | -# Error bars using mark_rule with y and y2 |
52 | | -error_bars = base.mark_rule(strokeWidth=3, color="#306998").encode(y=alt.Y("error_lower:Q"), y2="error_upper:Q") |
| 51 | +error_bars = base.mark_rule(strokeWidth=3, color=BRAND).encode( |
| 52 | + y=alt.Y("error_lower:Q", title=y_title, scale=y_scale), y2="error_upper:Q" |
| 53 | +) |
53 | 54 |
|
54 | | -# Error bar caps (top) |
55 | | -caps_top = base.mark_tick(thickness=3, size=20, color="#306998").encode(y="error_upper:Q") |
| 55 | +caps_top = base.mark_tick(thickness=3, size=22, color=BRAND).encode( |
| 56 | + y=alt.Y("error_upper:Q", title=y_title, scale=y_scale) |
| 57 | +) |
| 58 | +caps_bottom = base.mark_tick(thickness=3, size=22, color=BRAND).encode( |
| 59 | + y=alt.Y("error_lower:Q", title=y_title, scale=y_scale) |
| 60 | +) |
56 | 61 |
|
57 | | -# Error bar caps (bottom) |
58 | | -caps_bottom = base.mark_tick(thickness=3, size=20, color="#306998").encode(y="error_lower:Q") |
| 62 | +points = base.mark_circle(size=320, color=BRAND).encode( |
| 63 | + y=alt.Y("value:Q", title=y_title, scale=y_scale), |
| 64 | + tooltip=[ |
| 65 | + alt.Tooltip("category:N", title="Group"), |
| 66 | + alt.Tooltip("value:Q", title="Mean", format=".2f"), |
| 67 | + alt.Tooltip("error_lower:Q", title="Lower bound", format=".2f"), |
| 68 | + alt.Tooltip("error_upper:Q", title="Upper bound", format=".2f"), |
| 69 | + ], |
| 70 | +) |
59 | 71 |
|
60 | | -# Layer all elements |
61 | 72 | chart = ( |
62 | 73 | alt.layer(error_bars, caps_bottom, caps_top, points) |
63 | | - .properties(width=1600, height=900, title=alt.Title("errorbar-basic · altair · pyplots.ai", fontSize=28)) |
64 | | - .configure_axis(labelFontSize=18, titleFontSize=22) |
65 | | - .configure_view(strokeWidth=0) |
| 74 | + .properties( |
| 75 | + width=1600, |
| 76 | + height=900, |
| 77 | + background=PAGE_BG, |
| 78 | + title=alt.Title("errorbar-basic · altair · anyplot.ai", fontSize=28, color=INK, anchor="start", offset=20), |
| 79 | + ) |
| 80 | + .configure_view(fill=PAGE_BG, stroke=None) |
| 81 | + .configure_axis( |
| 82 | + labelFontSize=18, |
| 83 | + titleFontSize=22, |
| 84 | + labelColor=INK_SOFT, |
| 85 | + titleColor=INK, |
| 86 | + domainColor=INK_SOFT, |
| 87 | + tickColor=INK_SOFT, |
| 88 | + gridColor=INK, |
| 89 | + gridOpacity=0.10, |
| 90 | + labelAngle=0, |
| 91 | + ) |
| 92 | + .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) |
66 | 93 | ) |
67 | 94 |
|
68 | | -# Save |
69 | | -chart.save("plot.png", scale_factor=3.0) |
70 | | -chart.save("plot.html") |
| 95 | +chart.save(f"plot-{THEME}.png", scale_factor=3.0) |
| 96 | +chart.save(f"plot-{THEME}.html") |
0 commit comments