anywidget viewers for cone penetration test (CPT) and geotechnical borehole data, rendered with D3.js. Built for notebook use (marimo, Jupyter). Every viewer shares a zoomable vertical axis in depth below surface or elevation, so soundings, borehole logs, and interpretations line up.
Full documentation on bedrock.engineer/docs/cpt-anywidget
CPTViewer— one CPT: measurement channels (qc, fs, Rf, u1/u2, inclination, …) against the vertical axis, plus optional layer columns: a nearby borehole log, read-only interpretation columns (e.g. Robertson, Lengkeek), and an editable layer column synced back to Python.BoreholeViewer— a single borehole log with proportional soil-composition bands and hatch patterns; itslayerstrait takes plain dicts from any source, andlayers_from_bhrgt(brodata BHR-GT) andlayers_from_bore(pygef GEF/BRO XML) convert boreholes already in the Dutch BRO soil vocabulary.ProfileViewer— multiple CPTs side by side along a chainage axis (a cross-section);chainagecomputes along-profile distances from map coordinates.
From PyPI, with uv (recommended):
uv add cpt-anywidgetor with pip: pip install cpt-anywidget.
import polars as pl
from cpt_anywidget import CPTViewer, Channel
df = pl.DataFrame({
"depth": [0.0, 0.02, 0.04], # or "nap" for elevation
"coneResistance": [0.1, 0.4, 0.9], # MPa
"localFriction": [0.01, 0.02, 0.02],
})
CPTViewer(df, vertical="depth", channels=["coneResistance", "localFriction"])data is tidy columns — a polars or pandas DataFrame or a dict of
columns (lists, tuples, numpy arrays) — one row per depth sample. The widget never parses file
formats or converts units: loaders (e.g. brodata)
normalize upstream. The Dutch BRO column names get built-in display
defaults; any
other column can be bound with
Channel(key, label=…, unit=…, color=…, side=…).
Custom vertical datums work the same way via
Vertical(key, label=…, up=…).
Annotations, overlays, and axis limits are plain traits and track the zoom. See Interactions for the pointer and keyboard gestures.
- To zoom the vertical axis, hold the Ctrl key (Cmd on macOS) and turn the mouse wheel. Pinching on a trackpad or touch device zooms too, without keys.
- To pan a zoomed axis, drag in the plot area.
- To zoom to a range, hold the Shift key and drag along the axis.
- To reset the zoom, double-click in the plot area.
- Move the pointer across the plot to read the values at that depth.
- The mouse wheel without a modifier key scrolls the notebook, not the chart.
- To move a boundary, drag it. A boundary stops at the minimum layer thickness.
- To split a layer, click in the lane beside the column. A dashed line previews where the new boundary goes.
- To merge two layers, move the pointer near their boundary in the lane and click the × it offers. The upper layer keeps its class.
- To set the class of a layer, click the layer. A pie menu opens. Click a wedge, or walk the wedges with the arrow keys and push Enter.
- As a fast path, press the layer, drag toward a wedge, and release.
- To close the pie menu, push the Escape key or click outside the menu.
- Each edit goes back to Python through the
editedLayerstrait.
- To select a CPT strip, click it. The name shows in the
selectedtrait in Python. - To deselect the strip, click it again.
Front end (TypeScript, js/) builds into src/cpt_anywidget/static/:
npm install
npm run build # or: npm run dev (watch mode)Set ANYWIDGET_HMR=1 so JS edits hot-reload in the browser. Sample files
(BRO XML, GEF, AGS) live in examples/.