|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | pyramid-basic: Basic Pyramid Chart |
3 | | -Library: bokeh 3.8.1 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: bokeh 3.9.0 | Python 3.13.13 |
| 4 | +Quality: 90/100 | Updated: 2026-04-29 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | from bokeh.io import export_png, output_file, save |
8 | | -from bokeh.models import ColumnDataSource, Range1d |
| 10 | +from bokeh.models import ColumnDataSource, CustomJSTickFormatter, HoverTool, Range1d, Span |
9 | 11 | from bokeh.plotting import figure |
10 | 12 |
|
11 | 13 |
|
| 14 | +# Theme tokens |
| 15 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 16 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 17 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 18 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 19 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 20 | + |
| 21 | +MALE_COLOR = "#009E73" # Okabe-Ito position 1 |
| 22 | +FEMALE_COLOR = "#D55E00" # Okabe-Ito position 2 |
| 23 | + |
12 | 24 | # Data - Population by age group (in thousands) |
13 | 25 | age_groups = ["0-9", "10-19", "20-29", "30-39", "40-49", "50-59", "60-69", "70-79", "80+"] |
14 | 26 | male_population = [45, 52, 68, 82, 75, 65, 48, 32, 15] |
15 | 27 | female_population = [43, 50, 72, 85, 78, 70, 55, 40, 22] |
16 | | - |
17 | | -# Negate male values for left side of pyramid |
18 | 28 | male_negative = [-v for v in male_population] |
19 | 29 |
|
20 | | -# Create data sources |
21 | | -source_male = ColumnDataSource(data={"age": age_groups, "population": male_negative}) |
22 | | -source_female = ColumnDataSource(data={"age": age_groups, "population": female_population}) |
| 30 | +source_male = ColumnDataSource(data={"age": age_groups, "population": male_negative, "value": male_population}) |
| 31 | +source_female = ColumnDataSource(data={"age": age_groups, "population": female_population, "value": female_population}) |
23 | 32 |
|
24 | | -# Create figure with categorical y-axis and symmetric x-range |
| 33 | +# Plot |
25 | 34 | p = figure( |
26 | 35 | width=4800, |
27 | 36 | height=2700, |
28 | 37 | y_range=age_groups, |
29 | 38 | x_range=Range1d(-100, 100), |
30 | | - title="pyramid-basic · bokeh · pyplots.ai", |
| 39 | + title="pyramid-basic · bokeh · anyplot.ai", |
31 | 40 | x_axis_label="Population (thousands)", |
32 | 41 | y_axis_label="Age Group", |
33 | 42 | ) |
34 | 43 |
|
35 | | -# Draw horizontal bars - male (left, Python Blue) and female (right, coral pink) |
36 | 44 | bar_height = 0.7 |
37 | | -p.hbar( |
38 | | - y="age", right="population", height=bar_height, source=source_male, color="#306998", alpha=0.85, legend_label="Male" |
| 45 | +male_bars = p.hbar( |
| 46 | + y="age", |
| 47 | + right="population", |
| 48 | + height=bar_height, |
| 49 | + source=source_male, |
| 50 | + color=MALE_COLOR, |
| 51 | + alpha=0.85, |
| 52 | + legend_label="Male", |
39 | 53 | ) |
40 | | -p.hbar( |
| 54 | +female_bars = p.hbar( |
41 | 55 | y="age", |
42 | 56 | right="population", |
43 | 57 | height=bar_height, |
44 | 58 | source=source_female, |
45 | | - color="#E8888C", |
| 59 | + color=FEMALE_COLOR, |
46 | 60 | alpha=0.85, |
47 | 61 | legend_label="Female", |
48 | 62 | ) |
49 | 63 |
|
50 | | -# Text styling for 4800x2700 px canvas |
| 64 | +# HoverTools for interactivity |
| 65 | +p.add_tools(HoverTool(renderers=[male_bars], tooltips=[("Age Group", "@age"), ("Male", "@value{0,0} thousand")])) |
| 66 | +p.add_tools(HoverTool(renderers=[female_bars], tooltips=[("Age Group", "@age"), ("Female", "@value{0,0} thousand")])) |
| 67 | + |
| 68 | +# Center line at x=0 via Span — adapts to full plot height automatically |
| 69 | +center_line = Span(location=0, dimension="height", line_color=INK_SOFT, line_width=2, line_alpha=0.5) |
| 70 | +p.add_layout(center_line) |
| 71 | + |
| 72 | +# Show absolute values on x-axis (both sides positive) |
| 73 | +p.xaxis.formatter = CustomJSTickFormatter(code="return Math.abs(tick).toString()") |
| 74 | + |
| 75 | +# Style |
| 76 | +p.background_fill_color = PAGE_BG |
| 77 | +p.border_fill_color = PAGE_BG |
| 78 | +p.outline_line_color = INK_SOFT |
| 79 | + |
| 80 | +p.title.text_color = INK |
51 | 81 | p.title.text_font_size = "32pt" |
| 82 | +p.xaxis.axis_label_text_color = INK |
| 83 | +p.yaxis.axis_label_text_color = INK |
52 | 84 | p.xaxis.axis_label_text_font_size = "24pt" |
53 | 85 | p.yaxis.axis_label_text_font_size = "24pt" |
| 86 | +p.xaxis.major_label_text_color = INK_SOFT |
| 87 | +p.yaxis.major_label_text_color = INK_SOFT |
54 | 88 | p.xaxis.major_label_text_font_size = "18pt" |
55 | 89 | p.yaxis.major_label_text_font_size = "18pt" |
| 90 | +p.xaxis.axis_line_color = INK_SOFT |
| 91 | +p.yaxis.axis_line_color = INK_SOFT |
| 92 | +p.xaxis.major_tick_line_color = INK_SOFT |
| 93 | +p.yaxis.major_tick_line_color = INK_SOFT |
56 | 94 |
|
57 | | -# Grid styling - subtle dashed lines |
58 | | -p.xgrid.grid_line_alpha = 0.3 |
59 | | -p.ygrid.grid_line_alpha = 0.3 |
| 95 | +p.xgrid.grid_line_color = INK |
| 96 | +p.ygrid.grid_line_color = INK |
| 97 | +p.xgrid.grid_line_alpha = 0.10 |
| 98 | +p.ygrid.grid_line_alpha = 0.10 |
60 | 99 | p.xgrid.grid_line_dash = [6, 4] |
61 | 100 | p.ygrid.grid_line_dash = [6, 4] |
62 | 101 |
|
63 | | -# Legend styling |
| 102 | +p.legend.location = "bottom_right" |
| 103 | +p.legend.background_fill_color = ELEVATED_BG |
| 104 | +p.legend.border_line_color = INK_SOFT |
| 105 | +p.legend.label_text_color = INK_SOFT |
64 | 106 | p.legend.label_text_font_size = "18pt" |
65 | | -p.legend.location = "top_right" |
66 | | -p.legend.background_fill_alpha = 0.7 |
67 | | - |
68 | | -# Add center line at x=0 for visual reference |
69 | | -p.line([0, 0], [-0.5, len(age_groups) - 0.5], line_color="#333333", line_width=2, line_alpha=0.5) |
| 107 | +p.legend.background_fill_alpha = 0.9 |
70 | 108 |
|
71 | | -# Save outputs |
72 | | -export_png(p, filename="plot.png") |
73 | | -output_file("plot.html") |
| 109 | +# Save |
| 110 | +export_png(p, filename=f"plot-{THEME}.png") |
| 111 | +output_file(f"plot-{THEME}.html") |
74 | 112 | save(p) |
0 commit comments