{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "\n", "pn.extension('ipywidgets')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Matplotlib` pane allows displaying [Matplotlib](http://matplotlib.org) figures inside a Panel app. This includes figures created by [Seaborn](https://seaborn.pydata.org/), [Pandas `.plot`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html), [Plotnine](https://plotnine.readthedocs.io/) and any other plotting library building on top of `Matplotlib`.\n", "\n", "\n", "The `Matplotlib` pane will render the `object` to PNG or SVG at the declared DPI and then display it.\n", "\n", "#### Parameters:\n", "\n", "* **``alt_text``** (str, default=None): alt text to add to the image tag. The alt text is shown when a user cannot load or display the image. \n", "* **``dpi``** (int, default=144): The dots per inch of the exported png.\n", "* **``encode``** (bool, default=False): Whether to encode 'svg' as base64. Default is False. 'png' will always be encoded.\n", "* **``fixed_aspect``** (boolean, default=True): Whether the aspect ratio of the figure should be forced to be equal.\n", "* **``format``** (str, default='png'): The format to render the figure to: 'png' or 'svg'.\n", "* **``high_dpi``** (bool, default=True): Whether to optimize output for high-dpi displays.\n", "* **``interactive``** (boolean, default=False): Whether to use the interactive ipympl backend.\n", "* **``link_url``** (str, default=None): A link URL to make the figure clickable and link to some other website.\n", "* **``object``** (matplotlib.Figure): The Matplotlib [`Figure`](https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure) object to display.\n", "* **``tight``** (bool, default=False): Automatically adjust the figure size to fit the subplots and other artist elements.\n", "\n", "#### Resources\n", "\n", "- [How to - Style Matplotlib Plots](../../how_to/styling/matplotlib)\n", "___" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from matplotlib.figure import Figure\n", "from matplotlib import cm\n", "\n", "Y, X = np.mgrid[-3:3:100j, -3:3:100j]\n", "U = -1 - X**2 + Y\n", "V = 1 + X - Y**2\n", "\n", "fig = Figure(figsize=(4, 3))\n", "ax = fig.subplots()\n", "\n", "strm = ax.streamplot(X, Y, U, V, color=U, linewidth=2, cmap=cm.autumn)\n", "fig.colorbar(strm.lines)\n", "\n", "mpl_pane = pn.pane.Matplotlib(fig, dpi=144)\n", "mpl_pane" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By modifying the figure and using the ``trigger`` method on the pane's object we can easily update the plot:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "strm.lines.set_cmap(cm.viridis)\n", "\n", "mpl_pane.param.trigger('object')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, like all other models, a ``Matplotlib`` pane can be updated by setting the ``object`` directly:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from mpl_toolkits.mplot3d import axes3d\n", "\n", "fig3d = Figure(figsize=(8, 6))\n", "ax = fig3d.add_subplot(111, projection='3d')\n", "\n", "X, Y, Z = axes3d.get_test_data(0.05)\n", "ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.3)\n", "cset = ax.contourf(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)\n", "cset = ax.contourf(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)\n", "cset = ax.contourf(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)\n", "\n", "ax.set_xlabel('X')\n", "ax.set_xlim(-40, 40)\n", "ax.set_ylabel('Y')\n", "ax.set_ylim(-40, 40)\n", "ax.set_zlabel('Z')\n", "ax.set_zlim(-100, 100)\n", "\n", "mpl_pane.object = fig3d" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using the Matplotlib pyplot interface\n", "\n", "You might have noticed that we did not use the ``matplotlib.pyplot`` API above. We did this in order to avoid having to specifically close the figure. If the figure is not closed, it will cause memory leaks.\n", "\n", "**You can use the `matplotlib.pyplot` interface, but then you must specifically close the figure as shown below!**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "def create_voltage_figure(figsize=(4,3)):\n", " t = np.arange(0.0, 2.0, 0.01)\n", " s = 1 + np.sin(2 * np.pi * t)\n", "\n", " fig, ax = plt.subplots(figsize=figsize)\n", " ax.plot(t, s)\n", "\n", " ax.set(xlabel='time (s)', ylabel='voltage (mV)',\n", " title='Voltage')\n", " ax.grid()\n", "\n", " plt.close(fig) # CLOSE THE FIGURE!\n", " return fig\n", "\n", "pn.pane.Matplotlib(create_voltage_figure(), dpi=144, tight=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fixing clipping issues with `tight=True`\n", "\n", "If you find the figure to be clipped on the edges you can set `tight=True`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.FlexBox(\n", " pn.Column(\"## ❌ `tight=False`\", pn.pane.Matplotlib(create_voltage_figure(), dpi=144, tight=False)),\n", " pn.Column(\"## ✔️ `tight=True`\", pn.pane.Matplotlib(create_voltage_figure(), dpi=144, tight=True)),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Responsive plots\n", "\n", "If you want to make your plots responsively fit what ever container they are inside, then you should be using the appropriate `sizing_mode` in combination with\n", "\n", "- `format=\"svg\"`: to get better looking resized plots,\n", "- `fixed_aspect=True`: to allow the 'svg' image to resize its height and width independently and/ or\n", "- `fixed_aspect=False` (default): to allow the 'svg' image to resize its height and width while keeping the aspect ratio.\n", "\n", "Lets start by displaying using the default `'png'` format and `sizing_mode=\"stretch_width\"`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = create_voltage_figure(figsize=(6,1))\n", "\n", "pn.pane.Matplotlib(fig, tight=True, sizing_mode=\"stretch_width\", styles={\"background\": \"pink\"})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you have a wide window you will see some large, pink areas on the sides. If you decrease the window width, then you will see the plot responsively resize.\n", "\n", "Using the `'svg'` format you can make the figure take up the full width." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.pane.Matplotlib(fig, tight=True, format=\"svg\", sizing_mode=\"stretch_width\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But that might make the figure too high. Lets try with a fixed `height`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.pane.Matplotlib(fig, tight=True, height=150, format=\"svg\", sizing_mode=\"stretch_width\", styles={\"background\": \"pink\"})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But maybe we want the figure to take up the full width. Lets change the `fixed_aspect` to `False`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.pane.Matplotlib(fig, tight=True, height=150, format=\"svg\", fixed_aspect=False, sizing_mode=\"stretch_width\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In summary you should be able to achieve the kind of responsive sizing you need by using the appropriate combination of `format`, `fixed_aspect` and `sizing_mode` values." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using the interactive Matplotlib backend\n", "\n", "If you have installed [`ipympl`](https://matplotlib.org/ipympl/) you will also be able to use the `interactive` backend:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = Figure(figsize=(8, 6))\n", "ax = fig.add_subplot(111)\n", "\n", "dx, dy = 0.05, 0.05\n", "\n", "# generate 2 2d grids for the x & y bounds\n", "y, x = np.mgrid[slice(1, 5 + dy, dy),\n", " slice(1, 5 + dx, dx)]\n", "\n", "z = np.sin(x)**10 + np.cos(10 + y*x) * np.cos(x)\n", "\n", "cf = ax.contourf(x + dx/2., y + dy/2., z)\n", "fig.colorbar(cf, ax=ax)\n", "\n", "pn.pane.Matplotlib(fig, interactive=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Seaborn" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import seaborn as sns\n", "\n", "from matplotlib.figure import Figure\n", "\n", "sns.set_theme()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We recommend creating a Matplotlib `Figure` and providing it to Seaborn" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame(np.random.rand(10, 10), columns=[chr(65+i) for i in range(10)], index=[chr(97+i) for i in range(10)])\n", "\n", "fig = Figure(figsize=(2, 2))\n", "ax = fig.add_subplot(111)\n", "sns.heatmap(df, ax=ax)\n", "pn.pane.Matplotlib(fig, tight=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use Seaborn directly, but then you must remember to close the the `Figure` manually to avoid memory leaks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "dots = sns.load_dataset(\"dots\")\n", "fig = sns.relplot(\n", " data=dots, kind=\"line\",\n", " x=\"time\", y=\"firing_rate\", col=\"align\",\n", " hue=\"choice\", size=\"coherence\", style=\"choice\",\n", " facet_kws=dict(sharex=False),\n", ").fig\n", "plt.close(fig) # REMEMBER TO CLOSE THE FIGURE!\n", "pn.pane.Matplotlib(fig, height=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can remove the Seaborn theme via `matplotlib.rcdefaults()`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib import rcdefaults\n", "\n", "rcdefaults()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Pandas `.plot`\n", "\n", "We recommend creating a Matplotlib `Figure` and providing it to `pandas.plot`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from matplotlib.figure import Figure\n", "\n", "df = pd.DataFrame({'a': range(10)})\n", "fig = Figure(figsize=(4, 2))\n", "ax = fig.add_subplot(111)\n", "ax = df.plot.barh(ax=ax)\n", "\n", "pn.pane.Matplotlib(fig, tight=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Plotnine\n", "\n", "The [`plotnine.ggplot.draw`](https://plotnine.readthedocs.io/en/stable/generated/plotnine.ggplot.html#plotnine.ggplot.draw) method will return the Matplotlib `Figure` object.\n", "\n", "Please note you must close the figure your self.\n", "\n", "```python\n", "fig = plot.draw()\n", "matplotlib.pyplot.close(fig) # REMEMBER TO CLOSE THE FIGURE!\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Controls\n", "\n", "The `Matplotlib` pane exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.Row(mpl_pane.controls(jslink=True), mpl_pane)" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }