Skip to content

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 for CubePlot(da).geom_cube().theme_cube_studio(tight_axes=True) plus a vase overlay when attrs["vase"] is attached.
  • It returns a :class:~cubedynamics.plotting.CubePlot object; Jupyter renders it automatically—no display() call required.
  • The viewer is safe for dask-backed arrays and VirtualCube slices; it should not call .values on 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 DataArray or VirtualCube
  • 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 a geom_path3d stub ready for trajectories
  • Scales: ScaleFillContinuous, ScaleAlphaContinuous with diverging/centered options
  • Coordinates: coord_cube / CoordCube for camera elevation, azimuth, zoom, and aspect
  • Themes: CubeTheme, theme_cube_studio (CSS variables for background, legend, axes)
  • Facets: facet_wrap, facet_grid for multi-panel cube walls
  • Annotations: annot_plane, annot_text for domain and figure storytelling

Vase volumes inside the grammar

  • Stats (stat_vase): applies build_vase_mask to 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, while CubePlot(...) offers full control