|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | rose-basic: Basic Rose Chart |
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: 84/100 | Updated: 2026-04-30 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
| 10 | + |
| 11 | +# Remove this script's directory from sys.path to prevent bokeh.py from |
| 12 | +# shadowing the installed bokeh package when Python adds the script dir. |
| 13 | +_script_dir = os.path.dirname(os.path.abspath(__file__)) |
| 14 | +sys.path = [p for p in sys.path if os.path.abspath(p or ".") != _script_dir] |
| 15 | + |
7 | 16 | import numpy as np |
8 | 17 | from bokeh.io import export_png, output_file, save |
9 | 18 | from bokeh.models import ColumnDataSource |
10 | 19 | from bokeh.plotting import figure |
11 | 20 |
|
12 | 21 |
|
| 22 | +# Theme tokens |
| 23 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 24 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 25 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 26 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 27 | +BRAND = "#009E73" |
| 28 | + |
13 | 29 | # Data - Monthly rainfall (mm) showing seasonal patterns |
14 | | -np.random.seed(42) |
15 | 30 | months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
16 | 31 | values = [85, 70, 65, 45, 30, 20, 15, 25, 40, 60, 75, 90] |
17 | | - |
| 32 | +max_val = max(values) |
18 | 33 | n = len(months) |
| 34 | +angle_width = 2 * np.pi / n |
19 | 35 |
|
20 | 36 | # Calculate wedge angles (equal slices, starting from top/north) |
21 | | -angle_width = 2 * np.pi / n |
22 | 37 | start_angles = np.array([np.pi / 2 - angle_width / 2 - i * angle_width for i in range(n)]) |
23 | 38 | end_angles = start_angles - angle_width |
| 39 | +center_angles = (start_angles + end_angles) / 2 |
24 | 40 |
|
25 | | -# Normalize values to radius (max value = 1.0 for full radius) |
26 | | -max_val = max(values) |
| 41 | +# Normalize values to radius (max value = 1.0) |
27 | 42 | radii = [v / max_val for v in values] |
28 | 43 |
|
29 | | -# Colors - gradient from Python Yellow to Python Blue based on value |
30 | | -colors = ["#306998" if v >= 60 else "#4A89B8" if v >= 40 else "#7AB3D8" if v >= 25 else "#FFD43B" for v in values] |
| 44 | +# Brand green with alpha varying by rainfall intensity |
| 45 | +alphas = [0.35 + 0.65 * (v / max_val) for v in values] |
31 | 46 |
|
32 | 47 | source = ColumnDataSource( |
33 | | - data={ |
34 | | - "start_angle": start_angles, |
35 | | - "end_angle": end_angles, |
36 | | - "radius": radii, |
37 | | - "months": months, |
38 | | - "values": values, |
39 | | - "colors": colors, |
40 | | - } |
| 48 | + data={"start_angle": start_angles, "end_angle": end_angles, "radius": radii, "alphas": alphas} |
41 | 49 | ) |
42 | 50 |
|
43 | | -# Create figure with matching x/y ranges for circular shape |
| 51 | +# Create figure |
44 | 52 | p = figure( |
45 | 53 | width=4800, |
46 | 54 | height=2700, |
47 | | - title="Monthly Rainfall · rose-basic · bokeh · pyplots.ai", |
48 | | - x_range=(-1.3, 1.3), |
49 | | - y_range=(-1.2, 1.1), |
| 55 | + title="Monthly Rainfall · rose-basic · bokeh · anyplot.ai", |
| 56 | + x_range=(-1.5, 1.5), |
| 57 | + y_range=(-1.3, 1.35), |
50 | 58 | tools="", |
51 | 59 | toolbar_location=None, |
52 | 60 | ) |
|
59 | 67 | start_angle="end_angle", |
60 | 68 | end_angle="start_angle", |
61 | 69 | source=source, |
62 | | - fill_color="colors", |
63 | | - fill_alpha=0.8, |
64 | | - line_color="white", |
| 70 | + fill_color=BRAND, |
| 71 | + fill_alpha="alphas", |
| 72 | + line_color=PAGE_BG, |
65 | 73 | line_width=2, |
66 | 74 | ) |
67 | 75 |
|
68 | | -# Add radial gridlines (concentric circles) |
| 76 | +# Radial gridlines (concentric circles) |
| 77 | +theta = np.linspace(0, 2 * np.pi, 200) |
69 | 78 | for r in [0.25, 0.5, 0.75, 1.0]: |
70 | | - theta = np.linspace(0, 2 * np.pi, 100) |
71 | | - p.line(r * np.cos(theta), r * np.sin(theta), line_color="gray", line_alpha=0.3, line_width=1, line_dash="dashed") |
| 79 | + p.line(r * np.cos(theta), r * np.sin(theta), line_color=INK, line_alpha=0.22, line_width=1.5, line_dash="dashed") |
72 | 80 |
|
73 | | -# Add radial lines from center |
| 81 | +# Radial divider lines from center |
74 | 82 | for i in range(n): |
75 | 83 | angle = np.pi / 2 - i * angle_width |
76 | | - p.line([0, 1.05 * np.cos(angle)], [0, 1.05 * np.sin(angle)], line_color="gray", line_alpha=0.2, line_width=1) |
| 84 | + p.line([0, 1.05 * np.cos(angle)], [0, 1.05 * np.sin(angle)], line_color=INK, line_alpha=0.18, line_width=1) |
77 | 85 |
|
78 | | -# Add month labels around the outside |
79 | | -label_radius = 1.12 |
| 86 | +# Month labels centered in each wedge |
| 87 | +label_radius = 1.15 |
80 | 88 | for i, month in enumerate(months): |
81 | | - angle = np.pi / 2 - i * angle_width |
82 | | - x = label_radius * np.cos(angle) |
83 | | - y = label_radius * np.sin(angle) |
| 89 | + angle = center_angles[i] |
84 | 90 | p.text( |
85 | | - x=[x], |
86 | | - y=[y], |
| 91 | + x=[label_radius * np.cos(angle)], |
| 92 | + y=[label_radius * np.sin(angle)], |
87 | 93 | text=[month], |
88 | 94 | text_align="center", |
89 | 95 | text_baseline="middle", |
90 | | - text_font_size="18pt", |
91 | | - text_color="#333333", |
| 96 | + text_font_size="20pt", |
| 97 | + text_color=INK, |
92 | 98 | ) |
93 | 99 |
|
94 | | -# Add value scale labels on right side |
95 | | -p.text(x=[1.05], y=[0.25], text=["25%"], text_font_size="14pt", text_color="gray", text_align="left") |
96 | | -p.text(x=[1.05], y=[0.5], text=["50%"], text_font_size="14pt", text_color="gray", text_align="left") |
97 | | -p.text(x=[1.05], y=[0.75], text=["75%"], text_font_size="14pt", text_color="gray", text_align="left") |
98 | | -p.text(x=[1.05], y=[1.0], text=["100%"], text_font_size="14pt", text_color="gray", text_align="left") |
| 100 | +# Rainfall scale labels (actual mm values, right side) |
| 101 | +for r in [0.25, 0.5, 0.75, 1.0]: |
| 102 | + val_label = f"{int(r * max_val + 0.5)} mm" |
| 103 | + p.text(x=[1.2], y=[r], text=[val_label], text_font_size="15pt", text_color=INK_SOFT, text_align="left") |
99 | 104 |
|
100 | | -# Styling |
| 105 | +# Title and chrome |
101 | 106 | p.title.text_font_size = "28pt" |
102 | 107 | p.title.align = "center" |
103 | | -p.background_fill_color = "white" |
104 | | -p.border_fill_color = "white" |
| 108 | +p.title.text_color = INK |
| 109 | +p.title.text_font_style = "normal" |
| 110 | + |
| 111 | +p.background_fill_color = PAGE_BG |
| 112 | +p.border_fill_color = PAGE_BG |
105 | 113 | p.outline_line_color = None |
106 | 114 |
|
107 | 115 | # Hide axes (not needed for rose chart) |
108 | 116 | p.axis.visible = False |
109 | 117 | p.grid.visible = False |
110 | 118 |
|
111 | | -# Save as PNG and HTML |
112 | | -export_png(p, filename="plot.png") |
113 | | -output_file("plot.html") |
| 119 | +# Save |
| 120 | +export_png(p, filename=f"plot-{THEME}.png") |
| 121 | +output_file(f"plot-{THEME}.html") |
114 | 122 | save(p) |
0 commit comments