diff --git a/docs/examples/advanced.ipynb b/docs/examples/advanced.ipynb deleted file mode 100644 index 71bda7c..0000000 --- a/docs/examples/advanced.ipynb +++ /dev/null @@ -1,320 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Advanced Usage\n", - "\n", - "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/)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "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()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load sample data\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", - "\n", - "df_gap = px.data.gapminder()\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", - "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_exp\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Passing Kwargs to Plotly Express\n", - "\n", - "All keyword arguments are passed directly to [Plotly Express functions](https://plotly.com/python-api-reference/plotly.express.html):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# All px.line kwargs work: template, labels, colors, etc.\n", - "fig = xpx(stocks).line(\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": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 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", - "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" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 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" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Modifying Figures After Creation\n", - "\n", - "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": [ - "### update_layout\n", - "\n", - "Modify [layout properties](https://plotly.com/python/reference/layout/): legend, margins, fonts, etc." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks).line()\n", - "\n", - "fig.update_layout(\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\": \"center\",\n", - " \"x\": 0.5,\n", - " },\n", - " margin={\"t\": 80},\n", - ")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### update_traces\n", - "\n", - "Modify [trace properties](https://plotly.com/python/reference/): line width, markers, opacity, etc." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks).line()\n", - "fig.update_traces(line={\"width\": 3})\n", - "fig.update_layout(title=\"Thicker Lines\")\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks).scatter()\n", - "fig.update_traces(marker={\"size\": 12, \"opacity\": 0.7, \"line\": {\"width\": 1, \"color\": \"white\"}})\n", - "fig.update_layout(title=\"Custom Markers\")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### update_xaxes / update_yaxes\n", - "\n", - "Modify [axis properties](https://plotly.com/python/axes/): range slider, tick format, etc." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks).line(title=\"With Range Slider\")\n", - "fig.update_xaxes(rangeslider_visible=True)\n", - "fig.update_yaxes(tickformat=\".0%\")\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding Shapes and Annotations\n", - "\n", - "Add reference lines, [shapes](https://plotly.com/python/shapes/), and [annotations](https://plotly.com/python/text-and-annotations/):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "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", - "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" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding Traces with Graph Objects\n", - "\n", - "Add custom traces using [Plotly Graph Objects](https://plotly.com/python/graph-objects/):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = xpx(stocks.sel(company=\"GOOG\")).line(title=\"GOOG with Moving Average\")\n", - "\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", - "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\", \"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": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.12.0" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/examples/dimensions.ipynb b/docs/examples/dimensions.ipynb new file mode 100644 index 0000000..146146c --- /dev/null +++ b/docs/examples/dimensions.ipynb @@ -0,0 +1,232 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dimensions, Facets & Animation\n", + "\n", + "How to control which dimensions map to which visual properties." + ] + }, + { + "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()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Sample data: 2D\n", + "df = px.data.stocks().set_index(\"date\")\n", + "df.index = df.index.astype(\"datetime64[ns]\")\n", + "\n", + "stocks = xr.DataArray(\n", + " df.values,\n", + " dims=[\"date\", \"company\"],\n", + " coords={\"date\": df.index, \"company\": df.columns.tolist()},\n", + " name=\"price\",\n", + ")\n", + "\n", + "# Sample data: 3D\n", + "df_gap = px.data.gapminder()\n", + "countries = [\"United States\", \"China\", \"Germany\", \"Brazil\"]\n", + "metrics = [\"lifeExp\", \"gdpPercap\"]\n", + "\n", + "arrays = []\n", + "for metric in metrics:\n", + " df_pivot = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n", + " index=\"year\", columns=\"country\", values=metric\n", + " )\n", + " arrays.append(df_pivot.values)\n", + "\n", + "data_3d = xr.DataArray(\n", + " arrays,\n", + " dims=[\"metric\", \"year\", \"country\"],\n", + " coords={\n", + " \"metric\": metrics,\n", + " \"year\": df_pivot.index.tolist(),\n", + " \"country\": df_pivot.columns.tolist(),\n", + " },\n", + " name=\"value\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Default Dimension Assignment\n", + "\n", + "Dimensions are assigned to slots in order:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# date → x, company → color\n", + "xpx(stocks).line()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Explicit Assignment\n", + "\n", + "Override the defaults by specifying which dimension goes where:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Swap: company → x, date → color\n", + "xpx(stocks.isel(date=[0, 50, 100])).bar(x=\"company\", color=\"date\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Skipping Slots with None\n", + "\n", + "Use `None` to skip a slot, so dimensions shift to the next available slot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Skip color → company goes to line_dash instead\n", + "xpx(stocks.sel(company=[\"GOOG\", \"AAPL\", \"MSFT\"])).line(color=None)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Available Slots\n", + "\n", + "Different plot types have different slots:\n", + "\n", + "| Plot | Slots (in order) |\n", + "|------|------------------|\n", + "| line | x, color, line_dash, facet_col, facet_row, animation_frame |\n", + "| scatter | x, color, symbol, facet_col, facet_row, animation_frame |\n", + "| bar | x, color, facet_col, facet_row, animation_frame |\n", + "| imshow | x, y, facet_col, facet_row, animation_frame |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# line_dash for third dimension\n", + "xpx(stocks.sel(company=[\"GOOG\", \"AAPL\"])).line(line_dash=\"company\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# symbol for scatter\n", + "xpx(stocks.sel(company=[\"GOOG\", \"AAPL\"])).scatter(symbol=\"company\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Faceting\n", + "\n", + "Create a grid of subplots with `facet_col` and `facet_row`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# One subplot per metric\n", + "xpx(data_3d).line(facet_col=\"metric\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Grid: metric x country\n", + "xpx(data_3d).line(x=\"year\", facet_col=\"metric\", facet_row=\"country\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Animation\n", + "\n", + "Animate over a dimension with `animation_frame`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Animate through years\n", + "xpx(data_3d.sel(metric=\"lifeExp\")).bar(x=\"country\", animation_frame=\"year\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Animate heatmap\n", + "xpx(data_3d).imshow(animation_frame=\"metric\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/figure.ipynb b/docs/examples/figure.ipynb new file mode 100644 index 0000000..277e4dd --- /dev/null +++ b/docs/examples/figure.ipynb @@ -0,0 +1,236 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working with Figures\n", + "\n", + "All methods return a [Plotly Figure](https://plotly.com/python/figure-structure/) that can be modified and exported." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "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()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = px.data.stocks().set_index(\"date\")\n", + "df.index = df.index.astype(\"datetime64[ns]\")\n", + "\n", + "stocks = xr.DataArray(\n", + " df.values,\n", + " dims=[\"date\", \"company\"],\n", + " coords={\"date\": df.index, \"company\": df.columns.tolist()},\n", + " name=\"price\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## update_layout\n", + "\n", + "Modify [layout properties](https://plotly.com/python/reference/layout/): title, legend, margins, fonts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).line()\n", + "fig.update_layout(\n", + " title={\"text\": \"Stock Prices\", \"x\": 0.5},\n", + " legend={\"orientation\": \"h\", \"y\": 1.02},\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## update_traces\n", + "\n", + "Modify [trace properties](https://plotly.com/python/reference/): line width, markers, opacity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).line()\n", + "fig.update_traces(line={\"width\": 3})\n", + "fig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).scatter()\n", + "fig.update_traces(marker={\"size\": 10, \"opacity\": 0.7})\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## update_xaxes / update_yaxes\n", + "\n", + "Modify [axis properties](https://plotly.com/python/axes/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).line()\n", + "fig.update_xaxes(rangeslider_visible=True)\n", + "fig.update_yaxes(tickformat=\".0%\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## add_hline / add_vline\n", + "\n", + "Add reference lines. See [shapes](https://plotly.com/python/shapes/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).line()\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", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## add_annotation\n", + "\n", + "Add text annotations. See [annotations](https://plotly.com/python/text-and-annotations/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks).line()\n", + "fig.add_annotation(x=\"2018-10-01\", y=1.4, text=\"Peak\", showarrow=True, arrowhead=2)\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## add_trace\n", + "\n", + "Add custom traces using [Graph Objects](https://plotly.com/python/graph-objects/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = xpx(stocks.sel(company=\"GOOG\")).line()\n", + "\n", + "# Add moving average\n", + "goog = stocks.sel(company=\"GOOG\")\n", + "ma = goog.rolling(date=20, center=True).mean()\n", + "\n", + "fig.add_trace(\n", + " go.Scatter(\n", + " x=ma.coords[\"date\"].values,\n", + " y=ma.values,\n", + " mode=\"lines\",\n", + " name=\"20-day MA\",\n", + " line={\"dash\": \"dash\", \"color\": \"red\"},\n", + " )\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Export\n", + "\n", + "### HTML\n", + "\n", + "See [HTML export](https://plotly.com/python/interactive-html-export/).\n", + "\n", + "```python\n", + "fig.write_html(\"plot.html\")\n", + "fig.write_html(\"plot.html\", include_plotlyjs=\"cdn\") # smaller file\n", + "```\n", + "\n", + "### Images\n", + "\n", + "Requires [kaleido](https://github.com/plotly/Kaleido): `pip install kaleido`\n", + "\n", + "See [static image export](https://plotly.com/python/static-image-export/).\n", + "\n", + "```python\n", + "fig.write_image(\"plot.png\")\n", + "fig.write_image(\"plot.png\", scale=2) # higher resolution\n", + "fig.write_image(\"plot.svg\")\n", + "fig.write_image(\"plot.pdf\")\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/kwargs.ipynb b/docs/examples/kwargs.ipynb new file mode 100644 index 0000000..d28e100 --- /dev/null +++ b/docs/examples/kwargs.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plotly Express Kwargs\n", + "\n", + "All keyword arguments are passed directly to [Plotly Express](https://plotly.com/python-api-reference/plotly.express.html)." + ] + }, + { + "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()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = px.data.stocks().set_index(\"date\")\n", + "df.index = df.index.astype(\"datetime64[ns]\")\n", + "\n", + "stocks = xr.DataArray(\n", + " df.values,\n", + " dims=[\"date\", \"company\"],\n", + " coords={\"date\": df.index, \"company\": df.columns.tolist()},\n", + " name=\"price\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## title" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).line(title=\"Stock Prices\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## template\n", + "\n", + "See [Plotly templates](https://plotly.com/python/templates/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).line(template=\"plotly_dark\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).line(template=\"seaborn\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## labels\n", + "\n", + "Override axis and legend labels:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).line(labels={\"price\": \"Normalized Price\", \"date\": \"Date\", \"company\": \"Ticker\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## color_discrete_sequence\n", + "\n", + "See [Plotly color sequences](https://plotly.com/python/discrete-color/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).line(color_discrete_sequence=px.colors.qualitative.Set2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).line(\n", + " color_discrete_sequence=[\"#E63946\", \"#457B9D\", \"#2A9D8F\", \"#E9C46A\", \"#F4A261\", \"#264653\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## color_continuous_scale\n", + "\n", + "See [Plotly colorscales](https://plotly.com/python/builtin-colorscales/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).imshow(color_continuous_scale=\"Viridis\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Diverging colorscale with midpoint\n", + "change = stocks - stocks.isel(date=0)\n", + "xpx(change).imshow(color_continuous_scale=\"RdBu_r\", color_continuous_midpoint=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## markers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks.isel(date=slice(0, 30))).line(markers=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## barmode\n", + "\n", + "See [Plotly bar charts](https://plotly.com/python/bar-charts/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks.isel(date=[0, 50, 100])).bar(barmode=\"group\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## text_auto" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks.isel(date=[0, 50, 100])).bar(barmode=\"group\", text_auto=\".2f\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## range_x, range_y" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xpx(stocks).line(range_y=[0.5, 2.0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More\n", + "\n", + "See the full [Plotly Express API reference](https://plotly.com/python-api-reference/plotly.express.html) for all available kwargs." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/plot-types.ipynb b/docs/examples/plot-types.ipynb index 1f068a7..3e5ef05 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." + "Overview of all available plot types." ] }, { @@ -20,18 +20,7 @@ "\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" + "config.notebook()" ] }, { @@ -40,137 +29,23 @@ "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", + "# Sample data\n", + "df = px.data.stocks().set_index(\"date\")\n", + "df.index = df.index.astype(\"datetime64[ns]\")\n", "\n", "stocks = xr.DataArray(\n", - " df_stocks.values,\n", + " df.values,\n", " dims=[\"date\", \"company\"],\n", - " coords={\"date\": df_stocks.index, \"company\": df_stocks.columns.tolist()},\n", + " coords={\"date\": df.index, \"company\": df.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:" + "## area" ] }, { @@ -179,29 +54,14 @@ "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" + "xpx(stocks).area()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Scatter Plot\n", - "\n", - "Best for showing relationships between variables:" + "## bar" ] }, { @@ -210,28 +70,14 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(stocks).scatter(title=\"Stock Prices Scatter\")\n", - "fig" + "xpx(stocks.isel(date=[0, 25, 50, 75, 100])).bar()" ] }, { "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" + "## box" ] }, { @@ -240,18 +86,14 @@ "metadata": {}, "outputs": [], "source": [ - "# Life expectancy distributions by country\n", - "fig = xpx(life_exp).box(title=\"Life Expectancy Distribution by Country\")\n", - "fig" + "xpx(stocks).box()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Heatmap (imshow)\n", - "\n", - "Best for 2D grid data:" + "## imshow" ] }, { @@ -260,15 +102,14 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(life_exp).imshow(title=\"Life Expectancy Heatmap\")\n", - "fig" + "xpx(stocks).imshow()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### With different colorscale" + "## line" ] }, { @@ -277,20 +118,14 @@ "metadata": {}, "outputs": [], "source": [ - "fig = xpx(gdp).imshow(\n", - " color_continuous_scale=\"Viridis\",\n", - " title=\"GDP per Capita by Continent and Year\",\n", - ")\n", - "fig" + "xpx(stocks).line()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Faceting\n", - "\n", - "All plot types support faceting to create subplot grids:" + "## pie" ] }, { @@ -299,54 +134,14 @@ "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" + "xpx(stocks.isel(date=-1)).pie()" ] }, { "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" + "## scatter" ] }, { @@ -355,14 +150,7 @@ "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" + "xpx(stocks).scatter()" ] } ], diff --git a/docs/index.md b/docs/index.md index 9475099..3dea964 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,5 +7,6 @@ - [Getting Started](getting-started.ipynb) - Interactive tutorial - [Plot Types](examples/plot-types.ipynb) - All available plot types -- [Advanced Usage](examples/advanced.ipynb) - Configuration and customization +- [Dimensions & Facets](examples/dimensions.ipynb) - Control dimension mapping +- [Figure Customization](examples/figure.ipynb) - Customize your figures - [API Reference](api.md) - Full API documentation diff --git a/mkdocs.yml b/mkdocs.yml index 5d5055d..23fddb3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -69,5 +69,7 @@ nav: - Getting Started: getting-started.ipynb - Examples: - Plot Types: examples/plot-types.ipynb - - Advanced Usage: examples/advanced.ipynb + - Dimensions & Facets: examples/dimensions.ipynb + - Plotly Express Options: examples/kwargs.ipynb + - Figure Customization: examples/figure.ipynb - API Reference: api.md diff --git a/xarray_plotly/accessor.py b/xarray_plotly/accessor.py index 3c2dc36..a947b4a 100644 --- a/xarray_plotly/accessor.py +++ b/xarray_plotly/accessor.py @@ -33,7 +33,7 @@ class DataArrayPlotlyAccessor: ``` """ - __all__: ClassVar = ["line", "bar", "area", "scatter", "box", "imshow"] + __all__: ClassVar = ["line", "bar", "area", "scatter", "box", "imshow", "pie"] def __init__(self, darray: DataArray) -> None: self._da = darray @@ -274,6 +274,38 @@ def imshow( **px_kwargs, ) + def pie( + self, + *, + names: SlotValue = auto, + color: SlotValue = None, + facet_col: SlotValue = auto, + facet_row: SlotValue = auto, + **px_kwargs: Any, + ) -> go.Figure: + """Create an interactive pie chart. + + Slot order: names -> facet_col -> facet_row + + Args: + names: Dimension for pie slice names/categories. Default: first dimension. + color: Dimension for color grouping. Default: None (uses names). + facet_col: Dimension for subplot columns. Default: second dimension. + facet_row: Dimension for subplot rows. Default: third dimension. + **px_kwargs: Additional arguments passed to `plotly.express.pie()`. + + Returns: + Interactive Plotly Figure. + """ + return plotting.pie( + self._da, + names=names, + color=color, + facet_col=facet_col, + facet_row=facet_row, + **px_kwargs, + ) + class DatasetPlotlyAccessor: """Plotly Express plotting accessor for xarray Dataset. @@ -307,7 +339,7 @@ class DatasetPlotlyAccessor: ``` """ - __all__: ClassVar = ["line", "bar", "area", "scatter", "box"] + __all__: ClassVar = ["line", "bar", "area", "scatter", "box", "pie"] def __init__(self, dataset: Dataset) -> None: self._ds = dataset @@ -519,3 +551,36 @@ def box( animation_frame=animation_frame, **px_kwargs, ) + + def pie( + self, + var: str | None = None, + *, + names: SlotValue = auto, + color: SlotValue = None, + facet_col: SlotValue = auto, + facet_row: SlotValue = auto, + **px_kwargs: Any, + ) -> go.Figure: + """Create an interactive pie chart. + + Args: + var: Variable to plot. If None, plots all variables with "variable" dimension. + names: Dimension for pie slice names/categories. + color: Dimension for color grouping. + facet_col: Dimension for subplot columns. + facet_row: Dimension for subplot rows. + **px_kwargs: Additional arguments passed to `plotly.express.pie()`. + + Returns: + Interactive Plotly Figure. + """ + da = self._get_dataarray(var) + return plotting.pie( + da, + names=names, + color=color, + facet_col=facet_col, + facet_row=facet_row, + **px_kwargs, + ) diff --git a/xarray_plotly/config.py b/xarray_plotly/config.py index d077319..e18c931 100644 --- a/xarray_plotly/config.py +++ b/xarray_plotly/config.py @@ -44,6 +44,7 @@ ), "imshow": ("y", "x", "facet_col", "animation_frame"), "box": ("x", "color", "facet_col", "facet_row", "animation_frame"), + "pie": ("names", "facet_col", "facet_row"), } diff --git a/xarray_plotly/plotting.py b/xarray_plotly/plotting.py index 7e1fe87..a95e702 100644 --- a/xarray_plotly/plotting.py +++ b/xarray_plotly/plotting.py @@ -446,3 +446,64 @@ def imshow( animation_frame=slots.get("animation_frame"), **px_kwargs, ) + + +def pie( + darray: DataArray, + *, + names: SlotValue = auto, + color: SlotValue = None, + facet_col: SlotValue = auto, + facet_row: SlotValue = auto, + **px_kwargs: Any, +) -> go.Figure: + """ + Create an interactive pie chart from a DataArray. + + The values are the DataArray values. Dimensions fill slots in order: + names -> facet_col -> facet_row + + Parameters + ---------- + darray + The DataArray to plot. + names + Dimension for pie slice names/categories. Default: first dimension. + color + Dimension for color grouping. Default: None (uses names). + facet_col + Dimension for subplot columns. Default: second dimension. + facet_row + Dimension for subplot rows. Default: third dimension. + **px_kwargs + Additional arguments passed to `plotly.express.pie()`. + + Returns + ------- + plotly.graph_objects.Figure + """ + slots = assign_slots( + list(darray.dims), + "pie", + names=names, + facet_col=facet_col, + facet_row=facet_row, + ) + + df = to_dataframe(darray) + value_col = get_value_col(darray) + labels = {**build_labels(darray, slots, value_col), **px_kwargs.pop("labels", {})} + + # Use names dimension for color if not explicitly set + color_col = color if color is not None else slots.get("names") + + return px.pie( + df, + names=slots.get("names"), + values=value_col, + color=color_col, + facet_col=slots.get("facet_col"), + facet_row=slots.get("facet_row"), + labels=labels, + **px_kwargs, + )