Skip to content

Commit 323bc52

Browse files
feat(bokeh): implement facet-grid (#6516)
## Implementation: `facet-grid` - python/bokeh Implements the **python/bokeh** version of `facet-grid`. **File:** `plots/facet-grid/implementations/python/bokeh.py` **Parent Issue:** #2696 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25776341741)* --------- 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 d91c7ec commit 323bc52

2 files changed

Lines changed: 260 additions & 174 deletions

File tree

Lines changed: 95 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,36 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
facet-grid: Faceted Grid Plot
3-
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 90/100 | Created: 2025-12-30
3+
Library: bokeh 3.9.0 | Python 3.13.13
4+
Quality: 87/100 | Updated: 2026-05-13
55
"""
66

7+
import os
8+
import time
9+
from pathlib import Path
10+
711
import numpy as np
812
import pandas as pd
9-
from bokeh.io import export_png
13+
from bokeh.io import output_file, save
1014
from bokeh.layouts import column, gridplot
11-
from bokeh.models import ColumnDataSource, Div
15+
from bokeh.models import ColumnDataSource, Div, HoverTool
1216
from bokeh.plotting import figure
17+
from selenium import webdriver
18+
from selenium.webdriver.chrome.options import Options
19+
20+
21+
# Theme tokens
22+
THEME = os.getenv("ANYPLOT_THEME", "light")
23+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
24+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
25+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
26+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
1327

28+
# Okabe-Ito palette
29+
OKABE_ITO = [
30+
"#009E73", # bluish green (brand)
31+
"#D55E00", # vermillion
32+
"#0072B2", # blue
33+
]
1434

1535
# Data - Product performance across regions and seasons
1636
np.random.seed(42)
@@ -37,89 +57,121 @@
3757

3858
df = pd.DataFrame(data)
3959

40-
# Colors for each region - darker orange instead of yellow for better visibility
41-
colors = {"North": "#306998", "South": "#E69F00", "East": "#4ECDC4"}
60+
# Map regions to colors
61+
color_map = {"North": OKABE_ITO[0], "South": OKABE_ITO[1], "East": OKABE_ITO[2]}
4262

4363
# Create grid of plots (rows=seasons, cols=regions)
44-
# Target: 4800x2700 total. With 3 cols, 4 rows + title/legend:
45-
# - Subplot width: 4800 / 3 = 1600
46-
# - Subplot height: (2700 - 120 title - 100 legend) / 4 = 620
4764
plots = []
4865

4966
for season in seasons:
5067
row_plots = []
5168
for region in regions:
5269
subset = df[(df["region"] == region) & (df["season"] == season)]
53-
source = ColumnDataSource(data={"x": subset["marketing_spend"], "y": subset["sales"]})
70+
source = ColumnDataSource(
71+
data={"x": subset["marketing_spend"], "y": subset["sales"], "region": [region] * len(subset)}
72+
)
5473

5574
# Create figure for each cell
56-
p = figure(width=1600, height=620, x_range=(5, 50), y_range=(20, 130), tools="")
75+
p = figure(
76+
width=1600, height=620, x_range=(5, 50), y_range=(20, 130), tools="", title="", toolbar_location=None
77+
)
5778

5879
# Add scatter points
59-
p.scatter(
60-
x="x", y="y", source=source, size=18, color=colors[region], alpha=0.7, line_color="#333333", line_width=1
80+
scatter = p.scatter(
81+
x="x", y="y", source=source, size=18, color=color_map[region], alpha=0.7, line_color=PAGE_BG, line_width=1
6182
)
6283

84+
# Add hover tooltip
85+
hover = HoverTool(
86+
renderers=[scatter], tooltips=[("Region", "@region"), ("Spend", "@x{0.0}"), ("Sales", "@y{0.0}")]
87+
)
88+
p.add_tools(hover)
89+
6390
# Add facet label in top-left corner
6491
p.text(
65-
x=[8],
66-
y=[122],
67-
text=[f"{region} · {season}"],
68-
text_font_size="18pt",
69-
text_color="#333333",
70-
text_font_style="bold",
92+
x=[8], y=[122], text=[f"{region} · {season}"], text_font_size="18pt", text_color=INK, text_font_style="bold"
7193
)
7294

73-
# Style axes
95+
# Style axes - only show labels on edges
7496
p.xaxis.axis_label = "Marketing Spend ($K)" if season == seasons[-1] else ""
7597
p.yaxis.axis_label = "Sales ($K)" if region == regions[0] else ""
76-
p.xaxis.axis_label_text_font_size = "20pt"
77-
p.yaxis.axis_label_text_font_size = "20pt"
78-
p.xaxis.major_label_text_font_size = "16pt"
79-
p.yaxis.major_label_text_font_size = "16pt"
98+
p.xaxis.axis_label_text_font_size = "22pt"
99+
p.yaxis.axis_label_text_font_size = "22pt"
100+
p.xaxis.major_label_text_font_size = "18pt"
101+
p.yaxis.major_label_text_font_size = "18pt"
102+
103+
# Theme-adaptive styling
104+
p.background_fill_color = PAGE_BG
105+
p.border_fill_color = PAGE_BG
106+
p.outline_line_color = INK_SOFT
107+
p.title.text_color = INK
108+
p.xaxis.axis_label_text_color = INK
109+
p.yaxis.axis_label_text_color = INK
110+
p.xaxis.major_label_text_color = INK_SOFT
111+
p.yaxis.major_label_text_color = INK_SOFT
112+
p.xaxis.axis_line_color = INK_SOFT
113+
p.yaxis.axis_line_color = INK_SOFT
80114

81115
# Grid styling
82-
p.xgrid.grid_line_alpha = 0.3
83-
p.ygrid.grid_line_alpha = 0.3
84-
p.xgrid.grid_line_dash = "dashed"
85-
p.ygrid.grid_line_dash = "dashed"
86-
87-
# Background
88-
p.background_fill_color = "#fafafa"
116+
p.xgrid.grid_line_color = INK
117+
p.ygrid.grid_line_color = INK
118+
p.xgrid.grid_line_alpha = 0.15
119+
p.ygrid.grid_line_alpha = 0.15
89120

90121
row_plots.append(p)
91122
plots.append(row_plots)
92123

93124
# Create gridplot layout
94125
grid = gridplot(plots, toolbar_location=None, merge_tools=False)
95126

96-
# Add overall title using Div for cleaner implementation
127+
# Add overall title using Div
97128
title_div = Div(
98-
text="<h1 style='text-align: center; color: #333333; font-size: 32pt; margin: 20px 0;'>"
99-
"facet-grid · bokeh · pyplots.ai</h1>",
129+
text=f"<h1 style='text-align: center; color: {INK}; font-size: 32pt; margin: 20px 0;'>"
130+
f"facet-grid · bokeh · anyplot.ai</h1>",
100131
width=4800,
101132
height=120,
102133
)
103134

104135
# Create legend using Div for region-color mapping
105136
legend_html = (
106-
"<div style='text-align: center; font-size: 18pt; padding: 20px 0;'>"
107-
"<span style='font-weight: bold; margin-right: 30px;'>Region:</span>"
137+
f"<div style='text-align: center; font-size: 18pt; padding: 20px 0; color: {INK};'>"
138+
f"<span style='font-weight: bold; margin-right: 30px;'>Region:</span>"
108139
)
109-
for region, color in colors.items():
140+
for region in regions:
141+
color = color_map[region]
110142
legend_html += (
111143
f"<span style='margin-right: 40px;'>"
112144
f"<span style='display: inline-block; width: 20px; height: 20px; "
113-
f"background-color: {color}; border: 1px solid #333; border-radius: 50%; "
145+
f"background-color: {color}; border: 1px solid {INK_SOFT}; border-radius: 50%; "
114146
f"vertical-align: middle; margin-right: 8px;'></span>"
115-
f"{region}</span>"
147+
f"<span style='color: {INK_SOFT};'>{region}</span></span>"
116148
)
117149
legend_html += "</div>"
118150

119-
legend_div = Div(text=legend_html, width=4800, height=80)
151+
legend_div = Div(text=legend_html, width=4800, height=100)
120152

121153
# Combine title, grid, and legend using column layout
122154
final_layout = column(title_div, grid, legend_div)
123155

124-
# Save
125-
export_png(final_layout, filename="plot.png")
156+
# Save HTML
157+
output_file(f"plot-{THEME}.html")
158+
save(final_layout)
159+
160+
# Screenshot with headless Chrome
161+
W, H = 4800, 2700
162+
opts = Options()
163+
for arg in (
164+
"--headless=new",
165+
"--no-sandbox",
166+
"--disable-dev-shm-usage",
167+
"--disable-gpu",
168+
f"--window-size={W},{H}",
169+
"--hide-scrollbars",
170+
):
171+
opts.add_argument(arg)
172+
driver = webdriver.Chrome(options=opts)
173+
driver.set_window_size(W, H)
174+
driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}")
175+
time.sleep(3)
176+
driver.save_screenshot(f"plot-{THEME}.png")
177+
driver.quit()

0 commit comments

Comments
 (0)