Tile Grid Map for Equal-Area Geographic Comparison — Bokeh

A tile grid map represents geographic regions (states, countries, provinces) as equally-sized tiles — squares or hexagons — arranged to approximate their real-world geographic positions. Unlike choropleth maps where large-area regions dominate visually, every region receives identical visual weight, making tile grid maps ideal for per-capita or per-region comparisons where the statistic matters more than physical area. Tiles are colored by a data variable and labeled with region abbreviations for identification.

Tile Grid Map for Equal-Area Geographic Comparison rendered with Bokeh

Renders

Python source (Bokeh)

""" anyplot.ai
map-tilegrid: Tile Grid Map for Equal-Area Geographic Comparison
Library: bokeh 3.10.0 | Python 3.13.15
Quality: 95/100 | Created: 2026-08-24
"""

import os
import time
from pathlib import Path

from bokeh.io import output_file, save
from bokeh.models import (
    BasicTicker,
    ColorBar,
    ColumnDataSource,
    HoverTool,
    LinearColorMapper,
    NumeralTickFormatter,
    Range1d,
    Title,
)
from bokeh.plotting import figure
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
LABEL_LIGHT = "#F0EFE8"

# Data — renewable energy share of European countries, tile positions
# approximate each country's real-world location (row 0 = north, col 0 = west)
countries = [
    {"code": "IS", "row": 0, "col": 0, "value": 85},
    {"code": "NO", "row": 0, "col": 3, "value": 71},
    {"code": "SE", "row": 0, "col": 5, "value": 65},
    {"code": "FI", "row": 0, "col": 7, "value": 47},
    {"code": "IE", "row": 1, "col": 0, "value": 36},
    {"code": "UK", "row": 1, "col": 1, "value": 42},
    {"code": "DK", "row": 1, "col": 4, "value": 43},
    {"code": "EE", "row": 1, "col": 8, "value": 38},
    {"code": "BE", "row": 2, "col": 0, "value": 26},
    {"code": "NL", "row": 2, "col": 1, "value": 37},
    {"code": "DE", "row": 2, "col": 4, "value": 46},
    {"code": "PL", "row": 2, "col": 6, "value": 21},
    {"code": "LV", "row": 2, "col": 8, "value": 43},
    {"code": "FR", "row": 3, "col": 1, "value": 22},
    {"code": "CH", "row": 3, "col": 3, "value": 30},
    {"code": "CZ", "row": 3, "col": 5, "value": 19},
    {"code": "SK", "row": 3, "col": 6, "value": 21},
    {"code": "LT", "row": 3, "col": 8, "value": 29},
    {"code": "PT", "row": 4, "col": 0, "value": 34},
    {"code": "ES", "row": 4, "col": 1, "value": 24},
    {"code": "AT", "row": 4, "col": 4, "value": 78},
    {"code": "HU", "row": 4, "col": 6, "value": 15},
    {"code": "RO", "row": 4, "col": 7, "value": 30},
    {"code": "IT", "row": 5, "col": 2, "value": 20},
    {"code": "SI", "row": 5, "col": 4, "value": 27},
    {"code": "HR", "row": 5, "col": 5, "value": 32},
    {"code": "BG", "row": 5, "col": 7, "value": 24},
    {"code": "GR", "row": 6, "col": 5, "value": 22},
    {"code": "MT", "row": 7, "col": 4, "value": 13},
]
num_rows = max(c["row"] for c in countries) + 1
max_col = max(c["col"] for c in countries)

COUNTRY_NAMES = {
    "IS": "Iceland", "NO": "Norway", "SE": "Sweden", "FI": "Finland",
    "IE": "Ireland", "UK": "United Kingdom", "DK": "Denmark", "EE": "Estonia",
    "BE": "Belgium", "NL": "Netherlands", "DE": "Germany", "PL": "Poland",
    "LV": "Latvia", "FR": "France", "CH": "Switzerland", "CZ": "Czechia",
    "SK": "Slovakia", "LT": "Lithuania", "PT": "Portugal", "ES": "Spain",
    "AT": "Austria", "HU": "Hungary", "RO": "Romania", "IT": "Italy",
    "SI": "Slovenia", "HR": "Croatia", "BG": "Bulgaria", "GR": "Greece",
    "MT": "Malta",
}  # fmt: skip


def _lerp_hex(c0, c1, t):
    r0, g0, b0 = (int(c0[i : i + 2], 16) for i in (1, 3, 5))
    r1, g1, b1 = (int(c1[i : i + 2], 16) for i in (1, 3, 5))
    r, g, b = (int(round(a + (b - a) * t)) for a, b in ((r0, r1), (g0, g1), (b0, b1)))
    return f"#{r:02X}{g:02X}{b:02X}"


def _luminance(hex_color):
    r, g, b = (int(hex_color[i : i + 2], 16) / 255 for i in (1, 3, 5))
    return 0.2126 * r + 0.7152 * g + 0.0722 * b


# Continuous Imprint colormap — unipolar data (renewable share, no midpoint)
ANYPLOT_SEQ256 = [_lerp_hex("#009E73", "#4467A3", t / 255.0) for t in range(256)]
low = min(c["value"] for c in countries)
high = max(c["value"] for c in countries)

xs, ys, values, codes, names, label_colors = [], [], [], [], [], []
for c in countries:
    xs.append(c["col"])
    ys.append(num_rows - 1 - c["row"])  # flip so row 0 (north) renders at top
    values.append(c["value"])
    codes.append(c["code"])
    names.append(COUNTRY_NAMES[c["code"]])
    t = (c["value"] - low) / (high - low)
    tile_color = ANYPLOT_SEQ256[min(255, int(round(t * 255)))]
    label_colors.append(INK if _luminance(tile_color) > 0.55 else LABEL_LIGHT)

source = ColumnDataSource(
    data={"x": xs, "y": ys, "value": values, "code": codes, "name": names, "label_color": label_colors}
)
mapper = LinearColorMapper(palette=ANYPLOT_SEQ256, low=low, high=high)

# Title — compute fontsize per the length-scaling formula (default 50pt / floor 34pt)
title = "Renewable Energy Share in Europe · map-tilegrid · python · bokeh · anyplot.ai"
n = len(title)
ratio = 67 / n if n > 67 else 1.0
title_font_size = f"{max(34, round(50 * ratio))}pt"

# Plot
p = figure(
    width=2400,
    height=2400,
    title=title,
    toolbar_location=None,  # bokeh's default toolbar shrinks the PNG below `height=`
    x_range=Range1d(-0.75, max_col + 0.75),
    y_range=Range1d(-0.75, num_rows - 1 + 0.75),
    match_aspect=True,
    min_border_top=190,
    min_border_bottom=60,
    min_border_left=60,
    min_border_right=340,
)
tile_renderer = p.rect(
    x="x",
    y="y",
    width=0.88,
    height=0.88,
    source=source,
    fill_color={"field": "value", "transform": mapper},
    line_color=PAGE_BG,
    line_width=6,
)
hover = HoverTool(renderers=[tile_renderer], tooltips=[("Country", "@name"), ("Renewable share", "@value{0}%")])
p.add_tools(hover)
p.text(
    x="x",
    y="y",
    text="code",
    source=source,
    text_align="center",
    text_baseline="middle",
    text_font_size="34pt",
    text_font_style="bold",
    text_color="label_color",
)

# Colorbar
color_bar = ColorBar(
    color_mapper=mapper,
    ticker=BasicTicker(desired_num_ticks=5),
    formatter=NumeralTickFormatter(format="0"),
    title="Renewable share (%)",
    title_text_font_size="34pt",
    title_text_color=INK,
    major_label_text_font_size="30pt",
    major_label_text_color=INK_SOFT,
    background_fill_color=ELEVATED_BG,
    border_line_color=INK_SOFT,
    label_standoff=12,
    width=40,
    location=(0, 0),
)
p.add_layout(color_bar, "right")

# Style — theme-adaptive chrome; no axes for a schematic tile grid
p.title.text_font_size = title_font_size
p.title.text_color = INK

# Subtitle callout — sharpens the color-gradient insight into an explicit takeaway
p.add_layout(
    Title(
        text="Nordic and Alpine countries lead Europe's renewable transition",
        text_font_size="26pt",
        text_font_style="italic",
        text_color=INK_SOFT,
    ),
    "above",
)

p.background_fill_color = PAGE_BG
p.border_fill_color = PAGE_BG
p.outline_line_color = None
p.axis.visible = False
p.grid.visible = False

# Save (HTML + PNG via headless Chrome — see prompts/library/bokeh.md)
output_file(f"plot-{THEME}.html")
save(p)

W, H = 2400, 2400
opts = Options()
for arg in (
    "--headless=new",
    "--no-sandbox",
    "--disable-dev-shm-usage",
    "--disable-gpu",
    f"--window-size={W},{H}",
    "--hide-scrollbars",
):
    opts.add_argument(arg)
driver = webdriver.Chrome(options=opts)
driver.set_window_size(W, H)
driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}")
driver.execute_cdp_cmd(
    "Emulation.setDeviceMetricsOverride", {"width": W, "height": H, "deviceScaleFactor": 1, "mobile": False}
)
time.sleep(3)
driver.save_screenshot(f"plot-{THEME}.png")
driver.quit()

Part of Tile Grid Map for Equal-Area Geographic Comparison on anyplot.ai.

Other implementations