Skip to content

Commit 222af29

Browse files
feat(bokeh): implement pyramid-basic (#5528)
## Implementation: `pyramid-basic` - python/bokeh Implements the **python/bokeh** version of `pyramid-basic`. **File:** `plots/pyramid-basic/implementations/python/bokeh.py` **Parent Issue:** #1000 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25116077840)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 9b8f5d6 commit 222af29

2 files changed

Lines changed: 229 additions & 151 deletions

File tree

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,112 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
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
55
"""
66

7+
import os
8+
79
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
911
from bokeh.plotting import figure
1012

1113

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+
1224
# Data - Population by age group (in thousands)
1325
age_groups = ["0-9", "10-19", "20-29", "30-39", "40-49", "50-59", "60-69", "70-79", "80+"]
1426
male_population = [45, 52, 68, 82, 75, 65, 48, 32, 15]
1527
female_population = [43, 50, 72, 85, 78, 70, 55, 40, 22]
16-
17-
# Negate male values for left side of pyramid
1828
male_negative = [-v for v in male_population]
1929

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})
2332

24-
# Create figure with categorical y-axis and symmetric x-range
33+
# Plot
2534
p = figure(
2635
width=4800,
2736
height=2700,
2837
y_range=age_groups,
2938
x_range=Range1d(-100, 100),
30-
title="pyramid-basic · bokeh · pyplots.ai",
39+
title="pyramid-basic · bokeh · anyplot.ai",
3140
x_axis_label="Population (thousands)",
3241
y_axis_label="Age Group",
3342
)
3443

35-
# Draw horizontal bars - male (left, Python Blue) and female (right, coral pink)
3644
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",
3953
)
40-
p.hbar(
54+
female_bars = p.hbar(
4155
y="age",
4256
right="population",
4357
height=bar_height,
4458
source=source_female,
45-
color="#E8888C",
59+
color=FEMALE_COLOR,
4660
alpha=0.85,
4761
legend_label="Female",
4862
)
4963

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
5181
p.title.text_font_size = "32pt"
82+
p.xaxis.axis_label_text_color = INK
83+
p.yaxis.axis_label_text_color = INK
5284
p.xaxis.axis_label_text_font_size = "24pt"
5385
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
5488
p.xaxis.major_label_text_font_size = "18pt"
5589
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
5694

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
6099
p.xgrid.grid_line_dash = [6, 4]
61100
p.ygrid.grid_line_dash = [6, 4]
62101

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
64106
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
70108

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")
74112
save(p)

0 commit comments

Comments
 (0)