From cc7ceb75ff439ac52e233a7a650385d63511f2ba Mon Sep 17 00:00:00 2001
From: madsCodeBuddy
Date: Mon, 27 Apr 2026 22:54:02 -0700
Subject: [PATCH 01/10] refactor(stommel): use create_space_time_figure +
add_magnitude_labels
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replace the hand-rolled figure() setup and the TIME_MARKERS/SPACE_MARKERS reference-grid loop with the package's create_space_time_figure(space_on_x=False) and add_magnitude_labels(p, space_on_x=False). Override the package defaults that don't fit this figure (smaller width/height, wider x/y ranges, tighter axis font sizes, custom title styling, custom background color, explicit tool set without SaveTool/HelpTool).
Bonus: add_magnitude_labels has CustomJS sticky callbacks so the magnitude labels follow the visible edge on pan/zoom — better than the previous hand-rolled labels which were anchored at fixed positions.
Keep hand-rolled the manual ETL, patches+hover, and 4-row energy legend, documented in a comment block. add_processes(category_col=) would produce a 28-row legend and lose the hover.
Net: 368 → 346 lines (-22), no behavior change visible to readers.
---
docs/build_desert_farm.py | 96 +++++++++++++++------------------------
1 file changed, 37 insertions(+), 59 deletions(-)
diff --git a/docs/build_desert_farm.py b/docs/build_desert_farm.py
index a508e88..de40aa1 100644
--- a/docs/build_desert_farm.py
+++ b/docs/build_desert_farm.py
@@ -4,21 +4,26 @@
Shows all processes across 6 scales (Molecular → Global), colored by
dominant energy type (Chemical / Radiative / Thermal / Mechanical).
Designed for embedding on Google Sites via iframe.
+
+Uses package functions for figure setup, reference grid, and the
+ETL helpers. Hand-rolls the patches + hover + 4-row energy legend
+(see build_desert_farm_figure for why).
"""
import pandas as pd
import numpy as np
-from bokeh.plotting import figure
-from bokeh.models import ColumnDataSource, Span, Label, HoverTool, Legend, LegendItem
+from bokeh.models import ColumnDataSource, HoverTool, Legend, LegendItem
from bokeh.resources import CDN
from bokeh.embed import components
-from timeSpace.constants import TIME_MARKERS, SPACE_MARKERS
from timeSpace.calculations import create_ellipse_data, classify_process_geometry
from timeSpace.etl import process_magnitude_column
+from timeSpace.plotting import create_space_time_figure, add_magnitude_labels
from timeSpace.plotting_helpers import set_fill_alpha
# ── Configuration ──────────────────────────────────────────────────
+# Wider than create_space_time_figure's defaults (1e-3..1e12 time, 1e-21..1e21 space)
+# to fit fossil-fuel timescales and atomic / global volumes on this figure.
X_RANGE = (1e-3, 1e13)
Y_RANGE = (1e-28, 1e22)
@@ -47,7 +52,7 @@ def load_processes(csv_path):
"""Read desert farm process CSV and generate render coordinates.
Classifies each process geometry (ellipse/vline/hline/point) and only
- generates ellipse polygon data for true ellipses. Degenerate axes
+ generates ellipse polygon data for true ellipses. Degenerate axes
render as lines or point markers instead of fabricated ellipses.
Uses package functions:
@@ -58,14 +63,11 @@ def load_processes(csv_path):
"""
df = pd.read_csv(csv_path)
- # Apply units — same function as etl.py pipeline
for col in ["Time_min", "Time_max", "Space_min", "Space_max"]:
df[col] = df.apply(process_magnitude_column, column=col, axis=1)
- # Classify geometry before generating coords
df["geometry"] = df.apply(classify_process_geometry, axis=1)
- # Only generate ellipse data for actual ellipses
ellipse_mask = df["geometry"] == "ellipse"
df.loc[ellipse_mask, ["x_coords", "y_coords"]] = (
df.loc[ellipse_mask, ["Time_min", "Time_max", "Space_min", "Space_max"]]
@@ -82,8 +84,6 @@ def load_processes(csv_path):
df["color"] = df.Energy_type.map(ENERGY_COLORS)
df["label_x"] = np.sqrt(df.Time_min.apply(lambda q: q.value) * df.Time_max.apply(lambda q: q.value))
df["label_y"] = np.sqrt(df.Space_min.apply(lambda q: q.value) * df.Space_max.apply(lambda q: q.value))
-
- # Fill alpha — same function as main Stommel figure pipeline
df["fill_alpha"] = df.apply(set_fill_alpha, axis=1)
return df
@@ -95,60 +95,39 @@ def load_processes(csv_path):
def build_desert_farm_figure(csv_path, output_path):
df = load_processes(csv_path)
- p = figure(
+ # Package figure setup gives us Boyd orientation, x-axis-on-top, log scales,
+ # and matching axis labels. Override the defaults that don't fit this figure.
+ p = create_space_time_figure(
width=900,
height=650,
- x_axis_type="log",
- y_axis_type="log",
- x_axis_label="Time (s)",
- y_axis_label="Space (m³)",
- x_range=X_RANGE,
- y_range=Y_RANGE,
title="Desert Farm — Processes Across Scale",
- toolbar_location="above",
- x_axis_location="above",
- tools="pan,wheel_zoom,box_zoom,reset",
+ space_on_x=False,
)
- p.axis.axis_label_text_font_size = FONT_SIZE
- p.axis.major_label_text_font_size = "10pt"
+ p.x_range.start, p.x_range.end = X_RANGE
+ p.y_range.start, p.y_range.end = Y_RANGE
p.title.text_font_size = "16pt"
p.title.text_font_style = "bold"
+ p.axis.axis_label_text_font_size = FONT_SIZE
+ p.axis.major_label_text_font_size = "10pt"
p.background_fill_color = "#fafafa"
-
- # Reference grid
- for t, label_text in TIME_MARKERS.items():
- if X_RANGE[0] <= t <= X_RANGE[1]:
- p.add_layout(Span(location=t, dimension="height", line_color="#cccccc", line_dash="dashed", line_width=1))
- p.add_layout(
- Label(
- x=t,
- y=Y_RANGE[1],
- text=label_text,
- text_font_size=LABEL_FONT_SIZE,
- text_color="#aaaaaa",
- text_align="center",
- text_baseline="top",
- )
- )
-
- for s, label_text in SPACE_MARKERS.items():
- if Y_RANGE[0] <= s <= Y_RANGE[1]:
- p.add_layout(Span(location=s, dimension="width", line_color="#dddddd", line_dash="dashed", line_width=1))
- p.add_layout(
- Label(
- y=s,
- x=X_RANGE[0] * 1.5,
- text=label_text,
- text_font_size=LABEL_FONT_SIZE,
- text_color="#aaaaaa",
- text_align="left",
- )
- )
-
- # Plot processes by energy type, building legend items.
- # Split by geometry: ellipses use batched patches, lines/points
- # use individual glyphs. All renderers for the same energy type
- # share a LegendItem so the legend toggle hides them together.
+ p.toolbar_location = "above"
+ # Match original tool set (drops SaveTool / HelpTool from Bokeh defaults)
+ from bokeh.models import PanTool, WheelZoomTool, BoxZoomTool, ResetTool
+
+ p.toolbar.tools = [PanTool(), WheelZoomTool(), BoxZoomTool(), ResetTool()]
+
+ # Reference grid (TIME_MARKERS / SPACE_MARKERS as dashed Spans + Labels);
+ # bonus over the previous hand-rolled loop: labels stick to the visible edge
+ # on pan/zoom via CustomJS callbacks inside add_magnitude_labels.
+ add_magnitude_labels(p, font_size=LABEL_FONT_SIZE, space_on_x=False)
+
+ # Plot processes by energy type. Hand-rolled rather than using the package's
+ # add_processes(category_col=, category_colors=) because that path would
+ # (a) produce a 28-row legend (header + per-process) instead of the
+ # compact 4-row legend (one row per energy, click-to-hide group), and
+ # (b) lose the rich hover (Name, Scale, Energy, formatted ranges) — the
+ # package patch glyph data source has only x/y, not the metadata
+ # needed for tooltips.
legend_items = []
def _hover_display(val_min, val_max, unit):
@@ -259,7 +238,6 @@ def _hover_display(val_min, val_max, unit):
renderers.append(r)
- # Label for non-ellipse
lx = row.Time_min.value if geom == "point" else row.label_x
ly = row.Space_max.value if geom == "vline" else row.label_y
tr = p.text(
@@ -278,7 +256,7 @@ def _hover_display(val_min, val_max, unit):
if renderers:
legend_items.append(LegendItem(label=etype, renderers=renderers))
- # Legend
+ # Compact legend — one row per energy type, click to hide
legend = Legend(
items=legend_items,
location="top_left",
@@ -290,7 +268,7 @@ def _hover_display(val_min, val_max, unit):
)
p.add_layout(legend, "right")
- # Render
+ # ── Render HTML ────────────────────────────────────────────────
script, div = components(p)
html = f"""
From c542d93bace5b8482c6466aa27b99944a6993617 Mon Sep 17 00:00:00 2001
From: madsCodeBuddy
Date: Mon, 27 Apr 2026 22:54:02 -0700
Subject: [PATCH 02/10] build: regenerate desert_farm_stommel.html with
refactored builder
---
docs/desert_farm_stommel.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/desert_farm_stommel.html b/docs/desert_farm_stommel.html
index 7d2f083..74ba334 100644
--- a/docs/desert_farm_stommel.html
+++ b/docs/desert_farm_stommel.html
@@ -57,15 +57,15 @@ Operating Across Scale: From Molecules to Climate
chemical (green), radiative (gold), thermal (red), mechanical (blue).
Click the legend to toggle energy types on/off.
-
+