We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
unexpected Interactivity of "layered" plotly objects in Tabs
My first one of these so apologies if it's missing info / already flagged.

#### Software info:
python: 3.6.9
Panel = 0.6.2
plotly = 4.2.1
bokeh = 1.3.4
jupyter notebook server = 6.0.0
browser: Google chrome (and same behavior in embedded html output)
OS: Windows 8.1

#### Description of expected behavior and the observed behavior
Below applies to within jupyter notebook, in browser window (.show()) and in html output (.save(embed = True))

__Expected behavior:__ plotly objects within panel tabs to have same interactivity as when not in tabs.

__observed behavior:__ Only plotly object in "bottom level" (last in list of tabs, "fig2" tab in example) retains full interactivity (pan, zoom, select, legend trace select etc.)). All other tab "levels" (tabs other than the last one in tab list, "fig1" tab inexample) retain only legend select interactivity. Interactions with the area bound by the axis (where a crosshair is seen) in "fig1" results in changes to the "fig2" plotly object.


#### Complete, minimal, self-contained example code that reproduces the issue

```
import plotly.graph_objs as go
import panel as pn
import numpy as np
pn.extension('plotly')

x = np.linspace(0,10,100)
y = np.sin(x)
y2 = np.cos(x)

data1 = [go.Scatter(x = x, y = y, name = 'scatter sin(x)', mode="markers+lines"),
       go.Scatter(x = x, y = y2, name = 'scatter cos(x)', mode="markers+lines")]

data2 = [go.Bar(x = x, y = y, name = 'bar sin(x)'),
       go.Bar(x = x, y = y2, name = 'bar cos(x)')]

fig1 = go.Figure(data = data1)
fig2 = go.Figure(data = data2)

pn.Column('## App with plotly objects in tabs:',
          'Loss of interactivity on "fig1" tab plotly object',
         pn.Tabs(('fig1', pn.Pane(fig1)),
                 ('fig2', pn.Pane(fig2)))
         )

```
#### Screenshots of issue

![Panel_Plotly_tabs_bug](https://user-images.githubusercontent.com/38754794/69160627-d1ae4580-0ae1-11ea-999d-5557841df206.png)



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `panel/pane/plotly.py`
Content:
```
1 """
2 Defines a PlotlyPane which renders a plotly plot using PlotlyPlot
3 bokeh model.
4 """
5 import numpy as np
6 import param
7 
8 from bokeh.models import ColumnDataSource
9 from pyviz_comms import JupyterComm
10 
11 from .base import PaneBase
12 from ..util import isdatetime, lazy_load
13 from ..viewable import Layoutable
14 
15 
16 
17 class Plotly(PaneBase):
18     """
19     Plotly panes allow rendering plotly Figures and traces.
20 
21     For efficiency any array objects found inside a Figure are added
22     to a ColumnDataSource which allows using binary transport to sync
23     the figure on bokeh server and via Comms.
24     """
25 
26     click_data = param.Dict(doc="Click callback data")
27 
28     clickannotation_data = param.Dict(doc="Clickannotation callback data")
29 
30     config = param.Dict(doc="Config data")
31 
32     hover_data = param.Dict(doc="Hover callback data")
33 
34     relayout_data = param.Dict(doc="Relayout callback data")
35 
36     restyle_data = param.List(doc="Restyle callback data")
37 
38     selected_data = param.Dict(doc="Selected callback data")
39 
40     viewport = param.Dict(doc="Current viewport state")
41 
42     viewport_update_policy = param.Selector(default="mouseup", doc="""
43         Policy by which the viewport parameter is updated during user interactions.
44 
45         * "mouseup": updates are synchronized when mouse button is
46           released after panning
47         * "continuous": updates are synchronized continually while panning
48         * "throttle": updates are synchronized while panning, at 
49           intervals determined by the viewport_update_throttle parameter
50         """, objects=["mouseup", "continuous", "throttle"])
51 
52     viewport_update_throttle = param.Integer(default=200, bounds=(0, None), doc="""
53         Time interval in milliseconds at which viewport updates are
54         synchronized when viewport_update_policy is "throttle".""")
55 
56     _render_count = param.Integer(default=0, doc="""
57         Number of renders, increment to trigger re-render""")
58 
59     priority = 0.8
60 
61     _updates = True
62 
63     @classmethod
64     def applies(cls, obj):
65         return ((isinstance(obj, list) and obj and all(cls.applies(o) for o in obj)) or
66                 hasattr(obj, 'to_plotly_json') or (isinstance(obj, dict)
67                                                    and 'data' in obj and 'layout' in obj))
68 
69     def __init__(self, object=None, **params):
70         super().__init__(object, **params)
71         self._figure = None
72         self._update_figure()
73 
74     def _to_figure(self, obj):
75         import plotly.graph_objs as go
76         if isinstance(obj, go.Figure):
77             return obj
78         elif isinstance(obj, dict):
79             data, layout = obj['data'], obj['layout']
80         elif isinstance(obj, tuple):
81             data, layout = obj
82         else:
83             data, layout = obj, {}
84         data = data if isinstance(data, list) else [data]
85         return go.Figure(data=data, layout=layout)
86 
87     @staticmethod
88     def _get_sources(json):
89         sources = []
90         traces = json.get('data', [])
91         for trace in traces:
92             data = {}
93             Plotly._get_sources_for_trace(trace, data)
94             sources.append(ColumnDataSource(data))
95         return sources
96 
97     @staticmethod
98     def _get_sources_for_trace(json, data, parent_path=''):
99         for key, value in list(json.items()):
100             full_path = key if not parent_path else (parent_path + '.' + key)
101             if isinstance(value, np.ndarray):
102                 # Extract numpy array
103                 data[full_path] = [json.pop(key)]
104             elif isinstance(value, dict):
105                 # Recurse into dictionaries:
106                 Plotly._get_sources_for_trace(value, data=data, parent_path=full_path)
107             elif isinstance(value, list) and value and isinstance(value[0], dict):
108                 # recurse into object arrays:
109                 for i, element in enumerate(value):
110                     element_path = full_path + '.' + str(i)
111                     Plotly._get_sources_for_trace(
112                         element, data=data, parent_path=element_path
113                     )
114 
115     @param.depends('object', watch=True)
116     def _update_figure(self):
117         import plotly.graph_objs as go
118 
119         if (self.object is None or
120                 type(self.object) is not go.Figure or
121                 self.object is self._figure):
122             return
123 
124         # Monkey patch the message stubs used by FigureWidget.
125         # We only patch `Figure` objects (not subclasses like FigureWidget) so
126         # we don't interfere with subclasses that override these methods.
127         fig = self.object
128         fig._send_addTraces_msg = lambda *_, **__: self.param.trigger('object')
129         fig._send_moveTraces_msg = lambda *_, **__: self.param.trigger('object')
130         fig._send_deleteTraces_msg = lambda *_, **__: self.param.trigger('object')
131         fig._send_restyle_msg = lambda *_, **__: self.param.trigger('object')
132         fig._send_relayout_msg = lambda *_, **__: self.param.trigger('object')
133         fig._send_update_msg = lambda *_, **__: self.param.trigger('object')
134         fig._send_animate_msg = lambda *_, **__: self.param.trigger('object')
135         self._figure = fig
136 
137     def _update_data_sources(self, cds, trace):
138         trace_arrays = {}
139         Plotly._get_sources_for_trace(trace, trace_arrays)
140 
141         update_sources = False
142         for key, new_col in trace_arrays.items():
143             new = new_col[0]
144 
145             try:
146                 old = cds.data.get(key)[0]
147                 update_array = (
148                     (type(old) != type(new)) or
149                     (new.shape != old.shape) or
150                     (new != old).any())
151             except Exception:
152                 update_array = True
153 
154             if update_array:
155                 update_sources = True
156                 cds.data[key] = [new]
157 
158         return update_sources
159 
160     @staticmethod
161     def _plotly_json_wrapper(fig):
162         """Wraps around to_plotly_json and applies necessary fixes.
163 
164         For #382: Map datetime elements to strings.
165         """
166         json = fig.to_plotly_json()
167         data = json['data']
168 
169         for idx in range(len(data)):
170             for key in data[idx]:
171                 if isdatetime(data[idx][key]):
172                     arr = data[idx][key]
173                     if isinstance(arr, np.ndarray):
174                         arr = arr.astype(str) 
175                     else:
176                         arr = [str(v) for v in arr]
177                     data[idx][key] = arr
178         return json
179 
180     def _init_params(self):
181         viewport_params = [p for p in self.param if 'viewport' in p]
182         parameters = list(Layoutable.param)+viewport_params
183         params = {p: getattr(self, p) for p in parameters
184                   if getattr(self, p) is not None}
185 
186         if self.object is None:
187             json, sources = {}, []
188         else:
189             fig = self._to_figure(self.object)
190             json = self._plotly_json_wrapper(fig)
191             sources = Plotly._get_sources(json)
192 
193         params['_render_count'] = self._render_count
194         params['config'] = self.config or {}
195         params['data'] = json.get('data', [])
196         params['data_sources'] = sources
197         params['layout'] = layout = json.get('layout', {})
198         if layout.get('autosize') and self.sizing_mode is self.param.sizing_mode.default:
199             params['sizing_mode'] = 'stretch_both'
200         return params
201 
202     def _get_model(self, doc, root=None, parent=None, comm=None):
203         PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot', isinstance(comm, JupyterComm))
204         model = PlotlyPlot(**self._init_params())
205         if root is None:
206             root = model
207         self._link_props(model, self._linkable_params, doc, root, comm)
208         self._models[root.ref['id']] = (model, parent)
209         return model
210 
211     def _update(self, ref=None, model=None):
212         if self.object is None:
213             model.update(data=[], layout={})
214             model._render_count += 1
215             return
216 
217         fig = self._to_figure(self.object)
218         json = self._plotly_json_wrapper(fig)
219         layout = json.get('layout')
220 
221         traces = json['data']
222         new_sources = []
223         update_sources = False
224         for i, trace in enumerate(traces):
225             if i < len(model.data_sources):
226                 cds = model.data_sources[i]
227             else:
228                 cds = ColumnDataSource()
229                 new_sources.append(cds)
230 
231             update_sources = self._update_data_sources(cds, trace) or update_sources
232         try:
233             update_layout = model.layout != layout
234         except Exception:
235             update_layout = True
236 
237         # Determine if model needs updates
238         if (len(model.data) != len(traces)):
239             update_data = True
240         else:
241             update_data = False
242             for new, old in zip(traces, model.data):
243                 try:
244                     update_data = (
245                         {k: v for k, v in new.items() if k != 'uid'} !=
246                         {k: v for k, v in old.items() if k != 'uid'}
247                     )
248                 except Exception:
249                     update_data = True
250                 if update_data:
251                     break
252 
253         updates = {}
254         if self.sizing_mode is self.param.sizing_mode.default and 'autosize' in layout:
255             autosize = layout.get('autosize')
256             if autosize and model.sizing_mode != 'stretch_both':
257                 updates['sizing_mode'] = 'stretch_both'
258             elif not autosize and model.sizing_mode != 'fixed':
259                 updates['sizing_mode'] = 'fixed'
260 
261         if new_sources:
262             updates['data_sources'] = model.data_sources + new_sources
263 
264         if update_data:
265             updates['data'] = json.get('data')
266 
267         if update_layout:
268             updates['layout'] = layout
269 
270         if updates:
271             model.update(**updates)
272 
273         # Check if we should trigger rendering
274         if updates or update_sources:
275             model._render_count += 1
276
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/panel/pane/plotly.py b/panel/pane/plotly.py
--- a/panel/pane/plotly.py
+++ b/panel/pane/plotly.py
@@ -5,12 +5,12 @@
 import numpy as np
 import param
 
-from bokeh.models import ColumnDataSource
+from bokeh.models import ColumnDataSource, CustomJS, Tabs
 from pyviz_comms import JupyterComm
 
 from .base import PaneBase
 from ..util import isdatetime, lazy_load
-from ..viewable import Layoutable
+from ..viewable import Layoutable, Viewable
 
 
 
@@ -206,6 +206,8 @@
             root = model
         self._link_props(model, self._linkable_params, doc, root, comm)
         self._models[root.ref['id']] = (model, parent)
+        if _patch_tabs_plotly not in Viewable._preprocessing_hooks:
+            Viewable._preprocessing_hooks.append(_patch_tabs_plotly)
         return model
 
     def _update(self, ref=None, model=None):
@@ -273,3 +275,42 @@
         # Check if we should trigger rendering
         if updates or update_sources:
             model._render_count += 1
+
+
+def _patch_tabs_plotly(viewable, root):
+    """
+    A preprocessing hook which ensures that any Plotly panes rendered
+    inside Tabs are only visible when the tab they are in is active.
+    This is a workaround for https://github.com/holoviz/panel/issues/804.
+    """
+    from ..models.plotly import PlotlyPlot
+
+    tabs_models = list(root.select({'type': Tabs}))
+    plotly_models = root.select({'type': PlotlyPlot})
+
+    for model in plotly_models:
+        parent_tabs = []
+        for tabs in tabs_models:
+            if tabs.select_one({'id': model.id}):
+                parent_tabs.append(tabs)
+        parent_tab = None
+        for tabs in parent_tabs:
+            if not any(tabs.select_one(pt) for pt in parent_tabs if pt is not tabs):
+                parent_tab = tabs
+                break
+        if parent_tab is None:
+            return
+        for i, tab in enumerate(parent_tab.tabs):
+            if tab.select_one({'id': model.id}):
+                break
+        updated = False
+        code = "model.visible = cb_obj.active == i;"
+        for cb in parent_tab.js_property_callbacks.get('change:active', []):
+            if cb.code == code and cb.args.get('model') is model:
+                cb.args['i'] = i
+                updated = True
+        if updated:
+            continue
+        callback = CustomJS(args={'model': model, 'i': i, 'margin': model.margin}, code=code)
+        parent_tab.js_on_change('active', callback)
+        model.visible = parent_tab.active == i

{"golden_diff": "diff --git a/panel/pane/plotly.py b/panel/pane/plotly.py\n--- a/panel/pane/plotly.py\n+++ b/panel/pane/plotly.py\n@@ -5,12 +5,12 @@\n import numpy as np\n import param\n \n-from bokeh.models import ColumnDataSource\n+from bokeh.models import ColumnDataSource, CustomJS, Tabs\n from pyviz_comms import JupyterComm\n \n from .base import PaneBase\n from ..util import isdatetime, lazy_load\n-from ..viewable import Layoutable\n+from ..viewable import Layoutable, Viewable\n \n \n \n@@ -206,6 +206,8 @@\n             root = model\n         self._link_props(model, self._linkable_params, doc, root, comm)\n         self._models[root.ref['id']] = (model, parent)\n+        if _patch_tabs_plotly not in Viewable._preprocessing_hooks:\n+            Viewable._preprocessing_hooks.append(_patch_tabs_plotly)\n         return model\n \n     def _update(self, ref=None, model=None):\n@@ -273,3 +275,42 @@\n         # Check if we should trigger rendering\n         if updates or update_sources:\n             model._render_count += 1\n+\n+\n+def _patch_tabs_plotly(viewable, root):\n+    \"\"\"\n+    A preprocessing hook which ensures that any Plotly panes rendered\n+    inside Tabs are only visible when the tab they are in is active.\n+    This is a workaround for https://github.com/holoviz/panel/issues/804.\n+    \"\"\"\n+    from ..models.plotly import PlotlyPlot\n+\n+    tabs_models = list(root.select({'type': Tabs}))\n+    plotly_models = root.select({'type': PlotlyPlot})\n+\n+    for model in plotly_models:\n+        parent_tabs = []\n+        for tabs in tabs_models:\n+            if tabs.select_one({'id': model.id}):\n+                parent_tabs.append(tabs)\n+        parent_tab = None\n+        for tabs in parent_tabs:\n+            if not any(tabs.select_one(pt) for pt in parent_tabs if pt is not tabs):\n+                parent_tab = tabs\n+                break\n+        if parent_tab is None:\n+            return\n+        for i, tab in enumerate(parent_tab.tabs):\n+            if tab.select_one({'id': model.id}):\n+                break\n+        updated = False\n+        code = \"model.visible = cb_obj.active == i;\"\n+        for cb in parent_tab.js_property_callbacks.get('change:active', []):\n+            if cb.code == code and cb.args.get('model') is model:\n+                cb.args['i'] = i\n+                updated = True\n+        if updated:\n+            continue\n+        callback = CustomJS(args={'model': model, 'i': i, 'margin': model.margin}, code=code)\n+        parent_tab.js_on_change('active', callback)\n+        model.visible = parent_tab.active == i\n", "issue": "unexpected Interactivity of \"layered\" plotly objects in Tabs\nMy first one of these so apologies if it's missing info / already flagged.\r\n\r\n#### Software info:\r\npython: 3.6.9\r\nPanel = 0.6.2\r\nplotly = 4.2.1\r\nbokeh = 1.3.4\r\njupyter notebook server = 6.0.0\r\nbrowser: Google chrome (and same behavior in embedded html output)\r\nOS: Windows 8.1\r\n\r\n#### Description of expected behavior and the observed behavior\r\nBelow applies to within jupyter notebook, in browser window (.show()) and in html output (.save(embed = True))\r\n\r\n__Expected behavior:__ plotly objects within panel tabs to have same interactivity as when not in tabs.\r\n\r\n__observed behavior:__ Only plotly object in \"bottom level\" (last in list of tabs, \"fig2\" tab in example) retains full interactivity (pan, zoom, select, legend trace select etc.)). All other tab \"levels\" (tabs other than the last one in tab list, \"fig1\" tab inexample) retain only legend select interactivity. Interactions with the area bound by the axis (where a crosshair is seen) in \"fig1\" results in changes to the \"fig2\" plotly object.\r\n\r\n\r\n#### Complete, minimal, self-contained example code that reproduces the issue\r\n\r\n```\r\nimport plotly.graph_objs as go\r\nimport panel as pn\r\nimport numpy as np\r\npn.extension('plotly')\r\n\r\nx = np.linspace(0,10,100)\r\ny = np.sin(x)\r\ny2 = np.cos(x)\r\n\r\ndata1 = [go.Scatter(x = x, y = y, name = 'scatter sin(x)', mode=\"markers+lines\"),\r\n       go.Scatter(x = x, y = y2, name = 'scatter cos(x)', mode=\"markers+lines\")]\r\n\r\ndata2 = [go.Bar(x = x, y = y, name = 'bar sin(x)'),\r\n       go.Bar(x = x, y = y2, name = 'bar cos(x)')]\r\n\r\nfig1 = go.Figure(data = data1)\r\nfig2 = go.Figure(data = data2)\r\n\r\npn.Column('## App with plotly objects in tabs:',\r\n          'Loss of interactivity on \"fig1\" tab plotly object',\r\n         pn.Tabs(('fig1', pn.Pane(fig1)),\r\n                 ('fig2', pn.Pane(fig2)))\r\n         )\r\n\r\n```\r\n#### Screenshots of issue\r\n\r\n![Panel_Plotly_tabs_bug](https://user-images.githubusercontent.com/38754794/69160627-d1ae4580-0ae1-11ea-999d-5557841df206.png)\r\n\r\n\n", "before_files": [{"content": "\"\"\"\nDefines a PlotlyPane which renders a plotly plot using PlotlyPlot\nbokeh model.\n\"\"\"\nimport numpy as np\nimport param\n\nfrom bokeh.models import ColumnDataSource\nfrom pyviz_comms import JupyterComm\n\nfrom .base import PaneBase\nfrom ..util import isdatetime, lazy_load\nfrom ..viewable import Layoutable\n\n\n\nclass Plotly(PaneBase):\n    \"\"\"\n    Plotly panes allow rendering plotly Figures and traces.\n\n    For efficiency any array objects found inside a Figure are added\n    to a ColumnDataSource which allows using binary transport to sync\n    the figure on bokeh server and via Comms.\n    \"\"\"\n\n    click_data = param.Dict(doc=\"Click callback data\")\n\n    clickannotation_data = param.Dict(doc=\"Clickannotation callback data\")\n\n    config = param.Dict(doc=\"Config data\")\n\n    hover_data = param.Dict(doc=\"Hover callback data\")\n\n    relayout_data = param.Dict(doc=\"Relayout callback data\")\n\n    restyle_data = param.List(doc=\"Restyle callback data\")\n\n    selected_data = param.Dict(doc=\"Selected callback data\")\n\n    viewport = param.Dict(doc=\"Current viewport state\")\n\n    viewport_update_policy = param.Selector(default=\"mouseup\", doc=\"\"\"\n        Policy by which the viewport parameter is updated during user interactions.\n\n        * \"mouseup\": updates are synchronized when mouse button is\n          released after panning\n        * \"continuous\": updates are synchronized continually while panning\n        * \"throttle\": updates are synchronized while panning, at \n          intervals determined by the viewport_update_throttle parameter\n        \"\"\", objects=[\"mouseup\", \"continuous\", \"throttle\"])\n\n    viewport_update_throttle = param.Integer(default=200, bounds=(0, None), doc=\"\"\"\n        Time interval in milliseconds at which viewport updates are\n        synchronized when viewport_update_policy is \"throttle\".\"\"\")\n\n    _render_count = param.Integer(default=0, doc=\"\"\"\n        Number of renders, increment to trigger re-render\"\"\")\n\n    priority = 0.8\n\n    _updates = True\n\n    @classmethod\n    def applies(cls, obj):\n        return ((isinstance(obj, list) and obj and all(cls.applies(o) for o in obj)) or\n                hasattr(obj, 'to_plotly_json') or (isinstance(obj, dict)\n                                                   and 'data' in obj and 'layout' in obj))\n\n    def __init__(self, object=None, **params):\n        super().__init__(object, **params)\n        self._figure = None\n        self._update_figure()\n\n    def _to_figure(self, obj):\n        import plotly.graph_objs as go\n        if isinstance(obj, go.Figure):\n            return obj\n        elif isinstance(obj, dict):\n            data, layout = obj['data'], obj['layout']\n        elif isinstance(obj, tuple):\n            data, layout = obj\n        else:\n            data, layout = obj, {}\n        data = data if isinstance(data, list) else [data]\n        return go.Figure(data=data, layout=layout)\n\n    @staticmethod\n    def _get_sources(json):\n        sources = []\n        traces = json.get('data', [])\n        for trace in traces:\n            data = {}\n            Plotly._get_sources_for_trace(trace, data)\n            sources.append(ColumnDataSource(data))\n        return sources\n\n    @staticmethod\n    def _get_sources_for_trace(json, data, parent_path=''):\n        for key, value in list(json.items()):\n            full_path = key if not parent_path else (parent_path + '.' + key)\n            if isinstance(value, np.ndarray):\n                # Extract numpy array\n                data[full_path] = [json.pop(key)]\n            elif isinstance(value, dict):\n                # Recurse into dictionaries:\n                Plotly._get_sources_for_trace(value, data=data, parent_path=full_path)\n            elif isinstance(value, list) and value and isinstance(value[0], dict):\n                # recurse into object arrays:\n                for i, element in enumerate(value):\n                    element_path = full_path + '.' + str(i)\n                    Plotly._get_sources_for_trace(\n                        element, data=data, parent_path=element_path\n                    )\n\n    @param.depends('object', watch=True)\n    def _update_figure(self):\n        import plotly.graph_objs as go\n\n        if (self.object is None or\n                type(self.object) is not go.Figure or\n                self.object is self._figure):\n            return\n\n        # Monkey patch the message stubs used by FigureWidget.\n        # We only patch `Figure` objects (not subclasses like FigureWidget) so\n        # we don't interfere with subclasses that override these methods.\n        fig = self.object\n        fig._send_addTraces_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_moveTraces_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_deleteTraces_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_restyle_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_relayout_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_update_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_animate_msg = lambda *_, **__: self.param.trigger('object')\n        self._figure = fig\n\n    def _update_data_sources(self, cds, trace):\n        trace_arrays = {}\n        Plotly._get_sources_for_trace(trace, trace_arrays)\n\n        update_sources = False\n        for key, new_col in trace_arrays.items():\n            new = new_col[0]\n\n            try:\n                old = cds.data.get(key)[0]\n                update_array = (\n                    (type(old) != type(new)) or\n                    (new.shape != old.shape) or\n                    (new != old).any())\n            except Exception:\n                update_array = True\n\n            if update_array:\n                update_sources = True\n                cds.data[key] = [new]\n\n        return update_sources\n\n    @staticmethod\n    def _plotly_json_wrapper(fig):\n        \"\"\"Wraps around to_plotly_json and applies necessary fixes.\n\n        For #382: Map datetime elements to strings.\n        \"\"\"\n        json = fig.to_plotly_json()\n        data = json['data']\n\n        for idx in range(len(data)):\n            for key in data[idx]:\n                if isdatetime(data[idx][key]):\n                    arr = data[idx][key]\n                    if isinstance(arr, np.ndarray):\n                        arr = arr.astype(str) \n                    else:\n                        arr = [str(v) for v in arr]\n                    data[idx][key] = arr\n        return json\n\n    def _init_params(self):\n        viewport_params = [p for p in self.param if 'viewport' in p]\n        parameters = list(Layoutable.param)+viewport_params\n        params = {p: getattr(self, p) for p in parameters\n                  if getattr(self, p) is not None}\n\n        if self.object is None:\n            json, sources = {}, []\n        else:\n            fig = self._to_figure(self.object)\n            json = self._plotly_json_wrapper(fig)\n            sources = Plotly._get_sources(json)\n\n        params['_render_count'] = self._render_count\n        params['config'] = self.config or {}\n        params['data'] = json.get('data', [])\n        params['data_sources'] = sources\n        params['layout'] = layout = json.get('layout', {})\n        if layout.get('autosize') and self.sizing_mode is self.param.sizing_mode.default:\n            params['sizing_mode'] = 'stretch_both'\n        return params\n\n    def _get_model(self, doc, root=None, parent=None, comm=None):\n        PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot', isinstance(comm, JupyterComm))\n        model = PlotlyPlot(**self._init_params())\n        if root is None:\n            root = model\n        self._link_props(model, self._linkable_params, doc, root, comm)\n        self._models[root.ref['id']] = (model, parent)\n        return model\n\n    def _update(self, ref=None, model=None):\n        if self.object is None:\n            model.update(data=[], layout={})\n            model._render_count += 1\n            return\n\n        fig = self._to_figure(self.object)\n        json = self._plotly_json_wrapper(fig)\n        layout = json.get('layout')\n\n        traces = json['data']\n        new_sources = []\n        update_sources = False\n        for i, trace in enumerate(traces):\n            if i < len(model.data_sources):\n                cds = model.data_sources[i]\n            else:\n                cds = ColumnDataSource()\n                new_sources.append(cds)\n\n            update_sources = self._update_data_sources(cds, trace) or update_sources\n        try:\n            update_layout = model.layout != layout\n        except Exception:\n            update_layout = True\n\n        # Determine if model needs updates\n        if (len(model.data) != len(traces)):\n            update_data = True\n        else:\n            update_data = False\n            for new, old in zip(traces, model.data):\n                try:\n                    update_data = (\n                        {k: v for k, v in new.items() if k != 'uid'} !=\n                        {k: v for k, v in old.items() if k != 'uid'}\n                    )\n                except Exception:\n                    update_data = True\n                if update_data:\n                    break\n\n        updates = {}\n        if self.sizing_mode is self.param.sizing_mode.default and 'autosize' in layout:\n            autosize = layout.get('autosize')\n            if autosize and model.sizing_mode != 'stretch_both':\n                updates['sizing_mode'] = 'stretch_both'\n            elif not autosize and model.sizing_mode != 'fixed':\n                updates['sizing_mode'] = 'fixed'\n\n        if new_sources:\n            updates['data_sources'] = model.data_sources + new_sources\n\n        if update_data:\n            updates['data'] = json.get('data')\n\n        if update_layout:\n            updates['layout'] = layout\n\n        if updates:\n            model.update(**updates)\n\n        # Check if we should trigger rendering\n        if updates or update_sources:\n            model._render_count += 1\n", "path": "panel/pane/plotly.py"}], "after_files": [{"content": "\"\"\"\nDefines a PlotlyPane which renders a plotly plot using PlotlyPlot\nbokeh model.\n\"\"\"\nimport numpy as np\nimport param\n\nfrom bokeh.models import ColumnDataSource, CustomJS, Tabs\nfrom pyviz_comms import JupyterComm\n\nfrom .base import PaneBase\nfrom ..util import isdatetime, lazy_load\nfrom ..viewable import Layoutable, Viewable\n\n\n\nclass Plotly(PaneBase):\n    \"\"\"\n    Plotly panes allow rendering plotly Figures and traces.\n\n    For efficiency any array objects found inside a Figure are added\n    to a ColumnDataSource which allows using binary transport to sync\n    the figure on bokeh server and via Comms.\n    \"\"\"\n\n    click_data = param.Dict(doc=\"Click callback data\")\n\n    clickannotation_data = param.Dict(doc=\"Clickannotation callback data\")\n\n    config = param.Dict(doc=\"Config data\")\n\n    hover_data = param.Dict(doc=\"Hover callback data\")\n\n    relayout_data = param.Dict(doc=\"Relayout callback data\")\n\n    restyle_data = param.List(doc=\"Restyle callback data\")\n\n    selected_data = param.Dict(doc=\"Selected callback data\")\n\n    viewport = param.Dict(doc=\"Current viewport state\")\n\n    viewport_update_policy = param.Selector(default=\"mouseup\", doc=\"\"\"\n        Policy by which the viewport parameter is updated during user interactions.\n\n        * \"mouseup\": updates are synchronized when mouse button is\n          released after panning\n        * \"continuous\": updates are synchronized continually while panning\n        * \"throttle\": updates are synchronized while panning, at \n          intervals determined by the viewport_update_throttle parameter\n        \"\"\", objects=[\"mouseup\", \"continuous\", \"throttle\"])\n\n    viewport_update_throttle = param.Integer(default=200, bounds=(0, None), doc=\"\"\"\n        Time interval in milliseconds at which viewport updates are\n        synchronized when viewport_update_policy is \"throttle\".\"\"\")\n\n    _render_count = param.Integer(default=0, doc=\"\"\"\n        Number of renders, increment to trigger re-render\"\"\")\n\n    priority = 0.8\n\n    _updates = True\n\n    @classmethod\n    def applies(cls, obj):\n        return ((isinstance(obj, list) and obj and all(cls.applies(o) for o in obj)) or\n                hasattr(obj, 'to_plotly_json') or (isinstance(obj, dict)\n                                                   and 'data' in obj and 'layout' in obj))\n\n    def __init__(self, object=None, **params):\n        super().__init__(object, **params)\n        self._figure = None\n        self._update_figure()\n\n    def _to_figure(self, obj):\n        import plotly.graph_objs as go\n        if isinstance(obj, go.Figure):\n            return obj\n        elif isinstance(obj, dict):\n            data, layout = obj['data'], obj['layout']\n        elif isinstance(obj, tuple):\n            data, layout = obj\n        else:\n            data, layout = obj, {}\n        data = data if isinstance(data, list) else [data]\n        return go.Figure(data=data, layout=layout)\n\n    @staticmethod\n    def _get_sources(json):\n        sources = []\n        traces = json.get('data', [])\n        for trace in traces:\n            data = {}\n            Plotly._get_sources_for_trace(trace, data)\n            sources.append(ColumnDataSource(data))\n        return sources\n\n    @staticmethod\n    def _get_sources_for_trace(json, data, parent_path=''):\n        for key, value in list(json.items()):\n            full_path = key if not parent_path else (parent_path + '.' + key)\n            if isinstance(value, np.ndarray):\n                # Extract numpy array\n                data[full_path] = [json.pop(key)]\n            elif isinstance(value, dict):\n                # Recurse into dictionaries:\n                Plotly._get_sources_for_trace(value, data=data, parent_path=full_path)\n            elif isinstance(value, list) and value and isinstance(value[0], dict):\n                # recurse into object arrays:\n                for i, element in enumerate(value):\n                    element_path = full_path + '.' + str(i)\n                    Plotly._get_sources_for_trace(\n                        element, data=data, parent_path=element_path\n                    )\n\n    @param.depends('object', watch=True)\n    def _update_figure(self):\n        import plotly.graph_objs as go\n\n        if (self.object is None or\n                type(self.object) is not go.Figure or\n                self.object is self._figure):\n            return\n\n        # Monkey patch the message stubs used by FigureWidget.\n        # We only patch `Figure` objects (not subclasses like FigureWidget) so\n        # we don't interfere with subclasses that override these methods.\n        fig = self.object\n        fig._send_addTraces_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_moveTraces_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_deleteTraces_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_restyle_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_relayout_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_update_msg = lambda *_, **__: self.param.trigger('object')\n        fig._send_animate_msg = lambda *_, **__: self.param.trigger('object')\n        self._figure = fig\n\n    def _update_data_sources(self, cds, trace):\n        trace_arrays = {}\n        Plotly._get_sources_for_trace(trace, trace_arrays)\n\n        update_sources = False\n        for key, new_col in trace_arrays.items():\n            new = new_col[0]\n\n            try:\n                old = cds.data.get(key)[0]\n                update_array = (\n                    (type(old) != type(new)) or\n                    (new.shape != old.shape) or\n                    (new != old).any())\n            except Exception:\n                update_array = True\n\n            if update_array:\n                update_sources = True\n                cds.data[key] = [new]\n\n        return update_sources\n\n    @staticmethod\n    def _plotly_json_wrapper(fig):\n        \"\"\"Wraps around to_plotly_json and applies necessary fixes.\n\n        For #382: Map datetime elements to strings.\n        \"\"\"\n        json = fig.to_plotly_json()\n        data = json['data']\n\n        for idx in range(len(data)):\n            for key in data[idx]:\n                if isdatetime(data[idx][key]):\n                    arr = data[idx][key]\n                    if isinstance(arr, np.ndarray):\n                        arr = arr.astype(str) \n                    else:\n                        arr = [str(v) for v in arr]\n                    data[idx][key] = arr\n        return json\n\n    def _init_params(self):\n        viewport_params = [p for p in self.param if 'viewport' in p]\n        parameters = list(Layoutable.param)+viewport_params\n        params = {p: getattr(self, p) for p in parameters\n                  if getattr(self, p) is not None}\n\n        if self.object is None:\n            json, sources = {}, []\n        else:\n            fig = self._to_figure(self.object)\n            json = self._plotly_json_wrapper(fig)\n            sources = Plotly._get_sources(json)\n\n        params['_render_count'] = self._render_count\n        params['config'] = self.config or {}\n        params['data'] = json.get('data', [])\n        params['data_sources'] = sources\n        params['layout'] = layout = json.get('layout', {})\n        if layout.get('autosize') and self.sizing_mode is self.param.sizing_mode.default:\n            params['sizing_mode'] = 'stretch_both'\n        return params\n\n    def _get_model(self, doc, root=None, parent=None, comm=None):\n        PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot', isinstance(comm, JupyterComm))\n        model = PlotlyPlot(**self._init_params())\n        if root is None:\n            root = model\n        self._link_props(model, self._linkable_params, doc, root, comm)\n        self._models[root.ref['id']] = (model, parent)\n        if _patch_tabs_plotly not in Viewable._preprocessing_hooks:\n            Viewable._preprocessing_hooks.append(_patch_tabs_plotly)\n        return model\n\n    def _update(self, ref=None, model=None):\n        if self.object is None:\n            model.update(data=[], layout={})\n            model._render_count += 1\n            return\n\n        fig = self._to_figure(self.object)\n        json = self._plotly_json_wrapper(fig)\n        layout = json.get('layout')\n\n        traces = json['data']\n        new_sources = []\n        update_sources = False\n        for i, trace in enumerate(traces):\n            if i < len(model.data_sources):\n                cds = model.data_sources[i]\n            else:\n                cds = ColumnDataSource()\n                new_sources.append(cds)\n\n            update_sources = self._update_data_sources(cds, trace) or update_sources\n        try:\n            update_layout = model.layout != layout\n        except Exception:\n            update_layout = True\n\n        # Determine if model needs updates\n        if (len(model.data) != len(traces)):\n            update_data = True\n        else:\n            update_data = False\n            for new, old in zip(traces, model.data):\n                try:\n                    update_data = (\n                        {k: v for k, v in new.items() if k != 'uid'} !=\n                        {k: v for k, v in old.items() if k != 'uid'}\n                    )\n                except Exception:\n                    update_data = True\n                if update_data:\n                    break\n\n        updates = {}\n        if self.sizing_mode is self.param.sizing_mode.default and 'autosize' in layout:\n            autosize = layout.get('autosize')\n            if autosize and model.sizing_mode != 'stretch_both':\n                updates['sizing_mode'] = 'stretch_both'\n            elif not autosize and model.sizing_mode != 'fixed':\n                updates['sizing_mode'] = 'fixed'\n\n        if new_sources:\n            updates['data_sources'] = model.data_sources + new_sources\n\n        if update_data:\n            updates['data'] = json.get('data')\n\n        if update_layout:\n            updates['layout'] = layout\n\n        if updates:\n            model.update(**updates)\n\n        # Check if we should trigger rendering\n        if updates or update_sources:\n            model._render_count += 1\n\n\ndef _patch_tabs_plotly(viewable, root):\n    \"\"\"\n    A preprocessing hook which ensures that any Plotly panes rendered\n    inside Tabs are only visible when the tab they are in is active.\n    This is a workaround for https://github.com/holoviz/panel/issues/804.\n    \"\"\"\n    from ..models.plotly import PlotlyPlot\n\n    tabs_models = list(root.select({'type': Tabs}))\n    plotly_models = root.select({'type': PlotlyPlot})\n\n    for model in plotly_models:\n        parent_tabs = []\n        for tabs in tabs_models:\n            if tabs.select_one({'id': model.id}):\n                parent_tabs.append(tabs)\n        parent_tab = None\n        for tabs in parent_tabs:\n            if not any(tabs.select_one(pt) for pt in parent_tabs if pt is not tabs):\n                parent_tab = tabs\n                break\n        if parent_tab is None:\n            return\n        for i, tab in enumerate(parent_tab.tabs):\n            if tab.select_one({'id': model.id}):\n                break\n        updated = False\n        code = \"model.visible = cb_obj.active == i;\"\n        for cb in parent_tab.js_property_callbacks.get('change:active', []):\n            if cb.code == code and cb.args.get('model') is model:\n                cb.args['i'] = i\n                updated = True\n        if updated:\n            continue\n        callback = CustomJS(args={'model': model, 'i': i, 'margin': model.margin}, code=code)\n        parent_tab.js_on_change('active', callback)\n        model.visible = parent_tab.active == i\n", "path": "panel/pane/plotly.py"}]}