CubePlot Grammar of Graphics
Climate cubes use a ggplot-inspired grammar so you can build 3D visualizations layer by layer. The grammar mirrors data → aes → stat → geom → scale → coord → theme → facet → annotation.
High-level: v.plot()
v.plot()is shorthand forCubePlot(da).geom_cube().theme_cube_studio(tight_axes=True)plus a vase overlay whenattrs["vase"]is attached.- It returns a :class:
~cubedynamics.plotting.CubePlotobject; Jupyter renders it automatically—nodisplay()call required. - The viewer is safe for dask-backed arrays and
VirtualCubeslices; it should not call.valueson the full cube, only on streamed frames.
Use it for quicklooks:
from cubedynamics import pipe, verbs as v
pipe(cube) | v.plot(title="NDVI")
Then graduate to the full grammar for control:
from cubedynamics.plotting import CubePlot
p = (CubePlot(cube)
.geom_cube()
.coord_cube(elev=25, azim=60)
.theme_cube_studio(tight_axes=True))
Core pieces
- Data: any xarray
DataArrayorVirtualCube - Aesthetics (
aes): map cube variables to visuals (fill,alpha,slice) - Stats (
stat_*):stat_time_mean,stat_time_anomaly,stat_space_mean,stat_identity - Geoms (
geom_*):geom_cube,geom_slice,geom_outline, with ageom_path3dstub ready for trajectories - Scales:
ScaleFillContinuous,ScaleAlphaContinuouswith diverging/centered options - Coordinates:
coord_cube/CoordCubefor camera elevation, azimuth, zoom, and aspect - Themes:
CubeTheme,theme_cube_studio(CSS variables for background, legend, axes) - Facets:
facet_wrap,facet_gridfor multi-panel cube walls - Annotations:
annot_plane,annot_textfor domain and figure storytelling
Vase volumes inside the grammar
- Stats (
stat_vase): appliesbuild_vase_maskto attach a masked cube and the vase mask for downstream layers. - Geoms (
geom_vase_outline): instructs the viewer to tint cube faces where the vase touches each slice.
See Vase Volumes & Arbitrary 3-D Subsets for a walkthrough of defining vases and combining these layers with geom_cube.
Minimal example
Start with the high-level viewer:
from cubedynamics import pipe, verbs as v
pipe(cube) | v.plot(title="NDVI cube")
When you need more control, expand to the grammar:
from cubedynamics.plotting import CubePlot
p = (CubePlot(cube)
.geom_cube()
.coord_cube(elev=35, azim=45)
.theme_cube_studio(tight_axes=True))
Layered example
from cubedynamics.plotting.cube_plot import CubePlot
p = (CubePlot(cube, title="NDVI anomaly")
.aes(fill="ndvi")
.stat_time_anomaly(time_dim="time")
.geom_cube()
.scale_fill_continuous(center=0, palette="diverging")
.coord_cube(elev=35, azim=45)
.theme_cube_studio()
)
p.save("figure1.html")
Faceting
(CubePlot(cube)
.aes(fill="ndvi")
.facet_wrap(by="scenario", ncol=2)
.geom_slice(axis="time", value="2012-06-10")
.scale_fill_continuous(palette="sequential")
)
Annotations and captions
Use annot_plane for domain markers and caption metadata for publication-ready figures:
p = (CubePlot(cube, title="Scenario comparison")
.annot_plane(axis="time", value="2012-06-10", text="Fire event")
.annot_text(coord=(10, 20, 30), text="Tower")
)
p.caption = {"id": 2, "title": "NDVI anomaly", "text": "**Markdown** + $\nabla$ ready."}
p.save("scenario.html")
CubePlot philosophy
- Everything accepts cubes lazily—no forced
.compute() - Scales and themes drive legends automatically
pipe(ndvi) | v.plot()stays the default, whileCubePlot(...)offers full control