Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 261 additions & 0 deletions docs/examples/datasets.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Dataset Plotting\n",
"\n",
"Plot multiple variables from an xarray Dataset with automatic or custom slot assignment."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import xarray as xr\n",
"\n",
"from xarray_plotly import config, xpx\n",
"\n",
"config.notebook()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create a Dataset with multiple variables\n",
"time = np.arange(50)\n",
"cities = [\"NYC\", \"LA\", \"Chicago\"]\n",
"\n",
"ds = xr.Dataset(\n",
" {\n",
" \"temperature\": ([\"time\", \"city\"], 20 + 5 * np.random.randn(50, 3).cumsum(axis=0) / 10),\n",
" \"humidity\": ([\"time\", \"city\"], 50 + 10 * np.random.randn(50, 3).cumsum(axis=0) / 10),\n",
" \"pressure\": ([\"time\", \"city\"], 1013 + np.random.randn(50, 3).cumsum(axis=0)),\n",
" },\n",
" coords={\"time\": time, \"city\": cities},\n",
")\n",
"ds"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plot All Variables\n",
"\n",
"When you call a plot method on a Dataset without specifying `var`, all variables are combined into a single DataArray with a new `\"variable\"` dimension:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# All variables: time -> x, variable -> color, city -> line_dash\n",
"xpx(ds).line()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Control Where \"variable\" Goes\n",
"\n",
"The `\"variable\"` dimension can be assigned to any slot:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Variables as facet columns\n",
"xpx(ds).line(facet_col=\"variable\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Variables as rows, cities as columns\n",
"xpx(ds).line(facet_row=\"variable\", facet_col=\"city\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Configure Default \"variable\" Position\n",
"\n",
"By default, `\"variable\"` is placed as the **second** dimension so it maps to `color`. This keeps your first dimension (e.g., time) on the x-axis.\n",
"\n",
"You can change this globally with `config.set_options()`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Default: position=1 (second) -> variable goes to color\n",
"# Note: to_array() puts \"variable\" first, but xpx() reorders it to position 1\n",
"print(\"Raw to_array() dims:\", ds.to_array().dims) # (variable, time, city)\n",
"print(\"After xpx reorder: (time, variable, city)\") # time->x, variable->color\n",
"xpx(ds).line(title=\"Default: variable as color (position=1)\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Position 0: variable goes first (x-axis) - usually not what you want!\n",
"with config.set_options(dataset_variable_position=0):\n",
" fig = xpx(ds).line(title=\"position=0: variable on x-axis (probably not desired)\")\n",
"fig"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Position -1: variable goes last -> city gets color, variable gets line_dash\n",
"with config.set_options(dataset_variable_position=-1):\n",
" fig = xpx(ds).line(title=\"position=-1: variable as line_dash\")\n",
"fig"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plot a Single Variable\n",
"\n",
"Use `var=\"name\"` to plot just one variable:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"xpx(ds).line(var=\"temperature\", title=\"Temperature Only\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Different Plot Types"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Bar chart - latest values by city\n",
"xpx(ds.isel(time=-1)).bar(x=\"city\", color=\"variable\", barmode=\"group\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Box plot - distribution by variable\n",
"xpx(ds).box(x=\"variable\", color=\"city\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Area chart\n",
"xpx(ds).area(var=\"humidity\", title=\"Humidity Over Time\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Scatter\n",
"xpx(ds).scatter(var=\"temperature\", title=\"Temperature Scatter\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Pie chart - snapshot at one time\n",
"xpx(ds.isel(time=-1)).pie(var=\"temperature\", names=\"city\", title=\"Temperature Distribution\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Combining Slot Assignments\n",
"\n",
"Mix explicit assignments with auto-assignment:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Explicit: variable -> facet_col, let city auto-assign to color\n",
"xpx(ds).line(facet_col=\"variable\", color=\"city\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Skip color slot with None\n",
"xpx(ds).line(var=\"temperature\", color=None)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ nav:
- Getting Started: getting-started.ipynb
- Examples:
- Plot Types: examples/plot-types.ipynb
- Dataset Plotting: examples/datasets.ipynb
- Dimensions & Facets: examples/dimensions.ipynb
- Plotly Express Options: examples/kwargs.ipynb
- Figure Customization: examples/figure.ipynb
Expand Down
22 changes: 20 additions & 2 deletions xarray_plotly/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from xarray_plotly import plotting
from xarray_plotly.common import SlotValue, auto
from xarray_plotly.config import _options


class DataArrayPlotlyAccessor:
Expand Down Expand Up @@ -349,9 +350,26 @@ def __dir__(self) -> list[str]:
return list(self.__all__) + list(super().__dir__())

def _get_dataarray(self, var: str | None) -> DataArray:
"""Get DataArray from Dataset, either single var or all via to_array()."""
"""Get DataArray from Dataset, either single var or all via to_array().

When combining all variables, "variable" is placed at the position
specified by config.dataset_variable_position (default 1, second position).
Supports Python-style negative indexing: -1 = last, -2 = second-to-last, etc.
"""
if var is None:
return self._ds.to_array(dim="variable")
da = self._ds.to_array(dim="variable")
pos = _options.dataset_variable_position
# Move "variable" to configured position
if len(da.dims) > 1 and pos != 0:
dims = list(da.dims)
dims.remove("variable")
# Use Python-style indexing (handles negative indices correctly)
# Clamp to valid range: -1 -> last, -2 -> second-to-last, etc.
n = len(dims)
insert_pos = max(0, n + pos + 1) if pos < 0 else min(pos, n)
dims.insert(insert_pos, "variable")
da = da.transpose(*dims)
return da
return self._ds[var]

def line(
Expand Down
12 changes: 12 additions & 0 deletions xarray_plotly/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class Options:
label_include_units: Append units to labels. Default True.
label_unit_format: Format string for units. Use `{units}` as placeholder.
slot_orders: Slot orders per plot type. Keys are plot types, values are tuples.
dataset_variable_position: Position of "variable" dim when plotting all Dataset
variables. Default 1 (second position, typically color). Set to 0 for first
position (x-axis), or -1 for last position.
"""

label_use_long_name: bool = True
Expand All @@ -67,6 +70,7 @@ class Options:
slot_orders: dict[str, tuple[str, ...]] = field(
default_factory=lambda: dict(DEFAULT_SLOT_ORDERS)
)
dataset_variable_position: int = 1

def to_dict(self) -> dict[str, Any]:
"""Return options as a dictionary."""
Expand All @@ -76,6 +80,7 @@ def to_dict(self) -> dict[str, Any]:
"label_include_units": self.label_include_units,
"label_unit_format": self.label_unit_format,
"slot_orders": self.slot_orders,
"dataset_variable_position": self.dataset_variable_position,
}


Expand Down Expand Up @@ -106,6 +111,7 @@ def set_options(
label_include_units: bool | None = None,
label_unit_format: str | None = None,
slot_orders: dict[str, tuple[str, ...]] | None = None,
dataset_variable_position: int | None = None,
) -> Generator[None, None, None]:
"""Set xarray_plotly options globally or as a context manager.

Expand All @@ -115,6 +121,8 @@ def set_options(
label_include_units: Append units to labels.
label_unit_format: Format string for units. Use `{units}` as placeholder.
slot_orders: Slot orders per plot type.
dataset_variable_position: Position of "variable" dim when plotting all Dataset
variables. Default 1 (second, typically color). Use 0 for first, -1 for last.

Yields:
None when used as a context manager.
Expand All @@ -136,6 +144,7 @@ def set_options(
"label_include_units": _options.label_include_units,
"label_unit_format": _options.label_unit_format,
"slot_orders": dict(_options.slot_orders),
"dataset_variable_position": _options.dataset_variable_position,
}

# Apply new values (modify in place to keep reference)
Expand All @@ -149,6 +158,8 @@ def set_options(
_options.label_unit_format = label_unit_format
if slot_orders is not None:
_options.slot_orders = dict(slot_orders)
if dataset_variable_position is not None:
_options.dataset_variable_position = dataset_variable_position

try:
yield
Expand All @@ -159,6 +170,7 @@ def set_options(
_options.label_include_units = old_values["label_include_units"]
_options.label_unit_format = old_values["label_unit_format"]
_options.slot_orders = old_values["slot_orders"]
_options.dataset_variable_position = old_values["dataset_variable_position"]


def notebook(renderer: str = "notebook") -> None:
Expand Down