From 97ea901e8e5851f13cd59025a11b8ac297ab9de1 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:55:47 +0100 Subject: [PATCH 1/8] =?UTF-8?q?=E2=8F=BA=20Done!=20All=20three=20notebooks?= =?UTF-8?q?=20have=20been=20updated=20to=20use=20xarray's=20tutorial=20dat?= =?UTF-8?q?asets.=20Here's=20a=20summary:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dependencies added to pyproject.toml docs group: - pooch>=1.0 (downloads tutorial datasets) - netcdf4>=1.6 (reads NetCDF files) Datasets now used: ┌─────────────────┬───────────────────────────────────────────────────────┬────────────────────────────────────┐ │ Dataset │ Dimensions │ Use Case │ ├─────────────────┼───────────────────────────────────────────────────────┼────────────────────────────────────┤ │ air_temperature │ time (2920), lat (25), lon (53) │ Time series, heatmaps, climatology │ ├─────────────────┼───────────────────────────────────────────────────────┼────────────────────────────────────┤ │ eraint_uvz │ month (2), level (3), latitude (241), longitude (480) │ 4D data, pressure levels, faceting │ └─────────────────┴───────────────────────────────────────────────────────┴────────────────────────────────────┘ Key improvements: - getting-started.ipynb: Uses NCEP air temperature for basic intro - plot-types.ipynb: Demonstrates all plot types with both datasets, including 4D faceting and animations through pressure levels - advanced.ipynb: Shows xarray operations (rolling, groupby, resample) with real climate data, plus Hovmoller diagrams and temperature anomalies The notebooks now show meaningful patterns (seasonal cycles, jet streams, temperature gradients) instead of random noise. --- docs/examples/advanced.ipynb | 254 +++++++++++++++++++++------------ docs/examples/plot-types.ipynb | 212 +++++++++++++-------------- docs/getting-started.ipynb | 62 ++++---- pyproject.toml | 3 + 4 files changed, 297 insertions(+), 234 deletions(-) diff --git a/docs/examples/advanced.ipynb b/docs/examples/advanced.ipynb index 1b23f46..d935b56 100644 --- a/docs/examples/advanced.ipynb +++ b/docs/examples/advanced.ipynb @@ -6,7 +6,7 @@ "source": [ "# Advanced Usage\n", "\n", - "This notebook covers advanced patterns and customization options." + "This notebook covers advanced patterns and customization options using real climate datasets." ] }, { @@ -15,8 +15,6 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "import pandas as pd\n", "import xarray as xr\n", "\n", "from xarray_plotly import config, xpx\n", @@ -24,6 +22,31 @@ "config.notebook() # Configure Plotly for notebook rendering" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Tutorial Datasets" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# NCEP air temperature: 3D (time, lat, lon)\n", + "ds_air = xr.tutorial.open_dataset(\"air_temperature\")\n", + "air = ds_air[\"air\"]\n", + "\n", + "# ERA-Interim: 4D (month, level, latitude, longitude)\n", + "ds_era = xr.tutorial.open_dataset(\"eraint_uvz\")\n", + "\n", + "print(f\"Air temperature: {air.attrs.get('long_name', air.name)}\")\n", + "print(f\" Dimensions: {dict(air.sizes)}\")\n", + "print(f\" Units: {air.attrs.get('units', 'N/A')}\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -39,27 +62,21 @@ "metadata": {}, "outputs": [], "source": [ - "da = xr.DataArray(\n", - " np.random.randn(30, 3).cumsum(axis=0) + 15,\n", - " dims=[\"time\", \"station\"],\n", - " coords={\n", - " \"time\": pd.date_range(\"2024-01-01\", periods=30, freq=\"D\"),\n", - " \"station\": [\"Alpine\", \"Coastal\", \"Urban\"],\n", - " },\n", - " name=\"temperature\",\n", - " attrs={\n", - " \"long_name\": \"Air Temperature\",\n", - " \"units\": \"°C\",\n", - " \"standard_name\": \"air_temperature\",\n", - " },\n", - ")\n", - "\n", - "# Add coordinate attributes\n", - "da.coords[\"time\"].attrs = {\"long_name\": \"Time\", \"units\": \"days\"}\n", - "da.coords[\"station\"].attrs = {\"long_name\": \"Measurement Station\"}\n", - "\n", + "# The air temperature DataArray already has rich metadata\n", + "print(f\"Variable attrs: {air.attrs}\")\n", + "print(f\"Time coord attrs: {air.coords['time'].attrs}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# Labels are automatically extracted from attrs\n", - "fig = xpx(da).line(title=\"Temperature with Auto-Labels\")\n", + "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", + " title=\"Temperature with Auto-Labels from Metadata\"\n", + ")\n", "fig" ] }, @@ -80,7 +97,9 @@ "source": [ "# Disable units in labels\n", "with config.set_options(label_include_units=False):\n", - " fig = xpx(da).line(title=\"Without Units in Labels\")\n", + " fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", + " title=\"Without Units in Labels\"\n", + " )\n", "fig" ] }, @@ -99,11 +118,11 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da).line(\n", + "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", " labels={\n", - " \"temperature\": \"Temp (°C)\",\n", + " \"air\": \"Temperature (K)\",\n", " \"time\": \"Date\",\n", - " \"station\": \"Location\",\n", + " \"lat\": \"Latitude\",\n", " },\n", " title=\"Custom Labels\",\n", ")\n", @@ -116,7 +135,9 @@ "source": [ "## Advanced Dimension Assignment\n", "\n", - "### Complex Slot Assignments" + "### Complex Slot Assignments with 4D Data\n", + "\n", + "The ERA-Interim dataset has 4 dimensions - perfect for demonstrating complex assignments:" ] }, { @@ -125,25 +146,26 @@ "metadata": {}, "outputs": [], "source": [ - "np.random.seed(42)\n", - "\n", - "da_complex = xr.DataArray(\n", - " np.random.randn(20, 3, 2, 2),\n", - " dims=[\"time\", \"city\", \"scenario\", \"model\"],\n", - " coords={\n", - " \"time\": pd.date_range(\"2024-01-01\", periods=20),\n", - " \"city\": [\"NYC\", \"LA\", \"Chicago\"],\n", - " \"scenario\": [\"SSP2\", \"SSP5\"],\n", - " \"model\": [\"GCM-A\", \"GCM-B\"],\n", - " },\n", - " name=\"projection\",\n", - ")\n", + "# U-wind: (month, level, latitude, longitude)\n", + "u = ds_era[\"u\"]\n", + "print(f\"U-wind dimensions: {dict(u.sizes)}\")\n", + "print(f\"Pressure levels: {u.coords['level'].values} hPa\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Zonal mean profile: use line_dash for pressure level, color for month\n", + "u_zonal = u.mean(\"longitude\")\n", "\n", - "# Use line_dash for one dimension, color for another\n", - "fig = xpx(da_complex.sel(city=\"NYC\")).line(\n", - " color=\"scenario\",\n", - " line_dash=\"model\",\n", - " title=\"Multiple Visual Encodings\",\n", + "fig = xpx(u_zonal).line(\n", + " x=\"latitude\",\n", + " color=\"month\",\n", + " line_dash=\"level\",\n", + " title=\"Zonal Mean U-Wind: Multiple Visual Encodings\",\n", ")\n", "fig" ] @@ -163,10 +185,11 @@ "metadata": {}, "outputs": [], "source": [ - "# Average over model dimension\n", - "fig = xpx(da_complex.mean(\"model\")).line(\n", - " facet_col=\"city\",\n", - " title=\"Ensemble Mean by City\",\n", + "# Average over months to compare pressure levels\n", + "fig = xpx(u.mean([\"month\", \"longitude\"])).line(\n", + " x=\"latitude\",\n", + " color=\"level\",\n", + " title=\"Mean Zonal Wind Profile by Pressure Level\",\n", ")\n", "fig" ] @@ -177,10 +200,12 @@ "metadata": {}, "outputs": [], "source": [ - "# Select a specific slice\n", - "fig = xpx(da_complex.sel(scenario=\"SSP5\", model=\"GCM-A\")).line(\n", - " facet_col=\"city\",\n", - " title=\"SSP5 / GCM-A Projections\",\n", + "# Select specific slices for focused analysis\n", + "fig = xpx(u.sel(level=200)).imshow(\n", + " facet_col=\"month\",\n", + " color_continuous_scale=\"RdBu_r\",\n", + " color_continuous_midpoint=0,\n", + " title=\"200 hPa U-Wind (Jet Stream Level)\",\n", ")\n", "fig" ] @@ -205,9 +230,7 @@ "metadata": {}, "outputs": [], "source": [ - "da_simple = da.sel(station=\"Urban\")\n", - "\n", - "fig = xpx(da_simple).line(\n", + "fig = xpx(air.sel(lat=45, lon=260, method=\"nearest\")).line(\n", " template=\"plotly_dark\",\n", " title=\"Dark Theme\",\n", ")\n", @@ -229,7 +252,7 @@ "source": [ "import plotly.express as px\n", "\n", - "fig = xpx(da).line(\n", + "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\")).line(\n", " color_discrete_sequence=px.colors.qualitative.Set2,\n", " title=\"Custom Color Palette\",\n", ")\n", @@ -240,7 +263,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Heatmap Colorscales" + "### Heatmap Colorscales\n", + "\n", + "Climate data often benefits from diverging colorscales centered at meaningful values:" ] }, { @@ -249,17 +274,18 @@ "metadata": {}, "outputs": [], "source": [ - "da_2d = xr.DataArray(\n", - " np.random.randn(20, 30),\n", - " dims=[\"lat\", \"lon\"],\n", - " name=\"anomaly\",\n", - ")\n", + "# Temperature anomaly from mean\n", + "air_mean = air.mean(\"time\")\n", + "air_anomaly = air.isel(time=0) - air_mean\n", + "air_anomaly.name = \"temperature_anomaly\"\n", + "air_anomaly.attrs[\"long_name\"] = \"Temperature Anomaly\"\n", + "air_anomaly.attrs[\"units\"] = \"K\"\n", "\n", "# Diverging colorscale centered at zero\n", - "fig = xpx(da_2d).imshow(\n", + "fig = xpx(air_anomaly).imshow(\n", " color_continuous_scale=\"RdBu_r\",\n", " color_continuous_midpoint=0,\n", - " title=\"Diverging Colorscale\",\n", + " title=\"Temperature Anomaly (Diverging Colorscale)\",\n", ")\n", "fig" ] @@ -279,14 +305,14 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da).line()\n", + "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\")).line()\n", "\n", - "# Add horizontal reference line\n", - "fig.add_hline(y=15, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Reference\")\n", + "# Add horizontal reference line (freezing point in Kelvin)\n", + "fig.add_hline(y=273.15, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Freezing Point\")\n", "\n", "# Update layout\n", "fig.update_layout(\n", - " title=\"Temperature with Reference Line\",\n", + " title=\"Temperature with Freezing Point Reference\",\n", " legend={\n", " \"orientation\": \"h\",\n", " \"yanchor\": \"bottom\",\n", @@ -312,7 +338,7 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da).line()\n", + "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\").isel(time=slice(0, 100))).line()\n", "\n", "# Make all lines thicker\n", "fig.update_traces(line_width=3)\n", @@ -353,14 +379,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Integration Examples" + "## Integration with xarray Operations\n", + "\n", + "xarray_plotly works seamlessly with xarray's computation methods:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### With xarray operations" + "### Rolling Mean" ] }, { @@ -369,36 +397,33 @@ "metadata": {}, "outputs": [], "source": [ - "# Rolling mean\n", - "da_smooth = da.rolling(time=7, center=True).mean()\n", + "# 30-day rolling mean to smooth daily variations\n", + "air_smooth = air.sel(lat=45, lon=260, method=\"nearest\").rolling(time=30, center=True).mean()\n", "\n", - "fig = xpx(da_smooth).line(\n", - " title=\"7-Day Rolling Mean\",\n", + "fig = xpx(air_smooth).line(\n", + " title=\"30-Day Rolling Mean Temperature\",\n", ")\n", "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Groupby Operations (Climatology)" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Groupby operations\n", - "da_monthly = xr.DataArray(\n", - " np.random.randn(365, 3).cumsum(axis=0),\n", - " dims=[\"time\", \"category\"],\n", - " coords={\n", - " \"time\": pd.date_range(\"2024-01-01\", periods=365),\n", - " \"category\": [\"A\", \"B\", \"C\"],\n", - " },\n", - " name=\"value\",\n", - ")\n", - "\n", - "monthly_mean = da_monthly.groupby(\"time.month\").mean()\n", + "# Monthly climatology\n", + "monthly_clim = air.sel(lon=260, method=\"nearest\").groupby(\"time.month\").mean()\n", "\n", - "fig = xpx(monthly_mean).line(\n", - " title=\"Monthly Climatology\",\n", + "fig = xpx(monthly_clim.sel(lat=[30, 45, 60], method=\"nearest\")).line(\n", + " title=\"Monthly Temperature Climatology\",\n", ")\n", "fig.update_xaxes(\n", " tickmode=\"array\",\n", @@ -407,6 +432,53 @@ ")\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Resampling" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Weekly mean temperature\n", + "weekly = air.sel(lat=45, lon=260, method=\"nearest\").resample(time=\"W\").mean()\n", + "\n", + "fig = xpx(weekly).line(\n", + " title=\"Weekly Mean Temperature\",\n", + " markers=True,\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Spatial Statistics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Zonal mean temperature over time\n", + "zonal_mean = air.mean(\"lon\")\n", + "\n", + "# Show as heatmap (Hovmoller diagram)\n", + "fig = xpx(zonal_mean).imshow(\n", + " title=\"Zonal Mean Temperature (Hovmoller Diagram)\",\n", + " color_continuous_scale=\"thermal\",\n", + ")\n", + "fig" + ] } ], "metadata": { diff --git a/docs/examples/plot-types.ipynb b/docs/examples/plot-types.ipynb index 94ab5b7..01f1fe5 100644 --- a/docs/examples/plot-types.ipynb +++ b/docs/examples/plot-types.ipynb @@ -6,7 +6,7 @@ "source": [ "# Plot Types\n", "\n", - "This notebook demonstrates all the plot types available in xarray_plotly." + "This notebook demonstrates all the plot types available in xarray_plotly using real climate data from xarray's tutorial datasets." ] }, { @@ -15,8 +15,6 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "import pandas as pd\n", "import xarray as xr\n", "\n", "from xarray_plotly import config, xpx\n", @@ -28,9 +26,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Sample Data\n", + "## Load Tutorial Datasets\n", "\n", - "Let's create some sample data to work with:" + "We'll use two datasets:\n", + "- **air_temperature**: NCEP reanalysis with dimensions (time, lat, lon)\n", + "- **eraint_uvz**: ERA-Interim reanalysis with dimensions (month, level, latitude, longitude) - great for 4D data!" ] }, { @@ -39,40 +39,15 @@ "metadata": {}, "outputs": [], "source": [ - "np.random.seed(42)\n", + "# 3D dataset: time series of air temperature\n", + "ds_air = xr.tutorial.open_dataset(\"air_temperature\")\n", + "air = ds_air[\"air\"]\n", + "print(f\"air_temperature: {dict(air.sizes)}\")\n", "\n", - "# Time series data\n", - "da_ts = xr.DataArray(\n", - " np.random.randn(30, 3).cumsum(axis=0),\n", - " dims=[\"time\", \"category\"],\n", - " coords={\n", - " \"time\": pd.date_range(\"2024-01-01\", periods=30),\n", - " \"category\": [\"A\", \"B\", \"C\"],\n", - " },\n", - " name=\"value\",\n", - ")\n", - "\n", - "# 2D grid data\n", - "da_2d = xr.DataArray(\n", - " np.random.rand(20, 30),\n", - " dims=[\"lat\", \"lon\"],\n", - " coords={\n", - " \"lat\": np.linspace(-90, 90, 20),\n", - " \"lon\": np.linspace(-180, 180, 30),\n", - " },\n", - " name=\"temperature\",\n", - ")\n", - "\n", - "# Categorical data\n", - "da_cat = xr.DataArray(\n", - " np.random.rand(4, 3) * 100,\n", - " dims=[\"product\", \"region\"],\n", - " coords={\n", - " \"product\": [\"Widget\", \"Gadget\", \"Gizmo\", \"Thingamajig\"],\n", - " \"region\": [\"North\", \"South\", \"West\"],\n", - " },\n", - " name=\"sales\",\n", - ")" + "# 4D dataset: atmospheric variables at different pressure levels\n", + "ds_era = xr.tutorial.open_dataset(\"eraint_uvz\")\n", + "print(f\"eraint_uvz variables: {list(ds_era.data_vars)}\")\n", + "print(f\"eraint_uvz dimensions: {dict(ds_era.sizes)}\")" ] }, { @@ -90,7 +65,10 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_ts).line(title=\"Line Plot\")\n", + "# Temperature over time at different latitudes\n", + "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\")).line(\n", + " title=\"Air Temperature Time Series\"\n", + ")\n", "fig" ] }, @@ -107,7 +85,10 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_ts).line(markers=True, title=\"Line Plot with Markers\")\n", + "# Subsample for visibility\n", + "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\").isel(time=slice(0, 50))).line(\n", + " markers=True, title=\"Temperature with Markers\"\n", + ")\n", "fig" ] }, @@ -117,7 +98,7 @@ "source": [ "## Bar Chart\n", "\n", - "Best for comparing categorical data:" + "Best for comparing categorical data. Let's look at monthly mean temperature:" ] }, { @@ -126,7 +107,11 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_cat).bar(title=\"Stacked Bar Chart\")\n", + "# Monthly climatology at different locations\n", + "monthly = air.sel(lon=260, method=\"nearest\").groupby(\"time.month\").mean()\n", + "fig = xpx(monthly.sel(lat=[30, 45, 60], method=\"nearest\")).bar(\n", + " title=\"Monthly Mean Temperature by Latitude\"\n", + ")\n", "fig" ] }, @@ -143,7 +128,9 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_cat).bar(barmode=\"group\", title=\"Grouped Bar Chart\")\n", + "fig = xpx(monthly.sel(lat=[30, 45, 60], method=\"nearest\")).bar(\n", + " barmode=\"group\", title=\"Grouped Monthly Temperature\"\n", + ")\n", "fig" ] }, @@ -162,18 +149,17 @@ "metadata": {}, "outputs": [], "source": [ - "# Use absolute values for stacking to make sense\n", - "da_positive = xr.DataArray(\n", - " np.abs(np.random.randn(30, 3)) * 10,\n", - " dims=[\"time\", \"source\"],\n", - " coords={\n", - " \"time\": pd.date_range(\"2024-01-01\", periods=30),\n", - " \"source\": [\"Solar\", \"Wind\", \"Hydro\"],\n", - " },\n", - " name=\"energy\",\n", + "# Use wind components from ERA-Interim (take absolute values for stacking)\n", + "u_wind = abs(ds_era[\"u\"].isel(month=0, level=0))\n", + "v_wind = abs(ds_era[\"v\"].isel(month=0, level=0))\n", + "\n", + "# Create a combined dataset for stacked area\n", + "wind_components = xr.concat(\n", + " [u_wind.mean(\"longitude\"), v_wind.mean(\"longitude\")],\n", + " dim=xr.Variable(\"component\", [\"U wind\", \"V wind\"]),\n", ")\n", "\n", - "fig = xpx(da_positive).area(title=\"Stacked Area Chart\")\n", + "fig = xpx(wind_components).area(title=\"Wind Components by Latitude\")\n", "fig" ] }, @@ -192,7 +178,10 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_ts).scatter(title=\"Scatter Plot\")\n", + "# Temperature at different times\n", + "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\").isel(time=slice(0, 100))).scatter(\n", + " title=\"Temperature Scatter\"\n", + ")\n", "fig" ] }, @@ -211,7 +200,11 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_2d).scatter(x=\"lon\", y=\"lat\", color=\"value\", title=\"Lat/Lon Scatter\")\n", + "# Geopotential at 500 hPa\n", + "geo = ds_era[\"z\"].isel(month=0, level=1) # level=1 is 500 hPa\n", + "fig = xpx(geo).scatter(\n", + " x=\"longitude\", y=\"latitude\", color=\"value\", title=\"Geopotential Height at 500 hPa\"\n", + ")\n", "fig" ] }, @@ -230,15 +223,11 @@ "metadata": {}, "outputs": [], "source": [ - "# Create data with more samples\n", - "da_dist = xr.DataArray(\n", - " np.random.randn(100, 4) + np.array([0, 1, 2, 3]),\n", - " dims=[\"sample\", \"group\"],\n", - " coords={\"group\": [\"Control\", \"Treatment A\", \"Treatment B\", \"Treatment C\"]},\n", - " name=\"response\",\n", + "# Temperature distribution across different latitudes\n", + "# Sample time dimension to create distribution\n", + "fig = xpx(air.sel(lat=[25, 35, 45, 55, 65], lon=260, method=\"nearest\")).box(\n", + " title=\"Temperature Distribution by Latitude\"\n", ")\n", - "\n", - "fig = xpx(da_dist).box(title=\"Box Plot\")\n", "fig" ] }, @@ -257,7 +246,8 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_2d).imshow(title=\"Heatmap\")\n", + "# Air temperature at a single time step\n", + "fig = xpx(air.isel(time=0)).imshow(title=\"Air Temperature (2013-01-01)\")\n", "fig" ] }, @@ -274,9 +264,12 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da_2d).imshow(\n", - " color_continuous_scale=\"Viridis\",\n", - " title=\"Heatmap with Viridis colorscale\",\n", + "# U-wind component at 200 hPa (jet stream level)\n", + "u_200 = ds_era[\"u\"].isel(month=0, level=0) # level=0 is 200 hPa\n", + "fig = xpx(u_200).imshow(\n", + " color_continuous_scale=\"RdBu_r\",\n", + " color_continuous_midpoint=0,\n", + " title=\"U-Wind at 200 hPa (Jet Stream)\",\n", ")\n", "fig" ] @@ -287,7 +280,7 @@ "source": [ "## Faceting\n", "\n", - "All plot types support faceting to create subplot grids:" + "All plot types support faceting to create subplot grids. This is especially powerful with the 4D ERA-Interim data:" ] }, { @@ -296,22 +289,30 @@ "metadata": {}, "outputs": [], "source": [ - "# 3D data for faceting\n", - "da_3d = xr.DataArray(\n", - " np.random.randn(30, 3, 2).cumsum(axis=0),\n", - " dims=[\"time\", \"city\", \"scenario\"],\n", - " coords={\n", - " \"time\": pd.date_range(\"2024-01-01\", periods=30),\n", - " \"city\": [\"NYC\", \"LA\", \"Chicago\"],\n", - " \"scenario\": [\"Low\", \"High\"],\n", - " },\n", - " name=\"value\",\n", - ")\n", + "# U-wind at different pressure levels and months\n", + "u_zonal = ds_era[\"u\"].mean(\"longitude\") # Zonal mean\n", "\n", - "fig = xpx(da_3d).line(\n", - " facet_col=\"city\",\n", - " facet_row=\"scenario\",\n", - " title=\"Faceted Line Plot\",\n", + "fig = xpx(u_zonal).line(\n", + " x=\"latitude\",\n", + " color=\"level\",\n", + " facet_col=\"month\",\n", + " title=\"Zonal Mean U-Wind by Pressure Level\",\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Heatmaps faceted by pressure level\n", + "fig = xpx(ds_era[\"u\"].isel(month=0)).imshow(\n", + " facet_col=\"level\",\n", + " color_continuous_scale=\"RdBu_r\",\n", + " color_continuous_midpoint=0,\n", + " title=\"U-Wind at Different Pressure Levels\",\n", ")\n", "fig" ] @@ -331,35 +332,28 @@ "metadata": {}, "outputs": [], "source": [ - "# Create monthly data\n", - "da_monthly = xr.DataArray(\n", - " np.random.rand(12, 4) * 100,\n", - " dims=[\"month\", \"product\"],\n", - " coords={\n", - " \"month\": [\n", - " \"Jan\",\n", - " \"Feb\",\n", - " \"Mar\",\n", - " \"Apr\",\n", - " \"May\",\n", - " \"Jun\",\n", - " \"Jul\",\n", - " \"Aug\",\n", - " \"Sep\",\n", - " \"Oct\",\n", - " \"Nov\",\n", - " \"Dec\",\n", - " ],\n", - " \"product\": [\"A\", \"B\", \"C\", \"D\"],\n", - " },\n", - " name=\"sales\",\n", + "# Animate through pressure levels\n", + "fig = xpx(ds_era[\"z\"].isel(month=0)).imshow(\n", + " animation_frame=\"level\",\n", + " title=\"Geopotential Height by Pressure Level\",\n", + " color_continuous_scale=\"Viridis\",\n", ")\n", + "fig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Monthly temperature climatology animation\n", + "monthly_map = air.groupby(\"time.month\").mean()\n", "\n", - "fig = xpx(da_monthly).bar(\n", - " x=\"product\",\n", + "fig = xpx(monthly_map).imshow(\n", " animation_frame=\"month\",\n", - " title=\"Monthly Sales (Animated)\",\n", - " range_y=[0, 120],\n", + " title=\"Monthly Mean Air Temperature\",\n", + " color_continuous_scale=\"thermal\",\n", ")\n", "fig" ] diff --git a/docs/getting-started.ipynb b/docs/getting-started.ipynb index cd80907..6996f53 100644 --- a/docs/getting-started.ipynb +++ b/docs/getting-started.ipynb @@ -41,8 +41,6 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "import pandas as pd\n", "import xarray as xr\n", "\n", "from xarray_plotly import config, xpx\n", @@ -54,9 +52,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Create Sample Data\n", + "## Load Sample Data\n", "\n", - "Let's create a DataArray with multiple dimensions:" + "We'll use xarray's built-in tutorial datasets. The `air_temperature` dataset contains NCEP reanalysis data with 3 dimensions:" ] }, { @@ -65,22 +63,19 @@ "metadata": {}, "outputs": [], "source": [ - "# Create sample climate data\n", - "np.random.seed(42)\n", - "\n", - "da = xr.DataArray(\n", - " np.random.randn(50, 3, 2).cumsum(axis=0), # Random walk\n", - " dims=[\"time\", \"city\", \"scenario\"],\n", - " coords={\n", - " \"time\": pd.date_range(\"2020-01-01\", periods=50, freq=\"D\"),\n", - " \"city\": [\"New York\", \"Los Angeles\", \"Chicago\"],\n", - " \"scenario\": [\"baseline\", \"warming\"],\n", - " },\n", - " name=\"temperature\",\n", - " attrs={\"long_name\": \"Temperature Anomaly\", \"units\": \"°C\"},\n", - ")\n", - "\n", - "da.to_dataframe()" + "ds = xr.tutorial.open_dataset(\"air_temperature\")\n", + "ds" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Extract the air temperature DataArray\n", + "air = ds[\"air\"]\n", + "air" ] }, { @@ -89,7 +84,7 @@ "source": [ "## Your First Plot\n", "\n", - "Create an interactive line plot with a single method call:" + "Create an interactive line plot showing temperature over time at a specific location:" ] }, { @@ -98,8 +93,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Dimensions auto-assign: time→x, city→color, scenario→facet_col\n", - "fig = xpx(da).line()\n", + "# Select a few locations and plot temperature over time\n", + "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line()\n", "fig" ] }, @@ -125,8 +120,7 @@ "| Dimension | Slot |\n", "|-----------|------|\n", "| time (1st) | x-axis |\n", - "| city (2nd) | color |\n", - "| scenario (3rd) | facet_col |\n", + "| lat (2nd) | color |\n", "\n", "You can override this with explicit assignments:" ] @@ -137,8 +131,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Put scenario on color, city on facets\n", - "fig = xpx(da).line(color=\"scenario\", facet_col=\"city\")\n", + "# Compare multiple longitudes, using latitude as x-axis\n", + "fig = xpx(air.isel(time=0).sel(lon=[240, 260, 280], method=\"nearest\")).line(x=\"lat\", color=\"lon\")\n", "fig" ] }, @@ -157,8 +151,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Skip color, so city goes to line_dash instead\n", - "fig = xpx(da.sel(scenario=\"baseline\")).line(color=None)\n", + "# Skip color, so lat goes to line_dash instead\n", + "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(color=None)\n", "fig" ] }, @@ -177,12 +171,12 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da).line()\n", + "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line()\n", "\n", "fig.update_layout(\n", - " title=\"Temperature Anomaly Projections\",\n", + " title=\"Air Temperature at Different Latitudes\",\n", " template=\"plotly_white\",\n", - " legend_title_text=\"City\",\n", + " legend_title_text=\"Latitude\",\n", ")\n", "\n", "fig" @@ -201,8 +195,8 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(da).line(\n", - " title=\"Temperature Trends\",\n", + "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", + " title=\"NCEP Reanalysis Temperature\",\n", " color_discrete_sequence=[\"#E63946\", \"#457B9D\", \"#2A9D8F\"],\n", " template=\"simple_white\",\n", ")\n", diff --git a/pyproject.toml b/pyproject.toml index 5f7f927..e71ddac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,9 @@ docs = [ "mkdocstrings[python]>=0.24", "mkdocs-jupyter>=0.24", "mkdocs-plotly-plugin>=0.1", + "pooch>=1.0", + "netcdf4>=1.6", + "jupyter>=1.0", ] [project.entry-points."xarray.backends"] From db62607d4a2d338c598a17f4b9340a8b715fbe3f Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:02:20 +0100 Subject: [PATCH 2/8] Simplify data in getting started --- docs/getting-started.ipynb | 60 ++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/docs/getting-started.ipynb b/docs/getting-started.ipynb index 6996f53..11c0bfa 100644 --- a/docs/getting-started.ipynb +++ b/docs/getting-started.ipynb @@ -41,6 +41,7 @@ "metadata": {}, "outputs": [], "source": [ + "import plotly.express as px\n", "import xarray as xr\n", "\n", "from xarray_plotly import config, xpx\n", @@ -54,7 +55,7 @@ "source": [ "## Load Sample Data\n", "\n", - "We'll use xarray's built-in tutorial datasets. The `air_temperature` dataset contains NCEP reanalysis data with 3 dimensions:" + "We'll use plotly's built-in stock price data and convert it to an xarray DataArray:" ] }, { @@ -63,19 +64,20 @@ "metadata": {}, "outputs": [], "source": [ - "ds = xr.tutorial.open_dataset(\"air_temperature\")\n", - "ds" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Extract the air temperature DataArray\n", - "air = ds[\"air\"]\n", - "air" + "# Load stock prices from plotly\n", + "df = px.data.stocks()\n", + "df = df.set_index(\"date\")\n", + "df.index = df.index.astype(\"datetime64[ns]\")\n", + "\n", + "# Convert to xarray DataArray\n", + "stocks = xr.DataArray(\n", + " df.values,\n", + " dims=[\"date\", \"company\"],\n", + " coords={\"date\": df.index, \"company\": df.columns.tolist()},\n", + " name=\"price\",\n", + " attrs={\"long_name\": \"Stock Price\", \"units\": \"normalized\"},\n", + ")\n", + "stocks" ] }, { @@ -84,7 +86,7 @@ "source": [ "## Your First Plot\n", "\n", - "Create an interactive line plot showing temperature over time at a specific location:" + "Create an interactive line plot with a single method call:" ] }, { @@ -93,8 +95,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Select a few locations and plot temperature over time\n", - "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line()\n", + "# Dimensions auto-assign: date->x, company->color\n", + "fig = xpx(stocks).line()\n", "fig" ] }, @@ -119,8 +121,8 @@ "\n", "| Dimension | Slot |\n", "|-----------|------|\n", - "| time (1st) | x-axis |\n", - "| lat (2nd) | color |\n", + "| date (1st) | x-axis |\n", + "| company (2nd) | color |\n", "\n", "You can override this with explicit assignments:" ] @@ -131,8 +133,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Compare multiple longitudes, using latitude as x-axis\n", - "fig = xpx(air.isel(time=0).sel(lon=[240, 260, 280], method=\"nearest\")).line(x=\"lat\", color=\"lon\")\n", + "# Put company on x-axis, date on color (just first few dates)\n", + "fig = xpx(stocks.isel(date=[0, 25, 50, 75, 100])).bar(x=\"company\", color=\"date\")\n", "fig" ] }, @@ -151,8 +153,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Skip color, so lat goes to line_dash instead\n", - "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(color=None)\n", + "# Skip color, so company goes to line_dash instead\n", + "fig = xpx(stocks.sel(company=[\"GOOG\", \"AAPL\", \"MSFT\"])).line(color=None)\n", "fig" ] }, @@ -171,12 +173,12 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line()\n", + "fig = xpx(stocks).line()\n", "\n", "fig.update_layout(\n", - " title=\"Air Temperature at Different Latitudes\",\n", + " title=\"Tech Stock Performance (2018-2019)\",\n", " template=\"plotly_white\",\n", - " legend_title_text=\"Latitude\",\n", + " legend_title_text=\"Company\",\n", ")\n", "\n", "fig" @@ -195,9 +197,9 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", - " title=\"NCEP Reanalysis Temperature\",\n", - " color_discrete_sequence=[\"#E63946\", \"#457B9D\", \"#2A9D8F\"],\n", + "fig = xpx(stocks).line(\n", + " title=\"Stock Prices\",\n", + " color_discrete_sequence=[\"#E63946\", \"#457B9D\", \"#2A9D8F\", \"#E9C46A\", \"#F4A261\", \"#264653\"],\n", " template=\"simple_white\",\n", ")\n", "fig" From 5b1a6e87e17bb0cbab45631ef14dcd5d9f8c64a4 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:02:49 +0100 Subject: [PATCH 3/8] pinpoint dev deps and add dependabot Allow automerge of dependabot --- .github/dependabot.yml | 25 ++++++++++++++++++ .github/workflows/dependabot-auto-merge.yml | 24 ++++++++++++++++++ pyproject.toml | 28 ++++++++++----------- 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2e8b124 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + groups: + dev-dependencies: + patterns: + - "pytest*" + - "mypy" + - "ruff" + - "pre-commit" + - "nbstripout" + docs-dependencies: + patterns: + - "mkdocs*" + - "pooch" + - "netcdf4" + - "jupyter" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..3a701d5 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,24 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Auto-merge minor and patch updates + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index e71ddac..37c0f94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,22 +32,22 @@ Repository = "https://github.com/FBumann/xarray_plotly" [project.optional-dependencies] dev = [ - "pytest>=7.0", - "pytest-cov>=4.0", - "mypy>=1.0", - "ruff>=0.4", - "pre-commit>=3.0", - "nbstripout>=0.6", + "pytest==8.3.5", + "pytest-cov==6.0.0", + "mypy==1.14.1", + "ruff==0.9.2", + "pre-commit==4.0.1", + "nbstripout==0.8.1", ] docs = [ - "mkdocs>=1.5", - "mkdocs-material>=9.0", - "mkdocstrings[python]>=0.24", - "mkdocs-jupyter>=0.24", - "mkdocs-plotly-plugin>=0.1", - "pooch>=1.0", - "netcdf4>=1.6", - "jupyter>=1.0", + "mkdocs==1.6.1", + "mkdocs-material==9.5.49", + "mkdocstrings[python]==0.27.0", + "mkdocs-jupyter==0.25.1", + "mkdocs-plotly-plugin==0.1.5", + "pooch==1.8.2", + "netcdf4==1.7.2", + "jupyter==1.1.1", ] [project.entry-points."xarray.backends"] From cc733105ae0cf71ed799b7612abe6479a76aeffd Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:10:02 +0100 Subject: [PATCH 4/8] Change notbeook data --- docs/examples/plot-types.ipynb | 736 +++++++++++++++++---------------- 1 file changed, 383 insertions(+), 353 deletions(-) diff --git a/docs/examples/plot-types.ipynb b/docs/examples/plot-types.ipynb index 01f1fe5..cd56d93 100644 --- a/docs/examples/plot-types.ipynb +++ b/docs/examples/plot-types.ipynb @@ -1,361 +1,391 @@ { "cells": [ { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ - "# Plot Types\n", - "\n", - "This notebook demonstrates all the plot types available in xarray_plotly using real climate data from xarray's tutorial datasets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import xarray as xr\n", - "\n", - "from xarray_plotly import config, xpx\n", - "\n", - "config.notebook() # Configure Plotly for notebook rendering" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load Tutorial Datasets\n", - "\n", - "We'll use two datasets:\n", - "- **air_temperature**: NCEP reanalysis with dimensions (time, lat, lon)\n", - "- **eraint_uvz**: ERA-Interim reanalysis with dimensions (month, level, latitude, longitude) - great for 4D data!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 3D dataset: time series of air temperature\n", - "ds_air = xr.tutorial.open_dataset(\"air_temperature\")\n", - "air = ds_air[\"air\"]\n", - "print(f\"air_temperature: {dict(air.sizes)}\")\n", - "\n", - "# 4D dataset: atmospheric variables at different pressure levels\n", - "ds_era = xr.tutorial.open_dataset(\"eraint_uvz\")\n", - "print(f\"eraint_uvz variables: {list(ds_era.data_vars)}\")\n", - "print(f\"eraint_uvz dimensions: {dict(ds_era.sizes)}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Line Plot\n", - "\n", - "Best for time series and continuous data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Temperature over time at different latitudes\n", - "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\")).line(\n", - " title=\"Air Temperature Time Series\"\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### With markers" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Subsample for visibility\n", - "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\").isel(time=slice(0, 50))).line(\n", - " markers=True, title=\"Temperature with Markers\"\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Bar Chart\n", - "\n", - "Best for comparing categorical data. Let's look at monthly mean temperature:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Monthly climatology at different locations\n", - "monthly = air.sel(lon=260, method=\"nearest\").groupby(\"time.month\").mean()\n", - "fig = xpx(monthly.sel(lat=[30, 45, 60], method=\"nearest\")).bar(\n", - " title=\"Monthly Mean Temperature by Latitude\"\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Grouped bars" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(monthly.sel(lat=[30, 45, 60], method=\"nearest\")).bar(\n", - " barmode=\"group\", title=\"Grouped Monthly Temperature\"\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Area Chart\n", - "\n", - "Best for showing composition over time:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Use wind components from ERA-Interim (take absolute values for stacking)\n", - "u_wind = abs(ds_era[\"u\"].isel(month=0, level=0))\n", - "v_wind = abs(ds_era[\"v\"].isel(month=0, level=0))\n", - "\n", - "# Create a combined dataset for stacked area\n", - "wind_components = xr.concat(\n", - " [u_wind.mean(\"longitude\"), v_wind.mean(\"longitude\")],\n", - " dim=xr.Variable(\"component\", [\"U wind\", \"V wind\"]),\n", - ")\n", - "\n", - "fig = xpx(wind_components).area(title=\"Wind Components by Latitude\")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Scatter Plot\n", - "\n", - "Best for showing relationships between variables:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Temperature at different times\n", - "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\").isel(time=slice(0, 100))).scatter(\n", - " title=\"Temperature Scatter\"\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Dimension vs Dimension (Geographic style)\n", - "\n", - "You can plot one dimension against another, with values shown as color:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Geopotential at 500 hPa\n", - "geo = ds_era[\"z\"].isel(month=0, level=1) # level=1 is 500 hPa\n", - "fig = xpx(geo).scatter(\n", - " x=\"longitude\", y=\"latitude\", color=\"value\", title=\"Geopotential Height at 500 hPa\"\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Box Plot\n", - "\n", - "Best for showing distributions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Temperature distribution across different latitudes\n", - "# Sample time dimension to create distribution\n", - "fig = xpx(air.sel(lat=[25, 35, 45, 55, 65], lon=260, method=\"nearest\")).box(\n", - " title=\"Temperature Distribution by Latitude\"\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Heatmap (imshow)\n", - "\n", - "Best for 2D grid data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Air temperature at a single time step\n", - "fig = xpx(air.isel(time=0)).imshow(title=\"Air Temperature (2013-01-01)\")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### With different colorscale" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# U-wind component at 200 hPa (jet stream level)\n", - "u_200 = ds_era[\"u\"].isel(month=0, level=0) # level=0 is 200 hPa\n", - "fig = xpx(u_200).imshow(\n", - " color_continuous_scale=\"RdBu_r\",\n", - " color_continuous_midpoint=0,\n", - " title=\"U-Wind at 200 hPa (Jet Stream)\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Faceting\n", - "\n", - "All plot types support faceting to create subplot grids. This is especially powerful with the 4D ERA-Interim data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# U-wind at different pressure levels and months\n", - "u_zonal = ds_era[\"u\"].mean(\"longitude\") # Zonal mean\n", - "\n", - "fig = xpx(u_zonal).line(\n", - " x=\"latitude\",\n", - " color=\"level\",\n", - " facet_col=\"month\",\n", - " title=\"Zonal Mean U-Wind by Pressure Level\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Heatmaps faceted by pressure level\n", - "fig = xpx(ds_era[\"u\"].isel(month=0)).imshow(\n", - " facet_col=\"level\",\n", - " color_continuous_scale=\"RdBu_r\",\n", - " color_continuous_midpoint=0,\n", - " title=\"U-Wind at Different Pressure Levels\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Animation\n", - "\n", - "Create animated plots by assigning a dimension to `animation_frame`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Animate through pressure levels\n", - "fig = xpx(ds_era[\"z\"].isel(month=0)).imshow(\n", - " animation_frame=\"level\",\n", - " title=\"Geopotential Height by Pressure Level\",\n", - " color_continuous_scale=\"Viridis\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Monthly temperature climatology animation\n", - "monthly_map = air.groupby(\"time.month\").mean()\n", - "\n", - "fig = xpx(monthly_map).imshow(\n", - " animation_frame=\"month\",\n", - " title=\"Monthly Mean Air Temperature\",\n", - " color_continuous_scale=\"thermal\",\n", - ")\n", - "fig" + "{\n", + " \"cells\": [\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"# Plot Types\\n\",\n", + " \"\\n\",\n", + " \"This notebook demonstrates all the plot types available in xarray_plotly.\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"import plotly.express as px\\n\",\n", + " \"import xarray as xr\\n\",\n", + " \"\\n\",\n", + " \"from xarray_plotly import config, xpx\\n\",\n", + " \"\\n\",\n", + " \"config.notebook() # Configure Plotly for notebook rendering\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Load Sample Data\\n\",\n", + " \"\\n\",\n", + " \"We'll use plotly's built-in datasets converted to xarray:\\n\",\n", + " \"- **stocks**: Tech company stock prices over time\\n\",\n", + " \"- **gapminder**: Country statistics (life expectancy, GDP, population) by year\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Stock prices: 2D (date, company)\\n\",\n", + " \"df_stocks = px.data.stocks().set_index(\\\"date\\\")\\n\",\n", + " \"df_stocks.index = df_stocks.index.astype(\\\"datetime64[ns]\\\")\\n\",\n", + " \"\\n\",\n", + " \"stocks = xr.DataArray(\\n\",\n", + " \" df_stocks.values,\\n\",\n", + " \" dims=[\\\"date\\\", \\\"company\\\"],\\n\",\n", + " \" coords={\\\"date\\\": df_stocks.index, \\\"company\\\": df_stocks.columns.tolist()},\\n\",\n", + " \" name=\\\"price\\\",\\n\",\n", + " \")\\n\",\n", + " \"print(f\\\"stocks: {dict(stocks.sizes)}\\\")\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Gapminder: pivot to create multi-dimensional arrays\\n\",\n", + " \"df_gap = px.data.gapminder()\\n\",\n", + " \"\\n\",\n", + " \"# Life expectancy: 2D (year, country) - select a few countries\\n\",\n", + " \"countries = [\\\"United States\\\", \\\"China\\\", \\\"Germany\\\", \\\"Brazil\\\", \\\"Nigeria\\\"]\\n\",\n", + " \"df_life = df_gap[df_gap[\\\"country\\\"].isin(countries)].pivot(\\n\",\n", + " \" index=\\\"year\\\", columns=\\\"country\\\", values=\\\"lifeExp\\\"\\n\",\n", + " \")\\n\",\n", + " \"\\n\",\n", + " \"life_exp = xr.DataArray(\\n\",\n", + " \" df_life.values,\\n\",\n", + " \" dims=[\\\"year\\\", \\\"country\\\"],\\n\",\n", + " \" coords={\\\"year\\\": df_life.index, \\\"country\\\": df_life.columns.tolist()},\\n\",\n", + " \" name=\\\"life_expectancy\\\",\\n\",\n", + " \" attrs={\\\"units\\\": \\\"years\\\"},\\n\",\n", + " \")\\n\",\n", + " \"print(f\\\"life_exp: {dict(life_exp.sizes)}\\\")\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# GDP per capita by continent and year (aggregated)\\n\",\n", + " \"df_continent = df_gap.groupby([\\\"continent\\\", \\\"year\\\"])[\\\"gdpPercap\\\"].mean().reset_index()\\n\",\n", + " \"df_gdp = df_continent.pivot(index=\\\"year\\\", columns=\\\"continent\\\", values=\\\"gdpPercap\\\")\\n\",\n", + " \"\\n\",\n", + " \"gdp = xr.DataArray(\\n\",\n", + " \" df_gdp.values,\\n\",\n", + " \" dims=[\\\"year\\\", \\\"continent\\\"],\\n\",\n", + " \" coords={\\\"year\\\": df_gdp.index, \\\"continent\\\": df_gdp.columns.tolist()},\\n\",\n", + " \" name=\\\"gdp_per_capita\\\",\\n\",\n", + " \" attrs={\\\"units\\\": \\\"USD\\\"},\\n\",\n", + " \")\\n\",\n", + " \"print(f\\\"gdp: {dict(gdp.sizes)}\\\")\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Line Plot\\n\",\n", + " \"\\n\",\n", + " \"Best for time series and continuous data:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"fig = xpx(stocks).line(title=\\\"Stock Prices Over Time\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"fig = xpx(life_exp).line(title=\\\"Life Expectancy by Country\\\", markers=True)\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Bar Chart\\n\",\n", + " \"\\n\",\n", + " \"Best for comparing categorical data:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# GDP by continent for selected years\\n\",\n", + " \"fig = xpx(gdp.sel(year=[1952, 1977, 2007])).bar(title=\\\"GDP per Capita by Continent\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Grouped bars\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"fig = xpx(gdp.sel(year=[1952, 1977, 2007])).bar(barmode=\\\"group\\\", title=\\\"GDP per Capita (Grouped)\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Area Chart\\n\",\n", + " \"\\n\",\n", + " \"Best for showing composition over time:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Population by continent over time\\n\",\n", + " \"df_pop = df_gap.groupby([\\\"continent\\\", \\\"year\\\"])[\\\"pop\\\"].sum().reset_index()\\n\",\n", + " \"df_pop_pivot = df_pop.pivot(index=\\\"year\\\", columns=\\\"continent\\\", values=\\\"pop\\\")\\n\",\n", + " \"\\n\",\n", + " \"population = xr.DataArray(\\n\",\n", + " \" df_pop_pivot.values / 1e9, # Convert to billions\\n\",\n", + " \" dims=[\\\"year\\\", \\\"continent\\\"],\\n\",\n", + " \" coords={\\\"year\\\": df_pop_pivot.index, \\\"continent\\\": df_pop_pivot.columns.tolist()},\\n\",\n", + " \" name=\\\"population\\\",\\n\",\n", + " \" attrs={\\\"units\\\": \\\"billions\\\"},\\n\",\n", + " \")\\n\",\n", + " \"\\n\",\n", + " \"fig = xpx(population).area(title=\\\"World Population by Continent\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Scatter Plot\\n\",\n", + " \"\\n\",\n", + " \"Best for showing relationships between variables:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"fig = xpx(stocks).scatter(title=\\\"Stock Prices Scatter\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Box Plot\\n\",\n", + " \"\\n\",\n", + " \"Best for showing distributions:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Stock price distributions by company\\n\",\n", + " \"fig = xpx(stocks).box(title=\\\"Stock Price Distributions\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Life expectancy distributions by country\\n\",\n", + " \"fig = xpx(life_exp).box(title=\\\"Life Expectancy Distribution by Country\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Heatmap (imshow)\\n\",\n", + " \"\\n\",\n", + " \"Best for 2D grid data:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"fig = xpx(life_exp).imshow(title=\\\"Life Expectancy Heatmap\\\")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### With different colorscale\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"fig = xpx(gdp).imshow(\\n\",\n", + " \" color_continuous_scale=\\\"Viridis\\\",\\n\",\n", + " \" title=\\\"GDP per Capita by Continent and Year\\\",\\n\",\n", + " \")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Faceting\\n\",\n", + " \"\\n\",\n", + " \"All plot types support faceting to create subplot grids:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Create 3D data: life expectancy by year, country, and metric\\n\",\n", + " \"# We'll add GDP as another \\\"metric\\\" dimension\\n\",\n", + " \"df_metrics = df_gap[df_gap[\\\"country\\\"].isin(countries)].pivot(\\n\",\n", + " \" index=\\\"year\\\", columns=\\\"country\\\", values=\\\"gdpPercap\\\"\\n\",\n", + " \")\\n\",\n", + " \"gdp_countries = xr.DataArray(\\n\",\n", + " \" df_metrics.values,\\n\",\n", + " \" dims=[\\\"year\\\", \\\"country\\\"],\\n\",\n", + " \" coords={\\\"year\\\": df_metrics.index, \\\"country\\\": df_metrics.columns.tolist()},\\n\",\n", + " \" name=\\\"gdp_per_capita\\\",\\n\",\n", + " \")\\n\",\n", + " \"\\n\",\n", + " \"# Combine into 3D array\\n\",\n", + " \"combined = xr.concat(\\n\",\n", + " \" [life_exp, gdp_countries / 1000], # Scale GDP to thousands\\n\",\n", + " \" dim=xr.Variable(\\\"metric\\\", [\\\"Life Exp (years)\\\", \\\"GDP (thousands USD)\\\"]),\\n\",\n", + " \")\\n\",\n", + " \"\\n\",\n", + " \"fig = xpx(combined).line(\\n\",\n", + " \" facet_col=\\\"metric\\\",\\n\",\n", + " \" title=\\\"Country Comparison: Life Expectancy and GDP\\\",\\n\",\n", + " \")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## Animation\\n\",\n", + " \"\\n\",\n", + " \"Create animated plots by assigning a dimension to `animation_frame`:\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Animated bar chart of GDP by continent over time\\n\",\n", + " \"fig = xpx(gdp).bar(\\n\",\n", + " \" x=\\\"continent\\\",\\n\",\n", + " \" animation_frame=\\\"year\\\",\\n\",\n", + " \" title=\\\"GDP per Capita by Continent (Animated)\\\",\\n\",\n", + " \" range_y=[0, 35000],\\n\",\n", + " \")\\n\",\n", + " \"fig\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"code\",\n", + " \"execution_count\": null,\n", + " \"metadata\": {},\n", + " \"outputs\": [],\n", + " \"source\": [\n", + " \"# Animated line showing life expectancy evolution\\n\",\n", + " \"fig = xpx(life_exp).bar(\\n\",\n", + " \" x=\\\"country\\\",\\n\",\n", + " \" animation_frame=\\\"year\\\",\\n\",\n", + " \" title=\\\"Life Expectancy by Country (Animated)\\\",\\n\",\n", + " \" range_y=[0, 85],\\n\",\n", + " \")\\n\",\n", + " \"fig\"\n", + " ]\n", + " }\n", + " ],\n", + " \"metadata\": {\n", + " \"kernelspec\": {\n", + " \"display_name\": \"Python 3\",\n", + " \"language\": \"python\",\n", + " \"name\": \"python3\"\n", + " },\n", + " \"language_info\": {\n", + " \"name\": \"python\",\n", + " \"version\": \"3.12.0\"\n", + " }\n", + " },\n", + " \"nbformat\": 4,\n", + " \"nbformat_minor\": 4\n", + "}\n" ] } ], From 5fe70982b27d7a621c745908886ad51f384516fa Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:12:56 +0100 Subject: [PATCH 5/8] Update last notebook and remove extra deps --- docs/examples/advanced.ipynb | 347 ++++++++++++++++++++--------------- pyproject.toml | 2 - 2 files changed, 195 insertions(+), 154 deletions(-) diff --git a/docs/examples/advanced.ipynb b/docs/examples/advanced.ipynb index d935b56..9bb825c 100644 --- a/docs/examples/advanced.ipynb +++ b/docs/examples/advanced.ipynb @@ -6,7 +6,7 @@ "source": [ "# Advanced Usage\n", "\n", - "This notebook covers advanced patterns and customization options using real climate datasets." + "This notebook covers advanced Plotly customization and styling patterns." ] }, { @@ -15,6 +15,7 @@ "metadata": {}, "outputs": [], "source": [ + "import plotly.express as px\n", "import xarray as xr\n", "\n", "from xarray_plotly import config, xpx\n", @@ -26,7 +27,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Load Tutorial Datasets" + "## Load Sample Data" ] }, { @@ -35,16 +36,32 @@ "metadata": {}, "outputs": [], "source": [ - "# NCEP air temperature: 3D (time, lat, lon)\n", - "ds_air = xr.tutorial.open_dataset(\"air_temperature\")\n", - "air = ds_air[\"air\"]\n", + "# Stock prices\n", + "df_stocks = px.data.stocks().set_index(\"date\")\n", + "df_stocks.index = df_stocks.index.astype(\"datetime64[ns]\")\n", + "\n", + "stocks = xr.DataArray(\n", + " df_stocks.values,\n", + " dims=[\"date\", \"company\"],\n", + " coords={\"date\": df_stocks.index, \"company\": df_stocks.columns.tolist()},\n", + " name=\"price\",\n", + " attrs={\"long_name\": \"Stock Price\", \"units\": \"normalized\"},\n", + ")\n", "\n", - "# ERA-Interim: 4D (month, level, latitude, longitude)\n", - "ds_era = xr.tutorial.open_dataset(\"eraint_uvz\")\n", + "# Gapminder data\n", + "df_gap = px.data.gapminder()\n", + "countries = [\"United States\", \"China\", \"Germany\", \"Brazil\", \"Nigeria\"]\n", "\n", - "print(f\"Air temperature: {air.attrs.get('long_name', air.name)}\")\n", - "print(f\" Dimensions: {dict(air.sizes)}\")\n", - "print(f\" Units: {air.attrs.get('units', 'N/A')}\")" + "df_life = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n", + " index=\"year\", columns=\"country\", values=\"lifeExp\"\n", + ")\n", + "life_exp = xr.DataArray(\n", + " df_life.values,\n", + " dims=[\"year\", \"country\"],\n", + " coords={\"year\": df_life.index, \"country\": df_life.columns.tolist()},\n", + " name=\"life_expectancy\",\n", + " attrs={\"long_name\": \"Life Expectancy\", \"units\": \"years\"},\n", + ")" ] }, { @@ -62,9 +79,9 @@ "metadata": {}, "outputs": [], "source": [ - "# The air temperature DataArray already has rich metadata\n", - "print(f\"Variable attrs: {air.attrs}\")\n", - "print(f\"Time coord attrs: {air.coords['time'].attrs}\")" + "# Check the attributes we set\n", + "print(f\"Name: {stocks.name}\")\n", + "print(f\"Attrs: {stocks.attrs}\")" ] }, { @@ -74,9 +91,7 @@ "outputs": [], "source": [ "# Labels are automatically extracted from attrs\n", - "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", - " title=\"Temperature with Auto-Labels from Metadata\"\n", - ")\n", + "fig = xpx(stocks).line(title=\"Auto-Labels from Metadata\")\n", "fig" ] }, @@ -86,7 +101,7 @@ "source": [ "### Configuring Label Behavior\n", "\n", - "Use `config.set_options()` to control how labels are extracted from attributes:" + "Use `config.set_options()` to control how labels are extracted:" ] }, { @@ -97,9 +112,7 @@ "source": [ "# Disable units in labels\n", "with config.set_options(label_include_units=False):\n", - " fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", - " title=\"Without Units in Labels\"\n", - " )\n", + " fig = xpx(stocks).line(title=\"Without Units in Labels\")\n", "fig" ] }, @@ -118,11 +131,11 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(air.sel(lat=[40, 50, 60], lon=260, method=\"nearest\")).line(\n", + "fig = xpx(stocks).line(\n", " labels={\n", - " \"air\": \"Temperature (K)\",\n", - " \"time\": \"Date\",\n", - " \"lat\": \"Latitude\",\n", + " \"price\": \"Normalized Price\",\n", + " \"date\": \"Trading Date\",\n", + " \"company\": \"Ticker\",\n", " },\n", " title=\"Custom Labels\",\n", ")\n", @@ -135,9 +148,9 @@ "source": [ "## Advanced Dimension Assignment\n", "\n", - "### Complex Slot Assignments with 4D Data\n", + "### Using Multiple Visual Encodings\n", "\n", - "The ERA-Interim dataset has 4 dimensions - perfect for demonstrating complex assignments:" + "Combine color, line_dash, and facets to show multiple dimensions:" ] }, { @@ -146,10 +159,23 @@ "metadata": {}, "outputs": [], "source": [ - "# U-wind: (month, level, latitude, longitude)\n", - "u = ds_era[\"u\"]\n", - "print(f\"U-wind dimensions: {dict(u.sizes)}\")\n", - "print(f\"Pressure levels: {u.coords['level'].values} hPa\")" + "# Create 3D data by adding a \"metric\" dimension\n", + "df_gdp = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n", + " index=\"year\", columns=\"country\", values=\"gdpPercap\"\n", + ")\n", + "gdp = xr.DataArray(\n", + " df_gdp.values / 1000,\n", + " dims=[\"year\", \"country\"],\n", + " coords={\"year\": df_gdp.index, \"country\": df_gdp.columns.tolist()},\n", + " name=\"gdp\",\n", + ")\n", + "\n", + "# Combine into 3D: (metric, year, country)\n", + "combined = xr.concat(\n", + " [life_exp, gdp],\n", + " dim=xr.Variable(\"metric\", [\"Life Expectancy (years)\", \"GDP per Capita (thousands)\"]),\n", + ")\n", + "print(f\"Combined shape: {dict(combined.sizes)}\")" ] }, { @@ -158,14 +184,25 @@ "metadata": {}, "outputs": [], "source": [ - "# Zonal mean profile: use line_dash for pressure level, color for month\n", - "u_zonal = u.mean(\"longitude\")\n", - "\n", - "fig = xpx(u_zonal).line(\n", - " x=\"latitude\",\n", - " color=\"month\",\n", - " line_dash=\"level\",\n", - " title=\"Zonal Mean U-Wind: Multiple Visual Encodings\",\n", + "# Use facet_col for metric, color for country\n", + "fig = xpx(combined).line(\n", + " facet_col=\"metric\",\n", + " title=\"Multiple Metrics Comparison\",\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use line_dash for a dimension\n", + "fig = xpx(stocks.sel(company=[\"GOOG\", \"AAPL\", \"MSFT\"])).line(\n", + " color=None,\n", + " line_dash=\"company\",\n", + " title=\"Using Line Dash Instead of Color\",\n", ")\n", "fig" ] @@ -174,9 +211,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Reducing Dimensions Before Plotting\n", - "\n", - "When you have more dimensions than slots, reduce them first:" + "## Custom Styling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Themes" ] }, { @@ -185,11 +227,9 @@ "metadata": {}, "outputs": [], "source": [ - "# Average over months to compare pressure levels\n", - "fig = xpx(u.mean([\"month\", \"longitude\"])).line(\n", - " x=\"latitude\",\n", - " color=\"level\",\n", - " title=\"Mean Zonal Wind Profile by Pressure Level\",\n", + "fig = xpx(stocks).line(\n", + " template=\"plotly_dark\",\n", + " title=\"Dark Theme\",\n", ")\n", "fig" ] @@ -200,12 +240,9 @@ "metadata": {}, "outputs": [], "source": [ - "# Select specific slices for focused analysis\n", - "fig = xpx(u.sel(level=200)).imshow(\n", - " facet_col=\"month\",\n", - " color_continuous_scale=\"RdBu_r\",\n", - " color_continuous_midpoint=0,\n", - " title=\"200 hPa U-Wind (Jet Stream Level)\",\n", + "fig = xpx(stocks).line(\n", + " template=\"seaborn\",\n", + " title=\"Seaborn Theme\",\n", ")\n", "fig" ] @@ -214,14 +251,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Custom Styling" + "### Custom Colors" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "### Themes" + "fig = xpx(stocks).line(\n", + " color_discrete_sequence=px.colors.qualitative.Set2,\n", + " title=\"Set2 Color Palette\",\n", + ")\n", + "fig" ] }, { @@ -230,9 +273,10 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(air.sel(lat=45, lon=260, method=\"nearest\")).line(\n", - " template=\"plotly_dark\",\n", - " title=\"Dark Theme\",\n", + "# Custom color list\n", + "fig = xpx(life_exp).line(\n", + " color_discrete_sequence=[\"#E63946\", \"#457B9D\", \"#2A9D8F\", \"#E9C46A\", \"#F4A261\"],\n", + " title=\"Custom Color Sequence\",\n", ")\n", "fig" ] @@ -241,7 +285,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Custom Colors" + "### Heatmap Colorscales" ] }, { @@ -250,42 +294,28 @@ "metadata": {}, "outputs": [], "source": [ - "import plotly.express as px\n", - "\n", - "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\")).line(\n", - " color_discrete_sequence=px.colors.qualitative.Set2,\n", - " title=\"Custom Color Palette\",\n", + "fig = xpx(life_exp).imshow(\n", + " color_continuous_scale=\"Viridis\",\n", + " title=\"Viridis Colorscale\",\n", ")\n", "fig" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Heatmap Colorscales\n", - "\n", - "Climate data often benefits from diverging colorscales centered at meaningful values:" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Temperature anomaly from mean\n", - "air_mean = air.mean(\"time\")\n", - "air_anomaly = air.isel(time=0) - air_mean\n", - "air_anomaly.name = \"temperature_anomaly\"\n", - "air_anomaly.attrs[\"long_name\"] = \"Temperature Anomaly\"\n", - "air_anomaly.attrs[\"units\"] = \"K\"\n", + "# Diverging colorscale with midpoint\n", + "# Calculate change from first year\n", + "life_change = life_exp - life_exp.isel(year=0)\n", + "life_change.name = \"change\"\n", "\n", - "# Diverging colorscale centered at zero\n", - "fig = xpx(air_anomaly).imshow(\n", - " color_continuous_scale=\"RdBu_r\",\n", + "fig = xpx(life_change).imshow(\n", + " color_continuous_scale=\"RdBu\",\n", " color_continuous_midpoint=0,\n", - " title=\"Temperature Anomaly (Diverging Colorscale)\",\n", + " title=\"Life Expectancy Change (Diverging Colorscale)\",\n", ")\n", "fig" ] @@ -296,7 +326,7 @@ "source": [ "## Post-Creation Customization\n", "\n", - "All plots return Plotly `Figure` objects that can be extensively customized:" + "All plots return Plotly `Figure` objects that you can customize further:" ] }, { @@ -305,14 +335,14 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\")).line()\n", + "fig = xpx(stocks).line()\n", "\n", - "# Add horizontal reference line (freezing point in Kelvin)\n", - "fig.add_hline(y=273.15, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Freezing Point\")\n", + "# Add horizontal reference line\n", + "fig.add_hline(y=1.0, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Baseline\")\n", "\n", "# Update layout\n", "fig.update_layout(\n", - " title=\"Temperature with Freezing Point Reference\",\n", + " title=\"Stock Prices with Reference Line\",\n", " legend={\n", " \"orientation\": \"h\",\n", " \"yanchor\": \"bottom\",\n", @@ -325,25 +355,21 @@ "fig" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Modifying Traces" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "fig = xpx(air.sel(lat=[30, 45, 60], lon=260, method=\"nearest\").isel(time=slice(0, 100))).line()\n", + "# Add vertical line for an event\n", + "fig = xpx(stocks).line(title=\"Stock Prices with Event Marker\")\n", "\n", - "# Make all lines thicker\n", - "fig.update_traces(line_width=3)\n", - "\n", - "fig.update_layout(title=\"Thicker Lines\")\n", + "fig.add_vline(\n", + " x=\"2018-07-01\",\n", + " line_dash=\"dot\",\n", + " line_color=\"red\",\n", + " annotation_text=\"Mid-2018\",\n", + ")\n", "fig" ] }, @@ -351,44 +377,44 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Exporting Figures" + "### Modifying Traces" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "### Interactive HTML\n", - "\n", - "```python\n", - "fig.write_html(\"interactive_plot.html\")\n", - "```\n", + "fig = xpx(stocks).line()\n", "\n", - "### Static Images\n", - "\n", - "Requires `kaleido`: `pip install kaleido`\n", + "# Make all lines thicker\n", + "fig.update_traces(line_width=3)\n", "\n", - "```python\n", - "fig.write_image(\"plot.png\", scale=2) # High resolution\n", - "fig.write_image(\"plot.svg\") # Vector format\n", - "fig.write_image(\"plot.pdf\") # PDF\n", - "```" + "fig.update_layout(title=\"Thicker Lines\")\n", + "fig" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "## Integration with xarray Operations\n", + "fig = xpx(stocks).scatter()\n", + "\n", + "# Change marker style\n", + "fig.update_traces(marker={\"size\": 10, \"opacity\": 0.7})\n", "\n", - "xarray_plotly works seamlessly with xarray's computation methods:" + "fig.update_layout(title=\"Custom Marker Style\")\n", + "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Rolling Mean" + "### Adding Annotations" ] }, { @@ -397,11 +423,15 @@ "metadata": {}, "outputs": [], "source": [ - "# 30-day rolling mean to smooth daily variations\n", - "air_smooth = air.sel(lat=45, lon=260, method=\"nearest\").rolling(time=30, center=True).mean()\n", + "fig = xpx(life_exp).line(title=\"Life Expectancy with Annotations\")\n", "\n", - "fig = xpx(air_smooth).line(\n", - " title=\"30-Day Rolling Mean Temperature\",\n", + "# Add annotation for a specific point\n", + "fig.add_annotation(\n", + " x=2007,\n", + " y=life_exp.sel(year=2007, country=\"China\").values,\n", + " text=\"China 2007\",\n", + " showarrow=True,\n", + " arrowhead=2,\n", ")\n", "fig" ] @@ -410,34 +440,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Groupby Operations (Climatology)" + "## Exporting Figures" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# Monthly climatology\n", - "monthly_clim = air.sel(lon=260, method=\"nearest\").groupby(\"time.month\").mean()\n", + "### Interactive HTML\n", "\n", - "fig = xpx(monthly_clim.sel(lat=[30, 45, 60], method=\"nearest\")).line(\n", - " title=\"Monthly Temperature Climatology\",\n", - ")\n", - "fig.update_xaxes(\n", - " tickmode=\"array\",\n", - " tickvals=list(range(1, 13)),\n", - " ticktext=[\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"],\n", - ")\n", - "fig" + "```python\n", + "fig.write_html(\"interactive_plot.html\")\n", + "```\n", + "\n", + "### Static Images\n", + "\n", + "Requires `kaleido`: `pip install kaleido`\n", + "\n", + "```python\n", + "fig.write_image(\"plot.png\", scale=2) # High resolution\n", + "fig.write_image(\"plot.svg\") # Vector format\n", + "fig.write_image(\"plot.pdf\") # PDF\n", + "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Resampling" + "## Subplots with Shared Axes" ] }, { @@ -446,13 +477,14 @@ "metadata": {}, "outputs": [], "source": [ - "# Weekly mean temperature\n", - "weekly = air.sel(lat=45, lon=260, method=\"nearest\").resample(time=\"W\").mean()\n", - "\n", - "fig = xpx(weekly).line(\n", - " title=\"Weekly Mean Temperature\",\n", - " markers=True,\n", + "# Faceted plot with shared y-axis\n", + "fig = xpx(combined).line(\n", + " facet_col=\"metric\",\n", + " title=\"Facets with Independent Y-Axes\",\n", ")\n", + "\n", + "# Each facet gets its own y-axis range by default\n", + "fig.update_yaxes(matches=None)\n", "fig" ] }, @@ -460,7 +492,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Spatial Statistics" + "## Combining with Plotly Graph Objects\n", + "\n", + "You can add additional traces using Plotly's graph objects:" ] }, { @@ -469,13 +503,22 @@ "metadata": {}, "outputs": [], "source": [ - "# Zonal mean temperature over time\n", - "zonal_mean = air.mean(\"lon\")\n", + "import plotly.graph_objects as go\n", "\n", - "# Show as heatmap (Hovmoller diagram)\n", - "fig = xpx(zonal_mean).imshow(\n", - " title=\"Zonal Mean Temperature (Hovmoller Diagram)\",\n", - " color_continuous_scale=\"thermal\",\n", + "fig = xpx(stocks.sel(company=\"GOOG\")).line(title=\"GOOG with Moving Average\")\n", + "\n", + "# Calculate and add a moving average\n", + "goog = stocks.sel(company=\"GOOG\")\n", + "ma_20 = goog.rolling(date=20, center=True).mean()\n", + "\n", + "fig.add_trace(\n", + " go.Scatter(\n", + " x=ma_20.coords[\"date\"].values,\n", + " y=ma_20.values,\n", + " mode=\"lines\",\n", + " name=\"20-day MA\",\n", + " line={\"dash\": \"dash\", \"color\": \"red\"},\n", + " )\n", ")\n", "fig" ] diff --git a/pyproject.toml b/pyproject.toml index 37c0f94..f335446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,8 +45,6 @@ docs = [ "mkdocstrings[python]==0.27.0", "mkdocs-jupyter==0.25.1", "mkdocs-plotly-plugin==0.1.5", - "pooch==1.8.2", - "netcdf4==1.7.2", "jupyter==1.1.1", ] From 1cb4c9dd2c34f0b4a8ea3bd62673c809edf3ea4a Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:16:37 +0100 Subject: [PATCH 6/8] Fix notebook json --- docs/examples/plot-types.ipynb | 743 ++++++++++++++++----------------- 1 file changed, 360 insertions(+), 383 deletions(-) diff --git a/docs/examples/plot-types.ipynb b/docs/examples/plot-types.ipynb index cd56d93..1f068a7 100644 --- a/docs/examples/plot-types.ipynb +++ b/docs/examples/plot-types.ipynb @@ -1,391 +1,368 @@ { "cells": [ { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ - "{\n", - " \"cells\": [\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"# Plot Types\\n\",\n", - " \"\\n\",\n", - " \"This notebook demonstrates all the plot types available in xarray_plotly.\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"import plotly.express as px\\n\",\n", - " \"import xarray as xr\\n\",\n", - " \"\\n\",\n", - " \"from xarray_plotly import config, xpx\\n\",\n", - " \"\\n\",\n", - " \"config.notebook() # Configure Plotly for notebook rendering\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Load Sample Data\\n\",\n", - " \"\\n\",\n", - " \"We'll use plotly's built-in datasets converted to xarray:\\n\",\n", - " \"- **stocks**: Tech company stock prices over time\\n\",\n", - " \"- **gapminder**: Country statistics (life expectancy, GDP, population) by year\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Stock prices: 2D (date, company)\\n\",\n", - " \"df_stocks = px.data.stocks().set_index(\\\"date\\\")\\n\",\n", - " \"df_stocks.index = df_stocks.index.astype(\\\"datetime64[ns]\\\")\\n\",\n", - " \"\\n\",\n", - " \"stocks = xr.DataArray(\\n\",\n", - " \" df_stocks.values,\\n\",\n", - " \" dims=[\\\"date\\\", \\\"company\\\"],\\n\",\n", - " \" coords={\\\"date\\\": df_stocks.index, \\\"company\\\": df_stocks.columns.tolist()},\\n\",\n", - " \" name=\\\"price\\\",\\n\",\n", - " \")\\n\",\n", - " \"print(f\\\"stocks: {dict(stocks.sizes)}\\\")\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Gapminder: pivot to create multi-dimensional arrays\\n\",\n", - " \"df_gap = px.data.gapminder()\\n\",\n", - " \"\\n\",\n", - " \"# Life expectancy: 2D (year, country) - select a few countries\\n\",\n", - " \"countries = [\\\"United States\\\", \\\"China\\\", \\\"Germany\\\", \\\"Brazil\\\", \\\"Nigeria\\\"]\\n\",\n", - " \"df_life = df_gap[df_gap[\\\"country\\\"].isin(countries)].pivot(\\n\",\n", - " \" index=\\\"year\\\", columns=\\\"country\\\", values=\\\"lifeExp\\\"\\n\",\n", - " \")\\n\",\n", - " \"\\n\",\n", - " \"life_exp = xr.DataArray(\\n\",\n", - " \" df_life.values,\\n\",\n", - " \" dims=[\\\"year\\\", \\\"country\\\"],\\n\",\n", - " \" coords={\\\"year\\\": df_life.index, \\\"country\\\": df_life.columns.tolist()},\\n\",\n", - " \" name=\\\"life_expectancy\\\",\\n\",\n", - " \" attrs={\\\"units\\\": \\\"years\\\"},\\n\",\n", - " \")\\n\",\n", - " \"print(f\\\"life_exp: {dict(life_exp.sizes)}\\\")\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# GDP per capita by continent and year (aggregated)\\n\",\n", - " \"df_continent = df_gap.groupby([\\\"continent\\\", \\\"year\\\"])[\\\"gdpPercap\\\"].mean().reset_index()\\n\",\n", - " \"df_gdp = df_continent.pivot(index=\\\"year\\\", columns=\\\"continent\\\", values=\\\"gdpPercap\\\")\\n\",\n", - " \"\\n\",\n", - " \"gdp = xr.DataArray(\\n\",\n", - " \" df_gdp.values,\\n\",\n", - " \" dims=[\\\"year\\\", \\\"continent\\\"],\\n\",\n", - " \" coords={\\\"year\\\": df_gdp.index, \\\"continent\\\": df_gdp.columns.tolist()},\\n\",\n", - " \" name=\\\"gdp_per_capita\\\",\\n\",\n", - " \" attrs={\\\"units\\\": \\\"USD\\\"},\\n\",\n", - " \")\\n\",\n", - " \"print(f\\\"gdp: {dict(gdp.sizes)}\\\")\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Line Plot\\n\",\n", - " \"\\n\",\n", - " \"Best for time series and continuous data:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"fig = xpx(stocks).line(title=\\\"Stock Prices Over Time\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"fig = xpx(life_exp).line(title=\\\"Life Expectancy by Country\\\", markers=True)\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Bar Chart\\n\",\n", - " \"\\n\",\n", - " \"Best for comparing categorical data:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# GDP by continent for selected years\\n\",\n", - " \"fig = xpx(gdp.sel(year=[1952, 1977, 2007])).bar(title=\\\"GDP per Capita by Continent\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"### Grouped bars\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"fig = xpx(gdp.sel(year=[1952, 1977, 2007])).bar(barmode=\\\"group\\\", title=\\\"GDP per Capita (Grouped)\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Area Chart\\n\",\n", - " \"\\n\",\n", - " \"Best for showing composition over time:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Population by continent over time\\n\",\n", - " \"df_pop = df_gap.groupby([\\\"continent\\\", \\\"year\\\"])[\\\"pop\\\"].sum().reset_index()\\n\",\n", - " \"df_pop_pivot = df_pop.pivot(index=\\\"year\\\", columns=\\\"continent\\\", values=\\\"pop\\\")\\n\",\n", - " \"\\n\",\n", - " \"population = xr.DataArray(\\n\",\n", - " \" df_pop_pivot.values / 1e9, # Convert to billions\\n\",\n", - " \" dims=[\\\"year\\\", \\\"continent\\\"],\\n\",\n", - " \" coords={\\\"year\\\": df_pop_pivot.index, \\\"continent\\\": df_pop_pivot.columns.tolist()},\\n\",\n", - " \" name=\\\"population\\\",\\n\",\n", - " \" attrs={\\\"units\\\": \\\"billions\\\"},\\n\",\n", - " \")\\n\",\n", - " \"\\n\",\n", - " \"fig = xpx(population).area(title=\\\"World Population by Continent\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Scatter Plot\\n\",\n", - " \"\\n\",\n", - " \"Best for showing relationships between variables:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"fig = xpx(stocks).scatter(title=\\\"Stock Prices Scatter\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Box Plot\\n\",\n", - " \"\\n\",\n", - " \"Best for showing distributions:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Stock price distributions by company\\n\",\n", - " \"fig = xpx(stocks).box(title=\\\"Stock Price Distributions\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Life expectancy distributions by country\\n\",\n", - " \"fig = xpx(life_exp).box(title=\\\"Life Expectancy Distribution by Country\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Heatmap (imshow)\\n\",\n", - " \"\\n\",\n", - " \"Best for 2D grid data:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"fig = xpx(life_exp).imshow(title=\\\"Life Expectancy Heatmap\\\")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"### With different colorscale\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"fig = xpx(gdp).imshow(\\n\",\n", - " \" color_continuous_scale=\\\"Viridis\\\",\\n\",\n", - " \" title=\\\"GDP per Capita by Continent and Year\\\",\\n\",\n", - " \")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Faceting\\n\",\n", - " \"\\n\",\n", - " \"All plot types support faceting to create subplot grids:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Create 3D data: life expectancy by year, country, and metric\\n\",\n", - " \"# We'll add GDP as another \\\"metric\\\" dimension\\n\",\n", - " \"df_metrics = df_gap[df_gap[\\\"country\\\"].isin(countries)].pivot(\\n\",\n", - " \" index=\\\"year\\\", columns=\\\"country\\\", values=\\\"gdpPercap\\\"\\n\",\n", - " \")\\n\",\n", - " \"gdp_countries = xr.DataArray(\\n\",\n", - " \" df_metrics.values,\\n\",\n", - " \" dims=[\\\"year\\\", \\\"country\\\"],\\n\",\n", - " \" coords={\\\"year\\\": df_metrics.index, \\\"country\\\": df_metrics.columns.tolist()},\\n\",\n", - " \" name=\\\"gdp_per_capita\\\",\\n\",\n", - " \")\\n\",\n", - " \"\\n\",\n", - " \"# Combine into 3D array\\n\",\n", - " \"combined = xr.concat(\\n\",\n", - " \" [life_exp, gdp_countries / 1000], # Scale GDP to thousands\\n\",\n", - " \" dim=xr.Variable(\\\"metric\\\", [\\\"Life Exp (years)\\\", \\\"GDP (thousands USD)\\\"]),\\n\",\n", - " \")\\n\",\n", - " \"\\n\",\n", - " \"fig = xpx(combined).line(\\n\",\n", - " \" facet_col=\\\"metric\\\",\\n\",\n", - " \" title=\\\"Country Comparison: Life Expectancy and GDP\\\",\\n\",\n", - " \")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"markdown\",\n", - " \"metadata\": {},\n", - " \"source\": [\n", - " \"## Animation\\n\",\n", - " \"\\n\",\n", - " \"Create animated plots by assigning a dimension to `animation_frame`:\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Animated bar chart of GDP by continent over time\\n\",\n", - " \"fig = xpx(gdp).bar(\\n\",\n", - " \" x=\\\"continent\\\",\\n\",\n", - " \" animation_frame=\\\"year\\\",\\n\",\n", - " \" title=\\\"GDP per Capita by Continent (Animated)\\\",\\n\",\n", - " \" range_y=[0, 35000],\\n\",\n", - " \")\\n\",\n", - " \"fig\"\n", - " ]\n", - " },\n", - " {\n", - " \"cell_type\": \"code\",\n", - " \"execution_count\": null,\n", - " \"metadata\": {},\n", - " \"outputs\": [],\n", - " \"source\": [\n", - " \"# Animated line showing life expectancy evolution\\n\",\n", - " \"fig = xpx(life_exp).bar(\\n\",\n", - " \" x=\\\"country\\\",\\n\",\n", - " \" animation_frame=\\\"year\\\",\\n\",\n", - " \" title=\\\"Life Expectancy by Country (Animated)\\\",\\n\",\n", - " \" range_y=[0, 85],\\n\",\n", - " \")\\n\",\n", - " \"fig\"\n", - " ]\n", - " }\n", - " ],\n", - " \"metadata\": {\n", - " \"kernelspec\": {\n", - " \"display_name\": \"Python 3\",\n", - " \"language\": \"python\",\n", - " \"name\": \"python3\"\n", - " },\n", - " \"language_info\": {\n", - " \"name\": \"python\",\n", - " \"version\": \"3.12.0\"\n", - " }\n", - " },\n", - " \"nbformat\": 4,\n", - " \"nbformat_minor\": 4\n", - "}\n" + "# Plot Types\n", + "\n", + "This notebook demonstrates all the plot types available in xarray_plotly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.express as px\n", + "import xarray as xr\n", + "\n", + "from xarray_plotly import config, xpx\n", + "\n", + "config.notebook() # Configure Plotly for notebook rendering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Sample Data\n", + "\n", + "We'll use plotly's built-in datasets converted to xarray:\n", + "- **stocks**: Tech company stock prices over time\n", + "- **gapminder**: Country statistics (life expectancy, GDP, population) by year" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Stock prices: 2D (date, company)\n", + "df_stocks = px.data.stocks().set_index(\"date\")\n", + "df_stocks.index = df_stocks.index.astype(\"datetime64[ns]\")\n", + "\n", + "stocks = xr.DataArray(\n", + " df_stocks.values,\n", + " dims=[\"date\", \"company\"],\n", + " coords={\"date\": df_stocks.index, \"company\": df_stocks.columns.tolist()},\n", + " name=\"price\",\n", + ")\n", + "print(f\"stocks: {dict(stocks.sizes)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Gapminder: pivot to create multi-dimensional arrays\n", + "df_gap = px.data.gapminder()\n", + "\n", + "# Life expectancy: 2D (year, country) - select a few countries\n", + "countries = [\"United States\", \"China\", \"Germany\", \"Brazil\", \"Nigeria\"]\n", + "df_life = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n", + " index=\"year\", columns=\"country\", values=\"lifeExp\"\n", + ")\n", + "\n", + "life_exp = xr.DataArray(\n", + " df_life.values,\n", + " dims=[\"year\", \"country\"],\n", + " coords={\"year\": df_life.index, \"country\": df_life.columns.tolist()},\n", + " name=\"life_expectancy\",\n", + " attrs={\"units\": \"years\"},\n", + ")\n", + "print(f\"life_exp: {dict(life_exp.sizes)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# GDP per capita by continent and year (aggregated)\n", + "df_continent = df_gap.groupby([\"continent\", \"year\"])[\"gdpPercap\"].mean().reset_index()\n", + "df_gdp = df_continent.pivot(index=\"year\", columns=\"continent\", values=\"gdpPercap\")\n", + "\n", + "gdp = xr.DataArray(\n", + " df_gdp.values,\n", + " dims=[\"year\", \"continent\"],\n", + " coords={\"year\": df_gdp.index, \"continent\": df_gdp.columns.tolist()},\n", + " name=\"gdp_per_capita\",\n", + " attrs={\"units\": \"USD\"},\n", + ")\n", + "print(f\"gdp: {dict(gdp.sizes)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Line Plot\n", + "\n", + "Best for time series and continuous data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).line(title=\"Stock Prices Over Time\")\n", + "fig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(life_exp).line(title=\"Life Expectancy by Country\", markers=True)\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bar Chart\n", + "\n", + "Best for comparing categorical data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# GDP by continent for selected years\n", + "fig = xpx(gdp.sel(year=[1952, 1977, 2007])).bar(title=\"GDP per Capita by Continent\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Grouped bars" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(gdp.sel(year=[1952, 1977, 2007])).bar(barmode=\"group\", title=\"GDP per Capita (Grouped)\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Area Chart\n", + "\n", + "Best for showing composition over time:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Population by continent over time\n", + "df_pop = df_gap.groupby([\"continent\", \"year\"])[\"pop\"].sum().reset_index()\n", + "df_pop_pivot = df_pop.pivot(index=\"year\", columns=\"continent\", values=\"pop\")\n", + "\n", + "population = xr.DataArray(\n", + " df_pop_pivot.values / 1e9, # Convert to billions\n", + " dims=[\"year\", \"continent\"],\n", + " coords={\"year\": df_pop_pivot.index, \"continent\": df_pop_pivot.columns.tolist()},\n", + " name=\"population\",\n", + " attrs={\"units\": \"billions\"},\n", + ")\n", + "\n", + "fig = xpx(population).area(title=\"World Population by Continent\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Scatter Plot\n", + "\n", + "Best for showing relationships between variables:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).scatter(title=\"Stock Prices Scatter\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Box Plot\n", + "\n", + "Best for showing distributions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Stock price distributions by company\n", + "fig = xpx(stocks).box(title=\"Stock Price Distributions\")\n", + "fig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Life expectancy distributions by country\n", + "fig = xpx(life_exp).box(title=\"Life Expectancy Distribution by Country\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Heatmap (imshow)\n", + "\n", + "Best for 2D grid data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(life_exp).imshow(title=\"Life Expectancy Heatmap\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With different colorscale" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(gdp).imshow(\n", + " color_continuous_scale=\"Viridis\",\n", + " title=\"GDP per Capita by Continent and Year\",\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Faceting\n", + "\n", + "All plot types support faceting to create subplot grids:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create 3D data: life expectancy by year, country, and metric\n", + "# We'll add GDP as another \"metric\" dimension\n", + "df_metrics = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n", + " index=\"year\", columns=\"country\", values=\"gdpPercap\"\n", + ")\n", + "gdp_countries = xr.DataArray(\n", + " df_metrics.values,\n", + " dims=[\"year\", \"country\"],\n", + " coords={\"year\": df_metrics.index, \"country\": df_metrics.columns.tolist()},\n", + " name=\"gdp_per_capita\",\n", + ")\n", + "\n", + "# Combine into 3D array\n", + "combined = xr.concat(\n", + " [life_exp, gdp_countries / 1000], # Scale GDP to thousands\n", + " dim=xr.Variable(\"metric\", [\"Life Exp (years)\", \"GDP (thousands USD)\"]),\n", + ")\n", + "\n", + "fig = xpx(combined).line(\n", + " facet_col=\"metric\",\n", + " title=\"Country Comparison: Life Expectancy and GDP\",\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Animation\n", + "\n", + "Create animated plots by assigning a dimension to `animation_frame`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Animated bar chart of GDP by continent over time\n", + "fig = xpx(gdp).bar(\n", + " x=\"continent\",\n", + " animation_frame=\"year\",\n", + " title=\"GDP per Capita by Continent (Animated)\",\n", + " range_y=[0, 35000],\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Animated line showing life expectancy evolution\n", + "fig = xpx(life_exp).bar(\n", + " x=\"country\",\n", + " animation_frame=\"year\",\n", + " title=\"Life Expectancy by Country (Animated)\",\n", + " range_y=[0, 85],\n", + ")\n", + "fig" ] } ], From c512aa6df8e8efd6ef16651206e8766906bae9b9 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:17:56 +0100 Subject: [PATCH 7/8] Improve notebook --- docs/examples/advanced.ipynb | 410 ++++++++--------------------------- 1 file changed, 95 insertions(+), 315 deletions(-) diff --git a/docs/examples/advanced.ipynb b/docs/examples/advanced.ipynb index 9bb825c..71bda7c 100644 --- a/docs/examples/advanced.ipynb +++ b/docs/examples/advanced.ipynb @@ -6,7 +6,7 @@ "source": [ "# Advanced Usage\n", "\n", - "This notebook covers advanced Plotly customization and styling patterns." + "This notebook covers advanced Plotly customization. All kwargs are passed directly to [Plotly Express](https://plotly.com/python/plotly-express/), and figures can be modified using the full [Plotly Graph Objects API](https://plotly.com/python/graph-objects/)." ] }, { @@ -16,18 +16,12 @@ "outputs": [], "source": [ "import plotly.express as px\n", + "import plotly.graph_objects as go\n", "import xarray as xr\n", "\n", "from xarray_plotly import config, xpx\n", "\n", - "config.notebook() # Configure Plotly for notebook rendering" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load Sample Data" + "config.notebook()" ] }, { @@ -36,7 +30,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Stock prices\n", + "# Load sample data\n", "df_stocks = px.data.stocks().set_index(\"date\")\n", "df_stocks.index = df_stocks.index.astype(\"datetime64[ns]\")\n", "\n", @@ -45,13 +39,10 @@ " dims=[\"date\", \"company\"],\n", " coords={\"date\": df_stocks.index, \"company\": df_stocks.columns.tolist()},\n", " name=\"price\",\n", - " attrs={\"long_name\": \"Stock Price\", \"units\": \"normalized\"},\n", ")\n", "\n", - "# Gapminder data\n", "df_gap = px.data.gapminder()\n", "countries = [\"United States\", \"China\", \"Germany\", \"Brazil\", \"Nigeria\"]\n", - "\n", "df_life = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n", " index=\"year\", columns=\"country\", values=\"lifeExp\"\n", ")\n", @@ -59,8 +50,7 @@ " df_life.values,\n", " dims=[\"year\", \"country\"],\n", " coords={\"year\": df_life.index, \"country\": df_life.columns.tolist()},\n", - " name=\"life_expectancy\",\n", - " attrs={\"long_name\": \"Life Expectancy\", \"units\": \"years\"},\n", + " name=\"life_exp\",\n", ")" ] }, @@ -68,61 +58,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Working with xarray Attributes\n", - "\n", - "xarray_plotly automatically uses metadata from xarray attributes for labels:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check the attributes we set\n", - "print(f\"Name: {stocks.name}\")\n", - "print(f\"Attrs: {stocks.attrs}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Labels are automatically extracted from attrs\n", - "fig = xpx(stocks).line(title=\"Auto-Labels from Metadata\")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Configuring Label Behavior\n", + "## Passing Kwargs to Plotly Express\n", "\n", - "Use `config.set_options()` to control how labels are extracted:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Disable units in labels\n", - "with config.set_options(label_include_units=False):\n", - " fig = xpx(stocks).line(title=\"Without Units in Labels\")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Overriding Labels\n", - "\n", - "You can override the automatic labels:" + "All keyword arguments are passed directly to [Plotly Express functions](https://plotly.com/python-api-reference/plotly.express.html):" ] }, { @@ -131,138 +69,31 @@ "metadata": {}, "outputs": [], "source": [ + "# All px.line kwargs work: template, labels, colors, etc.\n", "fig = xpx(stocks).line(\n", - " labels={\n", - " \"price\": \"Normalized Price\",\n", - " \"date\": \"Trading Date\",\n", - " \"company\": \"Ticker\",\n", - " },\n", - " title=\"Custom Labels\",\n", + " title=\"Stock Performance\",\n", + " template=\"plotly_white\",\n", + " labels={\"price\": \"Normalized Price\", \"date\": \"Date\", \"company\": \"Ticker\"},\n", + " color_discrete_sequence=px.colors.qualitative.Set2,\n", ")\n", "fig" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Advanced Dimension Assignment\n", - "\n", - "### Using Multiple Visual Encodings\n", - "\n", - "Combine color, line_dash, and facets to show multiple dimensions:" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Create 3D data by adding a \"metric\" dimension\n", - "df_gdp = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n", - " index=\"year\", columns=\"country\", values=\"gdpPercap\"\n", - ")\n", - "gdp = xr.DataArray(\n", - " df_gdp.values / 1000,\n", - " dims=[\"year\", \"country\"],\n", - " coords={\"year\": df_gdp.index, \"country\": df_gdp.columns.tolist()},\n", - " name=\"gdp\",\n", - ")\n", + "# px.imshow kwargs: colorscale, midpoint, aspect\n", + "# See: https://plotly.com/python/imshow/\n", + "life_change = life_exp - life_exp.isel(year=0)\n", "\n", - "# Combine into 3D: (metric, year, country)\n", - "combined = xr.concat(\n", - " [life_exp, gdp],\n", - " dim=xr.Variable(\"metric\", [\"Life Expectancy (years)\", \"GDP per Capita (thousands)\"]),\n", - ")\n", - "print(f\"Combined shape: {dict(combined.sizes)}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Use facet_col for metric, color for country\n", - "fig = xpx(combined).line(\n", - " facet_col=\"metric\",\n", - " title=\"Multiple Metrics Comparison\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Use line_dash for a dimension\n", - "fig = xpx(stocks.sel(company=[\"GOOG\", \"AAPL\", \"MSFT\"])).line(\n", - " color=None,\n", - " line_dash=\"company\",\n", - " title=\"Using Line Dash Instead of Color\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Custom Styling" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Themes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks).line(\n", - " template=\"plotly_dark\",\n", - " title=\"Dark Theme\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks).line(\n", - " template=\"seaborn\",\n", - " title=\"Seaborn Theme\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Custom Colors" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks).line(\n", - " color_discrete_sequence=px.colors.qualitative.Set2,\n", - " title=\"Set2 Color Palette\",\n", + "fig = xpx(life_change).imshow(\n", + " color_continuous_scale=\"RdBu\",\n", + " color_continuous_midpoint=0,\n", + " aspect=\"auto\",\n", + " title=\"Life Expectancy Change Since 1952\",\n", ")\n", "fig" ] @@ -273,10 +104,12 @@ "metadata": {}, "outputs": [], "source": [ - "# Custom color list\n", - "fig = xpx(life_exp).line(\n", - " color_discrete_sequence=[\"#E63946\", \"#457B9D\", \"#2A9D8F\", \"#E9C46A\", \"#F4A261\"],\n", - " title=\"Custom Color Sequence\",\n", + "# px.bar kwargs: barmode, text_auto\n", + "# See: https://plotly.com/python/bar-charts/\n", + "fig = xpx(life_exp.sel(year=[1952, 1982, 2007])).bar(\n", + " barmode=\"group\",\n", + " text_auto=\".1f\",\n", + " title=\"Life Expectancy\",\n", ")\n", "fig" ] @@ -285,48 +118,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Heatmap Colorscales" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(life_exp).imshow(\n", - " color_continuous_scale=\"Viridis\",\n", - " title=\"Viridis Colorscale\",\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Diverging colorscale with midpoint\n", - "# Calculate change from first year\n", - "life_change = life_exp - life_exp.isel(year=0)\n", - "life_change.name = \"change\"\n", + "## Modifying Figures After Creation\n", "\n", - "fig = xpx(life_change).imshow(\n", - " color_continuous_scale=\"RdBu\",\n", - " color_continuous_midpoint=0,\n", - " title=\"Life Expectancy Change (Diverging Colorscale)\",\n", - ")\n", - "fig" + "All methods return a Plotly `Figure`. Use the [Figure API](https://plotly.com/python/creating-and-updating-figures/) to customize:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Post-Creation Customization\n", + "### update_layout\n", "\n", - "All plots return Plotly `Figure` objects that you can customize further:" + "Modify [layout properties](https://plotly.com/python/reference/layout/): legend, margins, fonts, etc." ] }, { @@ -337,38 +140,16 @@ "source": [ "fig = xpx(stocks).line()\n", "\n", - "# Add horizontal reference line\n", - "fig.add_hline(y=1.0, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Baseline\")\n", - "\n", - "# Update layout\n", "fig.update_layout(\n", - " title=\"Stock Prices with Reference Line\",\n", + " title={\"text\": \"Stock Prices\", \"x\": 0.5, \"font\": {\"size\": 24}},\n", " legend={\n", " \"orientation\": \"h\",\n", " \"yanchor\": \"bottom\",\n", " \"y\": 1.02,\n", - " \"xanchor\": \"right\",\n", - " \"x\": 1,\n", + " \"xanchor\": \"center\",\n", + " \"x\": 0.5,\n", " },\n", - ")\n", - "\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Add vertical line for an event\n", - "fig = xpx(stocks).line(title=\"Stock Prices with Event Marker\")\n", - "\n", - "fig.add_vline(\n", - " x=\"2018-07-01\",\n", - " line_dash=\"dot\",\n", - " line_color=\"red\",\n", - " annotation_text=\"Mid-2018\",\n", + " margin={\"t\": 80},\n", ")\n", "fig" ] @@ -377,7 +158,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Modifying Traces" + "### update_traces\n", + "\n", + "Modify [trace properties](https://plotly.com/python/reference/): line width, markers, opacity, etc." ] }, { @@ -387,10 +170,7 @@ "outputs": [], "source": [ "fig = xpx(stocks).line()\n", - "\n", - "# Make all lines thicker\n", - "fig.update_traces(line_width=3)\n", - "\n", + "fig.update_traces(line={\"width\": 3})\n", "fig.update_layout(title=\"Thicker Lines\")\n", "fig" ] @@ -402,11 +182,8 @@ "outputs": [], "source": [ "fig = xpx(stocks).scatter()\n", - "\n", - "# Change marker style\n", - "fig.update_traces(marker={\"size\": 10, \"opacity\": 0.7})\n", - "\n", - "fig.update_layout(title=\"Custom Marker Style\")\n", + "fig.update_traces(marker={\"size\": 12, \"opacity\": 0.7, \"line\": {\"width\": 1, \"color\": \"white\"}})\n", + "fig.update_layout(title=\"Custom Markers\")\n", "fig" ] }, @@ -414,7 +191,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Adding Annotations" + "### update_xaxes / update_yaxes\n", + "\n", + "Modify [axis properties](https://plotly.com/python/axes/): range slider, tick format, etc." ] }, { @@ -423,16 +202,9 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(life_exp).line(title=\"Life Expectancy with Annotations\")\n", - "\n", - "# Add annotation for a specific point\n", - "fig.add_annotation(\n", - " x=2007,\n", - " y=life_exp.sel(year=2007, country=\"China\").values,\n", - " text=\"China 2007\",\n", - " showarrow=True,\n", - " arrowhead=2,\n", - ")\n", + "fig = xpx(stocks).line(title=\"With Range Slider\")\n", + "fig.update_xaxes(rangeslider_visible=True)\n", + "fig.update_yaxes(tickformat=\".0%\")\n", "fig" ] }, @@ -440,35 +212,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Exporting Figures" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Interactive HTML\n", - "\n", - "```python\n", - "fig.write_html(\"interactive_plot.html\")\n", - "```\n", - "\n", - "### Static Images\n", - "\n", - "Requires `kaleido`: `pip install kaleido`\n", + "### Adding Shapes and Annotations\n", "\n", - "```python\n", - "fig.write_image(\"plot.png\", scale=2) # High resolution\n", - "fig.write_image(\"plot.svg\") # Vector format\n", - "fig.write_image(\"plot.pdf\") # PDF\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Subplots with Shared Axes" + "Add reference lines, [shapes](https://plotly.com/python/shapes/), and [annotations](https://plotly.com/python/text-and-annotations/):" ] }, { @@ -477,14 +223,20 @@ "metadata": {}, "outputs": [], "source": [ - "# Faceted plot with shared y-axis\n", - "fig = xpx(combined).line(\n", - " facet_col=\"metric\",\n", - " title=\"Facets with Independent Y-Axes\",\n", - ")\n", + "fig = xpx(stocks).line(title=\"With Reference Lines and Annotations\")\n", + "\n", + "fig.add_hline(y=1.0, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Baseline\")\n", + "fig.add_vline(x=\"2018-10-01\", line_dash=\"dot\", line_color=\"red\")\n", "\n", - "# Each facet gets its own y-axis range by default\n", - "fig.update_yaxes(matches=None)\n", + "fig.add_annotation(\n", + " x=\"2018-10-01\",\n", + " y=1.4,\n", + " text=\"Market correction\",\n", + " showarrow=True,\n", + " arrowhead=2,\n", + " ax=40,\n", + " ay=-40,\n", + ")\n", "fig" ] }, @@ -492,9 +244,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Combining with Plotly Graph Objects\n", + "### Adding Traces with Graph Objects\n", "\n", - "You can add additional traces using Plotly's graph objects:" + "Add custom traces using [Plotly Graph Objects](https://plotly.com/python/graph-objects/):" ] }, { @@ -503,11 +255,9 @@ "metadata": {}, "outputs": [], "source": [ - "import plotly.graph_objects as go\n", - "\n", "fig = xpx(stocks.sel(company=\"GOOG\")).line(title=\"GOOG with Moving Average\")\n", "\n", - "# Calculate and add a moving average\n", + "# Add moving average as a new trace\n", "goog = stocks.sel(company=\"GOOG\")\n", "ma_20 = goog.rolling(date=20, center=True).mean()\n", "\n", @@ -517,11 +267,41 @@ " y=ma_20.values,\n", " mode=\"lines\",\n", " name=\"20-day MA\",\n", - " line={\"dash\": \"dash\", \"color\": \"red\"},\n", + " line={\"dash\": \"dash\", \"color\": \"red\", \"width\": 2},\n", " )\n", ")\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exporting Figures\n", + "\n", + "See [static image export](https://plotly.com/python/static-image-export/) and [HTML export](https://plotly.com/python/interactive-html-export/).\n", + "\n", + "```python\n", + "# Interactive HTML\n", + "fig.write_html(\"plot.html\")\n", + "\n", + "# Static images (requires: pip install kaleido)\n", + "fig.write_image(\"plot.png\", scale=2)\n", + "fig.write_image(\"plot.svg\")\n", + "fig.write_image(\"plot.pdf\")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More Resources\n", + "\n", + "- [Plotly Express API](https://plotly.com/python-api-reference/plotly.express.html)\n", + "- [Figure Reference](https://plotly.com/python/reference/)\n", + "- [Plotly Tutorials](https://plotly.com/python/)" + ] } ], "metadata": { From ba840e2cbc7f5fca1f0dba75263f4071d052c1f3 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:20:29 +0100 Subject: [PATCH 8/8] fix dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f335446..145b399 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ docs = [ "mkdocs-material==9.5.49", "mkdocstrings[python]==0.27.0", "mkdocs-jupyter==0.25.1", - "mkdocs-plotly-plugin==0.1.5", + "mkdocs-plotly-plugin==0.1.3", "jupyter==1.1.1", ]