We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Panel transforms plotly timestamps to numbers
Create a plotly time series curve:
```python
import numpy as np
import pandas as pd
import panel as pn
import plotly.offline as py
import plotly.graph_objs as go

pn.extension("plotly")
py.init_notebook_mode()

index = pd.date_range(start="2019-01-01", end="2019-02-01", freq="1D")
data = np.random.randn(index.size)
traces = [go.Scatter(x=index, y=data)]
```
`py.iplot(traces)` correctly displays it:
![newplot](https://user-images.githubusercontent.com/22977572/56109036-1d4ab080-5f4f-11e9-8784-1d27bbb63dd8.png)
`pn.panel(traces)` converts the timestamps to numbers:
![newplot2](https://user-images.githubusercontent.com/22977572/56109062-34899e00-5f4f-11e9-968c-8c09489b0eb7.png)

Tested with panel 0.5.1 and plotly 3.7.1

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

{"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@@ -13,6 +13,7 @@\n import param\n \n from .base import PaneBase\n+from ..util import isdatetime\n \n \n class Plotly(PaneBase):\n@@ -153,6 +154,21 @@\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+                    data[idx][key].astype(str)\n+        return json\n+\n     def _get_model(self, doc, root=None, parent=None, comm=None):\n         \"\"\"\n         Should return the bokeh model to be rendered.\n@@ -172,7 +188,7 @@\n             json, sources = {}, []\n         else:\n             fig = self._to_figure(self.object)\n-            json = fig.to_plotly_json()\n+            json = self._plotly_json_wrapper(fig)\n             sources = Plotly._get_sources(json)\n         model = PlotlyPlot(data=json.get('data', []),\n                            layout=json.get('layout', {}),\n@@ -209,7 +225,7 @@\n             return\n \n         fig = self._to_figure(self.object)\n-        json = fig.to_plotly_json()\n+        json = self._plotly_json_wrapper(fig)\n \n         traces = json['data']\n         new_sources = []\n", "issue": "Panel transforms plotly timestamps to numbers\nCreate a plotly time series curve:\r\n```python\r\nimport numpy as np\r\nimport pandas as pd\r\nimport panel as pn\r\nimport plotly.offline as py\r\nimport plotly.graph_objs as go\r\n\r\npn.extension(\"plotly\")\r\npy.init_notebook_mode()\r\n\r\nindex = pd.date_range(start=\"2019-01-01\", end=\"2019-02-01\", freq=\"1D\")\r\ndata = np.random.randn(index.size)\r\ntraces = [go.Scatter(x=index, y=data)]\r\n```\r\n`py.iplot(traces)` correctly displays it:\r\n![newplot](https://user-images.githubusercontent.com/22977572/56109036-1d4ab080-5f4f-11e9-8784-1d27bbb63dd8.png)\r\n`pn.panel(traces)` converts the timestamps to numbers:\r\n![newplot2](https://user-images.githubusercontent.com/22977572/56109062-34899e00-5f4f-11e9-968c-8c09489b0eb7.png)\r\n\r\nTested with panel 0.5.1 and plotly 3.7.1\n", "before_files": [{"content": "\"\"\"\nDefines a PlotlyPane which renders a plotly plot using PlotlyPlot\nbokeh model.\n\"\"\"\nfrom __future__ import absolute_import, division, unicode_literals\n\nimport sys\n\nimport numpy as np\n\nfrom bokeh.models import ColumnDataSource\nfrom pyviz_comms import JupyterComm\nimport param\n\nfrom .base import PaneBase\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    config = param.Dict(doc=\"\"\"config data\"\"\")\n    relayout_data = param.Dict(doc=\"\"\"relayout callback data\"\"\")\n    restyle_data = param.List(doc=\"\"\"restyle callback data\"\"\")\n    click_data = param.Dict(doc=\"\"\"click callback data\"\"\")\n    hover_data = param.Dict(doc=\"\"\"hover callback data\"\"\")\n    clickannotation_data = param.Dict(doc=\"\"\"clickannotation callback data\"\"\")\n    selected_data = param.Dict(doc=\"\"\"selected callback data\"\"\")\n    viewport = param.Dict(doc=\"\"\"current viewport state\"\"\")\n    viewport_update_policy = param.Selector(\n        objects=[\"mouseup\", \"continuous\", \"throttle\"],\n        default=\"mouseup\",\n        doc=\"\"\"\\\nPolicy by which the viewport parameter is updated during user interactions:\n - \"mouseup\": updates are synchronized when mouse button is released after panning\n - \"continuous\": updates are synchronized continually while panning\n - \"throttle\": updates are synchronized while panning, at intervals determined by the\n               viewport_update_throttle parameter\"\"\"\n    )\n    viewport_update_throttle = param.Integer(\n        bounds=(0, None),\n        default=200,\n        doc='''\\\nTime interval in milliseconds at which viewport updates are synchronized when\nviewport_update_policy is \"throttle\"'''\n    )\n    _render_count = param.Integer(\n        doc=\"\"\"Number of renders, increment to trigger re-render\"\"\", default=0)\n\n    _updates = True\n\n    priority = 0.8\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(Plotly, self).__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:\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    def _get_model(self, doc, root=None, parent=None, comm=None):\n        \"\"\"\n        Should return the bokeh model to be rendered.\n        \"\"\"\n        if 'panel.models.plotly' not in sys.modules:\n            if isinstance(comm, JupyterComm):\n                self.param.warning('PlotlyPlot was not imported on instantiation '\n                                   'and may not render in a notebook. Restart '\n                                   'the notebook kernel and ensure you load '\n                                   'it as part of the extension using:'\n                                   '\\n\\npn.extension(\\'plotly\\')\\n')\n            from ..models.plotly import PlotlyPlot\n        else:\n            PlotlyPlot = getattr(sys.modules['panel.models.plotly'], 'PlotlyPlot')\n\n        if self.object is None:\n            json, sources = {}, []\n        else:\n            fig = self._to_figure(self.object)\n            json = fig.to_plotly_json()\n            sources = Plotly._get_sources(json)\n        model = PlotlyPlot(data=json.get('data', []),\n                           layout=json.get('layout', {}),\n                           config=self.config,\n                           viewport=self.viewport,\n                           viewport_update_policy=self.viewport_update_policy,\n                           viewport_update_throttle=self.viewport_update_throttle,\n                           data_sources=sources,\n                           _render_count=self._render_count)\n\n        if root is None:\n            root = model\n\n        self._link_props(\n            model, [\n                'config', 'relayout_data', 'restyle_data', 'click_data',  'hover_data',\n                'clickannotation_data', 'selected_data', 'viewport',\n                'viewport_update_policy', 'viewport_update_throttle', '_render_count'\n            ],\n            doc,\n            root,\n            comm\n        )\n\n        if root is None:\n            root = model\n        self._models[root.ref['id']] = (model, parent)\n        return model\n\n    def _update(self, model):\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 = fig.to_plotly_json()\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 != json.get('layout')\n        except:\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                except:\n                    update_data = True\n                if update_data:\n                    break\n\n        if new_sources:\n            model.data_sources += new_sources\n\n        if update_data:\n            model.data = json.get('data')\n\n        if update_layout:\n            model.layout = json.get('layout')\n\n        # Check if we should trigger rendering\n        if new_sources or update_sources or update_data or update_layout:\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\"\"\"\nfrom __future__ import absolute_import, division, unicode_literals\n\nimport sys\n\nimport numpy as np\n\nfrom bokeh.models import ColumnDataSource\nfrom pyviz_comms import JupyterComm\nimport param\n\nfrom .base import PaneBase\nfrom ..util import isdatetime\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    config = param.Dict(doc=\"\"\"config data\"\"\")\n    relayout_data = param.Dict(doc=\"\"\"relayout callback data\"\"\")\n    restyle_data = param.List(doc=\"\"\"restyle callback data\"\"\")\n    click_data = param.Dict(doc=\"\"\"click callback data\"\"\")\n    hover_data = param.Dict(doc=\"\"\"hover callback data\"\"\")\n    clickannotation_data = param.Dict(doc=\"\"\"clickannotation callback data\"\"\")\n    selected_data = param.Dict(doc=\"\"\"selected callback data\"\"\")\n    viewport = param.Dict(doc=\"\"\"current viewport state\"\"\")\n    viewport_update_policy = param.Selector(\n        objects=[\"mouseup\", \"continuous\", \"throttle\"],\n        default=\"mouseup\",\n        doc=\"\"\"\\\nPolicy by which the viewport parameter is updated during user interactions:\n - \"mouseup\": updates are synchronized when mouse button is released after panning\n - \"continuous\": updates are synchronized continually while panning\n - \"throttle\": updates are synchronized while panning, at intervals determined by the\n               viewport_update_throttle parameter\"\"\"\n    )\n    viewport_update_throttle = param.Integer(\n        bounds=(0, None),\n        default=200,\n        doc='''\\\nTime interval in milliseconds at which viewport updates are synchronized when\nviewport_update_policy is \"throttle\"'''\n    )\n    _render_count = param.Integer(\n        doc=\"\"\"Number of renders, increment to trigger re-render\"\"\", default=0)\n\n    _updates = True\n\n    priority = 0.8\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(Plotly, self).__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:\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                    data[idx][key].astype(str)\n        return json\n\n    def _get_model(self, doc, root=None, parent=None, comm=None):\n        \"\"\"\n        Should return the bokeh model to be rendered.\n        \"\"\"\n        if 'panel.models.plotly' not in sys.modules:\n            if isinstance(comm, JupyterComm):\n                self.param.warning('PlotlyPlot was not imported on instantiation '\n                                   'and may not render in a notebook. Restart '\n                                   'the notebook kernel and ensure you load '\n                                   'it as part of the extension using:'\n                                   '\\n\\npn.extension(\\'plotly\\')\\n')\n            from ..models.plotly import PlotlyPlot\n        else:\n            PlotlyPlot = getattr(sys.modules['panel.models.plotly'], 'PlotlyPlot')\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        model = PlotlyPlot(data=json.get('data', []),\n                           layout=json.get('layout', {}),\n                           config=self.config,\n                           viewport=self.viewport,\n                           viewport_update_policy=self.viewport_update_policy,\n                           viewport_update_throttle=self.viewport_update_throttle,\n                           data_sources=sources,\n                           _render_count=self._render_count)\n\n        if root is None:\n            root = model\n\n        self._link_props(\n            model, [\n                'config', 'relayout_data', 'restyle_data', 'click_data',  'hover_data',\n                'clickannotation_data', 'selected_data', 'viewport',\n                'viewport_update_policy', 'viewport_update_throttle', '_render_count'\n            ],\n            doc,\n            root,\n            comm\n        )\n\n        if root is None:\n            root = model\n        self._models[root.ref['id']] = (model, parent)\n        return model\n\n    def _update(self, model):\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\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 != json.get('layout')\n        except:\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                except:\n                    update_data = True\n                if update_data:\n                    break\n\n        if new_sources:\n            model.data_sources += new_sources\n\n        if update_data:\n            model.data = json.get('data')\n\n        if update_layout:\n            model.layout = json.get('layout')\n\n        # Check if we should trigger rendering\n        if new_sources or update_sources or update_data or update_layout:\n            model._render_count += 1\n", "path": "panel/pane/plotly.py"}]}