Skip to content
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 0.4.1 (_unreleased_)

- Improve plotting: Add possibility to use `explore` on Datasets. In such a case, the user can select a data variable from a dropdown menu. Use Coordinates for the dimension sliders. Add colorbars. Add slider animations. Use the new H3Layer for H3 grids. ({pull}`197`)

## 0.4.0 (2025-11-03)

- support interactive facet plots and combining maps ({pull}`183`)
Expand Down
86 changes: 84 additions & 2 deletions docs/tutorials/h3.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
"metadata": {},
"outputs": [],
"source": [
"ds[\"air\"].dggs.explore()"
"ds.dggs.explore()"
]
},
{
Expand All @@ -217,6 +217,88 @@
" alpha=0.8, cmap=\"coolwarm\", center=273.15\n",
")"
]
},
{
"cell_type": "markdown",
"id": "18",
"metadata": {},
"source": [
"We can also explore Datasets with variables consisting of different dimensions and configure them separately!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19",
"metadata": {},
"outputs": [],
"source": [
"ds[\"air_anomaly\"] = ds[\"air\"].groupby(\"time.month\") - ds[\"air\"].groupby(\n",
" \"time.month\"\n",
").quantile([0.25, 0.5, 0.75], dim=\"time\")\n",
"ds[\"air_anomaly\"].attrs = {\"long_name\": \"Air Temperature Anomaly\", \"units\": \"K\"}\n",
"ds[\"max_air_anomaly\"] = ds[\"air_anomaly\"].max(dim=[\"time\", \"quantile\"])\n",
"ds[\"max_air_anomaly\"].attrs = {\n",
" \"long_name\": \"Maximum Air Temperature Anomaly\",\n",
" \"units\": \"K\",\n",
"}\n",
"ds.dggs.explore(\n",
" air_anomaly={\"center\": 0.0, \"cmap\": \"coolwarm\"},\n",
" max_air_anomaly={\"vmax\": 20},\n",
" robust=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "20",
"metadata": {},
"source": [
"By passing a `model_kwargs`, the underlying lonboard Map can be configured. This way it is possible to create globe views:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21",
"metadata": {},
"outputs": [],
"source": [
"import ipywidgets\n",
"\n",
"s = ipywidgets.IntSlider(description=\"Test Slider\", min=0, max=10, value=5)\n",
"\n",
"a = {s: 0}\n",
"a[s]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22",
"metadata": {},
"outputs": [],
"source": [
"from lonboard.basemap import MaplibreBasemap\n",
"from lonboard.experimental.view import GlobeView\n",
"\n",
"basemap = MaplibreBasemap(mode=\"interleaved\")\n",
"view = GlobeView()\n",
"ds.dggs.explore(\n",
" air_anomaly={\"center\": 0.0, \"cmap\": \"coolwarm\"},\n",
" max_air_anomaly={\"vmax\": 20},\n",
" robust=True,\n",
" map_kwargs={\"view\": view, \"basemap\": basemap},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand All @@ -230,7 +312,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
"version": "3.13.9"
}
},
"nbformat": 4,
Expand Down
85 changes: 69 additions & 16 deletions xdggs/accessor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from typing import TYPE_CHECKING

import numpy.typing as npt
import xarray as xr

from xdggs.grid import DGGSInfo
from xdggs.index import DGGSIndex
from xdggs.plotting import explore

if TYPE_CHECKING:
from matplotlib.colors import Colormap


@xr.register_dataset_accessor("dggs")
@xr.register_dataarray_accessor("dggs")
Expand Down Expand Up @@ -208,38 +213,86 @@ def zoom_to(self, level: int):

return xr.DataArray(zoomed, coords={self._name: self.cell_ids}, dims=dims)

def explore(self, *, cmap="viridis", center=None, alpha=None, coords=None):
"""interactively explore the data using `lonboard`
def explore(
self,
*,
coords: float | None = None,
cmap: "str | Colormap | dict[str, str | Colormap]" = "viridis",
alpha: float | None = None,
center: float | dict[str, float] | None = None,
vmin: float | dict[str, float] | None = None,
vmax: float | dict[str, float] | None = None,
robust: bool = False,
map_kwargs: dict = {},
**coloring_kwargs,
):
"""Interactively explore the data using `lonboard`.

Requires `lonboard`, `matplotlib`, and `arro3.core` to be installed.

Parameters
----------
cmap : str
The name of the color map to use
center : int or float, optional
If set, will use this as the center value of a diverging color map.
alpha : float, optional
If set, controls the transparency of the polygons.
coords : list of str, default: ["latitude", "longitude"]
Additional coordinates to contain in the table of contents.
cmap : str or Colormap or dict[str, str or Colormap], default: "viridis"
The name of the color map to use. If a dict is provided, it can map variable
names to specific color maps.
alpha : float, optional
If set, controls the transparency of the polygons.
center : int or float or dict[str, float], optional
If set, will use this as the center value of a diverging color map.
Similar to cmap, can be a dict mapping variable names to center values.
vmin : float or dict[str, float], optional
If set, will use this as the minimum value for colormap normalization.
Similar to cmap, can be a dict mapping variable names to minimum values.
vmax : float or dict[str, float], optional
If set, will use this as the maximum value for colormap normalization.
Similar to cmap, can be a dict mapping variable names to maximum values.
robust : bool, default: False
If True, the colormap range is computed with robust quantiles (2nd and 98th percentile)
instead of the actual min and max of the data.
This is ignored if vmin and/or vmax are set.
map_kwargs : dict, optional
Additional keyword arguments are forwarded to `lonboard.Map`.
coloring_kwargs : dict, optional
Cmap, center, vmin and vmax can also be set as dictionary entries for each variable by this.
E.g. `coloring_kwargs={"air_anomaly": {"cmap": "coolwarm", "center": 0.0}}` would result in
the same as setting `cmap={"air_anomaly": "coolwarm"}` and `center={"air_anomaly": 0.0}`.

Returns
-------
map : lonboard.Map
The rendered map.

Notes
-----
Plotting currently is restricted to 1D `DataArray` objects.
"""
if isinstance(self._obj, xr.Dataset):
raise ValueError("does not work with Dataset objects, yet")

if coloring_kwargs:
# Manually building the dicts to override the function arguments
if not isinstance(cmap, dict):
cmap = dict.fromkeys(self._obj.data_vars, cmap)
if not isinstance(center, dict):
center = dict.fromkeys(self._obj.data_vars, center)
if not isinstance(vmin, dict):
vmin = dict.fromkeys(self._obj.data_vars, vmin)
if not isinstance(vmax, dict):
vmax = dict.fromkeys(self._obj.data_vars, vmax)
# Now all color_kwargs are dicts
for data_var, params in coloring_kwargs.items():
if "cmap" in params:
cmap[data_var] = params["cmap"]
if "center" in params:
center[data_var] = params["center"]
if "vmin" in params:
vmin[data_var] = params["vmin"]
if "vmax" in params:
vmax[data_var] = params["vmax"]
return explore(
self._obj,
coords=coords,
cmap=cmap,
center=center,
alpha=alpha,
coords=coords,
center=center,
vmin=vmin,
vmax=vmax,
robust=robust,
**map_kwargs,
)
Loading