{
 "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
}
