|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | span-basic: Basic Span Plot (Highlighted Region) |
3 | | -Library: bokeh 3.8.1 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: bokeh 3.9.0 | Python 3.13.13 |
| 4 | +Quality: 89/100 | Updated: 2026-04-30 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import numpy as np |
8 | 10 | from bokeh.io import export_png, output_file, save |
9 | | -from bokeh.models import BoxAnnotation, ColumnDataSource, Label |
| 11 | +from bokeh.models import BoxAnnotation, ColumnDataSource, HoverTool, Label |
10 | 12 | from bokeh.plotting import figure |
11 | 13 |
|
12 | 14 |
|
| 15 | +# Theme tokens |
| 16 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 17 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 18 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 19 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 20 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 21 | +BRAND = "#009E73" # Okabe-Ito position 1 — always first series |
| 22 | + |
13 | 23 | # Data - Monthly revenue over 2 years with spans highlighting key periods |
14 | 24 | np.random.seed(42) |
15 | 25 | months = np.arange(1, 25) |
|
24 | 34 | p = figure( |
25 | 35 | width=4800, |
26 | 36 | height=2700, |
27 | | - title="span-basic · bokeh · pyplots.ai", |
| 37 | + title="span-basic · bokeh · anyplot.ai", |
28 | 38 | x_axis_label="Month", |
29 | 39 | y_axis_label="Revenue (thousands $)", |
30 | 40 | ) |
31 | 41 |
|
32 | 42 | # Add vertical span - highlight Q4 of Year 1 (months 10-12) |
33 | 43 | vertical_span = BoxAnnotation( |
34 | | - left=10, right=12, fill_alpha=0.25, fill_color="#306998", line_color="#306998", line_width=2, line_alpha=0.5 |
| 44 | + left=10, right=12, fill_alpha=0.25, fill_color="#0072B2", line_color="#0072B2", line_width=2, line_alpha=0.5 |
35 | 45 | ) |
36 | 46 | p.add_layout(vertical_span) |
37 | 47 |
|
38 | 48 | # Add horizontal span - highlight target revenue range (120-140) |
39 | 49 | horizontal_span = BoxAnnotation( |
40 | | - bottom=120, top=140, fill_alpha=0.2, fill_color="#FFD43B", line_color="#FFD43B", line_width=2, line_alpha=0.5 |
| 50 | + bottom=120, top=140, fill_alpha=0.2, fill_color="#E69F00", line_color="#E69F00", line_width=2, line_alpha=0.5 |
41 | 51 | ) |
42 | 52 | p.add_layout(horizontal_span) |
43 | 53 |
|
44 | | -# Plot line with markers |
45 | | -p.line(x="x", y="y", source=source, line_width=4, line_color="#306998", legend_label="Monthly Revenue") |
46 | | -p.scatter(x="x", y="y", source=source, size=16, fill_color="#306998", line_color="white", line_width=2) |
| 54 | +# Plot line with markers (Okabe-Ito position 1) |
| 55 | +p.line(x="x", y="y", source=source, line_width=4, line_color=BRAND, legend_label="Monthly Revenue") |
| 56 | +p.scatter(x="x", y="y", source=source, size=20, fill_color=BRAND, line_color=PAGE_BG, line_width=2) |
| 57 | + |
| 58 | +# HoverTool — showcases Bokeh's interactive HTML output |
| 59 | +hover = HoverTool(tooltips=[("Month", "@x"), ("Revenue", "@y{0.1} K$")]) |
| 60 | +p.add_tools(hover) |
47 | 61 |
|
48 | | -# Add labels for spans |
| 62 | +# Add labels for spans — positioned prominently at top of each span |
49 | 63 | vertical_label = Label( |
50 | | - x=10.2, y=102, text="Q4 Peak Season", text_font_size="24pt", text_color="#1a4d7c", text_font_style="bold" |
| 64 | + x=10.2, y=143, text="Q4 Peak Season", text_font_size="28pt", text_color="#0072B2", text_font_style="bold" |
51 | 65 | ) |
52 | 66 | p.add_layout(vertical_label) |
53 | 67 |
|
54 | 68 | horizontal_label = Label( |
55 | | - x=17, y=125, text="Target Range", text_font_size="28pt", text_color="#B8860B", text_font_style="bold" |
| 69 | + x=17, |
| 70 | + y=124, |
| 71 | + text="Target Range", |
| 72 | + text_font_size="28pt", |
| 73 | + text_color="#B8720B" if THEME == "light" else "#E69F00", |
| 74 | + text_font_style="bold", |
56 | 75 | ) |
57 | 76 | p.add_layout(horizontal_label) |
58 | 77 |
|
59 | | -# Style text sizes for 4800x2700 px |
| 78 | +# Apply theme-adaptive chrome |
| 79 | +p.background_fill_color = PAGE_BG |
| 80 | +p.border_fill_color = PAGE_BG |
| 81 | +p.outline_line_color = None # remove box border; L-shaped spines via xaxis/yaxis lines only |
| 82 | + |
| 83 | +p.title.text_color = INK |
60 | 84 | p.title.text_font_size = "48pt" |
| 85 | +p.xaxis.axis_label_text_color = INK |
| 86 | +p.yaxis.axis_label_text_color = INK |
61 | 87 | p.xaxis.axis_label_text_font_size = "36pt" |
62 | 88 | p.yaxis.axis_label_text_font_size = "36pt" |
| 89 | +p.xaxis.major_label_text_color = INK_SOFT |
| 90 | +p.yaxis.major_label_text_color = INK_SOFT |
63 | 91 | p.xaxis.major_label_text_font_size = "28pt" |
64 | 92 | p.yaxis.major_label_text_font_size = "28pt" |
| 93 | +p.xaxis.axis_line_color = INK_SOFT |
| 94 | +p.yaxis.axis_line_color = INK_SOFT |
| 95 | +p.xaxis.major_tick_line_color = INK_SOFT |
| 96 | +p.yaxis.major_tick_line_color = INK_SOFT |
65 | 97 |
|
66 | | -# Grid styling |
67 | | -p.grid.grid_line_alpha = 0.3 |
68 | | -p.grid.grid_line_dash = "dashed" |
| 98 | +p.xgrid.grid_line_color = None # y-only grid preferred for line charts |
| 99 | +p.ygrid.grid_line_color = INK |
| 100 | +p.ygrid.grid_line_alpha = 0.10 |
69 | 101 |
|
70 | | -# Legend styling |
| 102 | +# Legend — top-left for better visual balance |
71 | 103 | p.legend.label_text_font_size = "28pt" |
72 | | -p.legend.location = "bottom_right" |
73 | | -p.legend.background_fill_alpha = 0.7 |
74 | | - |
75 | | -# Save as PNG and HTML |
76 | | -export_png(p, filename="plot.png") |
| 104 | +p.legend.location = "top_left" |
| 105 | +p.legend.background_fill_color = ELEVATED_BG |
| 106 | +p.legend.border_line_color = INK_SOFT |
| 107 | +p.legend.label_text_color = INK_SOFT |
77 | 108 |
|
78 | | -output_file("plot.html") |
| 109 | +# Save |
| 110 | +export_png(p, filename=f"plot-{THEME}.png") |
| 111 | +output_file(f"plot-{THEME}.html") |
79 | 112 | save(p) |
0 commit comments