Repository: holoviz/panel

Issue:
_tkinter.TclError: invalid command name ".!canvas"
```bash
pip install panel==0.13.1 hvplot==0.8.0 holoviews==1.14.9 matplotlib==3.5.2
```

--------

I'm trying to create a demo app using Panel, hvplot and HoloViews using the matplotlib backend

```python
import pandas as pd
import hvplot.pandas
import panel as pn
import holoviews as hv
import holoviews.plotting.mpl

pn.extension("plotly", sizing_mode="stretch_width")

hv.extension("matplotlib")

data = pd.DataFrame(
    {
        "x": [1, 2, 3, 4, 5, 6],
        "y": [2, 4, 8, 16, 32, 64],
    }
)
plot = data.hvplot(x="x", y="y", responsive=True)

pn.pane.HoloViews(plot, backend="matplotlib", sizing_mode="stretch_both", min_height=400).servable()
```

```
panel serve script2.py
```

![image](https://user-images.githubusercontent.com/42288570/176988770-fed67016-1af0-4ffd-a9e3-9211288d5e70.png)

```bash
TclError: invalid command name ".!canvas"
Traceback (most recent call last):
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\bokeh\application\handlers\code_runner.py", line 231, in run
    exec(self._code, module.__dict__)
  File "C:\repos\private\awesome-panel-lightning\script2.py", line 19, in <module>
    pn.pane.HoloViews(plot, backend="matplotlib", sizing_mode="stretch_both", min_height=400).servable()
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\viewable.py", line 374, in servable
    self.server_doc(title=title, location=location) # type: ignore
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\viewable.py", line 853, in server_doc
    model = self.get_root(doc)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\pane\base.py", line 314, in get_root
    root = self.layout._get_model(doc, comm=comm)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\layout\base.py", line 146, in _get_model
    objects = self._get_objects(model, [], doc, root, comm)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\layout\base.py", line 131, in _get_objects
    child = pane._get_model(doc, root, model, comm)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\pane\holoviews.py", line 273, in _get_model
    model = child_pane._get_model(doc, root, parent, comm)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\pane\plot.py", line 210, in _get_model
    return PNG._get_model(self, doc, root, parent, comm)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\pane\markup.py", line 39, in _get_model
    model = self._bokeh_model(**self._get_properties())
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\pane\image.py", line 125, in _get_properties
    data = self._data()
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\panel\pane\plot.py", line 244, in _data
    self.object.set_dpi(self.dpi)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\matplotlib\figure.py", line 2767, in set_dpi
    self.dpi = val
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\matplotlib\figure.py", line 2457, in _set_dpi
    self.set_size_inches(w, h, forward=forward)
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\matplotlib\figure.py", line 2723, in set_size_inches
    manager.resize(*(size * self.dpi).astype(int))
  File "c:\repos\private\awesome-panel-lightning\.venv\lib\site-packages\matplotlib\backends\_backend_tk.py", line 472, in resize
    self.canvas._tkcanvas.configure(width=width, height=height)
  File "C:\Program Files\Python39\lib\tkinter\__init__.py", line 1646, in configure
    return self._configure('configure', cnf, kw)
  File "C:\Program Files\Python39\lib\tkinter\__init__.py", line 1636, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".!canvas"
```


Discussion:
This doesn't seem like a Panel issue, you need to set the appropriate matplotlib backend. Overriding this automatically would be quite frustrating for some users.
See [matplotlib docs](https://matplotlib.org/stable/users/explain/backends.html#selecting-a-backend) for more details.
I think this should be better handled by hvplot, HoloViews or Panel.

The whole promise of hvPlot and HoloViews is that I can learn their apis and then output to Matplotlib, Bokeh or Plotly without having to know their api. If I have too know the Api of Matplotlib, Bokeh and/ or Plotly and how to navigate their docs in addition and dive into it whenever I want to use them as backend then things become more complex. Not less complex.

One potential improvement would be to catch the error message and tell me how to handle it. I still don't know how to handle it and would have to look up things in the matplotlib documentation. I don't think its obvious from the error message what the cause is and how to solve it.

An alternative improvement would be to warn or fail already when I set `hv.extension("matplotlib")` because the configuration of the `matplotlib` backend is not valid.
The problem with that is that setting the appropriate backend depends entirely on your use case and we simply cannot handle every possible error from every matplotlib backend. If you want to open a tkinter GUI from Panel then you should be able to do that. Using HoloViews together with a matplotlib GUI interface is also a supported configuration. The best we may be able to do in the HoloViews pane in Panel is warn about GUI based backends and point you to matplotlib.use('agg').

Resolving patch:
diff --git a/panel/pane/plot.py b/panel/pane/plot.py
--- a/panel/pane/plot.py
+++ b/panel/pane/plot.py
@@ -292,7 +292,10 @@ def _update(self, ref: str, model: Model) -> None:
         manager.canvas.draw_idle()
 
     def _data(self):
-        self.object.set_dpi(self.dpi)
+        try:
+            self.object.set_dpi(self.dpi)
+        except Exception as ex:
+            raise Exception("The Matplotlib backend is not configured. Try adding `matplotlib.use('agg')`") from ex
         b = BytesIO()
 
         if self.tight:


Test patch: