Panel: Hovering over Plotly plot raises DeserializationError

Created on 30 Oct 2020  路  5Comments  路  Source: holoviz/panel

ALL software version info

Panel 0.10.1
Bokek 2.2.3
Plotly 4.8.2 and 4.12.0
Windows 10
Chrome

Description of expected behavior and the observed behavior

I hover over Plotly plot. I would not expect exceptions raised but I do.

Additional Context

scoodood has also noted this on Discourse https://discourse.holoviz.org/t/this-panel-plotly-example-is-throwing-error/1363 for the Plotly Reference example.

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

"""
Scoodood is asking on [Discourse](https://discourse.holoviz.org/t/how-to-capture-the-click-event-on-plotly-plot-with-panel/1360)

How to capture the click event on Plotly plot with Panel?
"""

import numpy as np
from panel.template.react import ReactTemplate
import plotly.graph_objs as go
import panel as pn

pn.extension("plotly")
pn.config.sizing_mode = "stretch_width"


def create_plot():
    t = np.linspace(0, 10, 50)
    x, y, z = np.cos(t), np.sin(t), t
    fig = go.Figure(
        data=go.Scatter3d(x=x, y=y, z=z, mode="markers"), layout=dict(title="3D Scatter Plot")
    )
    fig.layout.autosize = True
    return fig


def create_layout(plot):
    description_panel = pn.layout.Card(
        __doc__, header="# How to capture Plotly Click Events?", sizing_mode="stretch_both"
    )
    plot_panel = pn.pane.Plotly(plot, config={"responsive": True}, sizing_mode="stretch_both")
    settings_panel = plot_panel.controls(jslink=True)

    template = ReactTemplate(title="Awesome Panel - Plotly App")
    template.sidebar.append(settings_panel)
    template.main[0, :] = description_panel
    template.main[1:4, :] = plot_panel
    return template


def create_app():
    plot = create_plot()
    return create_layout(plot)


app = create_app()
app.servable()

Stack traceback and/or browser JavaScript console output

$ python -m panel serve 'scripts\discourse_1360_plotly_clickevents.py' --dev
2020-10-30 19:25:22,434 Starting Bokeh server version 2.2.3 (running on Tornado 6.0.4)
2020-10-30 19:25:22,436 User authentication hooks NOT provided (default user enabled)
2020-10-30 19:25:22,439 Bokeh app running at: http://localhost:5006/discourse_1360_plotly_clickevents
2020-10-30 19:25:22,440 Starting Bokeh server with process id: 28196
2020-10-30 19:25:25,393 WebSocket connection opened
2020-10-30 19:25:25,394 ServerConnection created
2020-10-30 19:25:30,042 error handling message
 message: Message 'PATCH-DOC' content: {'events': [{'kind': 'ModelChanged', 'model': {'id': '1043'}, 'attr': 'hover_data', 'new': ''}], 'references': []}
 error: DeserializationError('Dict(String, Any) expected a dict or None, got ')
Traceback (most recent call last):
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\protocol_handler.py",
line 90, in handle
    work = await handler(message, connection)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 67,
in _needs_document_lock_wrapper
    result = func(self, *args, **kwargs)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 261, in _handle_patch
    message.apply_to_document(self.document, self)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in apply_to_document
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 1169, in _with_self_as_curdoc
    return f()
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in <lambda>
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 410, in apply_json_patch
    patched_obj.set_from_json(attr, value, models=references, setter=setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\has_props.py", line 345, in set_from_json
    descriptor.set_from_json(self, json, models, setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\descriptors.py", line 611, in set_from_json
    self.property.from_json(json, models),
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\container.py",
line 176, in from_json
    raise DeserializationError("%s expected a dict or None, got %s" % (self, json))
bokeh.core.property.bases.DeserializationError: Dict(String, Any) expected a dict or None, got
2020-10-30 19:25:30,048 error handling message
 message: Message 'PATCH-DOC' content: {'events': [{'kind': 'ModelChanged', 'model': {'id': '1043'}, 'attr': 'hover_data', 'new': ''}], 'references': []}
 error: DeserializationError('Dict(String, Any) expected a dict or None, got ')
Traceback (most recent call last):
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\protocol_handler.py",
line 90, in handle
    work = await handler(message, connection)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 67,
in _needs_document_lock_wrapper
    result = func(self, *args, **kwargs)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 261, in _handle_patch
    message.apply_to_document(self.document, self)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in apply_to_document
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 1169, in _with_self_as_curdoc
    return f()
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in <lambda>
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 410, in apply_json_patch
    patched_obj.set_from_json(attr, value, models=references, setter=setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\has_props.py", line 345, in set_from_json
    descriptor.set_from_json(self, json, models, setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\descriptors.py", line 611, in set_from_json
    self.property.from_json(json, models),
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\container.py",
line 176, in from_json
    raise DeserializationError("%s expected a dict or None, got %s" % (self, json))
bokeh.core.property.bases.DeserializationError: Dict(String, Any) expected a dict or None, got
2020-10-30 19:25:32,037 error handling message
 message: Message 'PATCH-DOC' content: {'events': [{'kind': 'ModelChanged', 'model': {'id': '1043'}, 'attr': 'hover_data', 'new': ''}], 'references': []}
 error: DeserializationError('Dict(String, Any) expected a dict or None, got ')
Traceback (most recent call last):
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\protocol_handler.py",
line 90, in handle
    work = await handler(message, connection)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 67,
in _needs_document_lock_wrapper
    result = func(self, *args, **kwargs)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 261, in _handle_patch
    message.apply_to_document(self.document, self)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in apply_to_document
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 1169, in _with_self_as_curdoc
    return f()
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in <lambda>
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 410, in apply_json_patch
    patched_obj.set_from_json(attr, value, models=references, setter=setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\has_props.py", line 345, in set_from_json
    descriptor.set_from_json(self, json, models, setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\descriptors.py", line 611, in set_from_json
    self.property.from_json(json, models),
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\container.py",
line 176, in from_json
    raise DeserializationError("%s expected a dict or None, got %s" % (self, json))
bokeh.core.property.bases.DeserializationError: Dict(String, Any) expected a dict or None, got
2020-10-30 19:25:32,044 error handling message
 message: Message 'PATCH-DOC' content: {'events': [{'kind': 'ModelChanged', 'model': {'id': '1043'}, 'attr': 'hover_data', 'new': ''}], 'references': []}
 error: DeserializationError('Dict(String, Any) expected a dict or None, got ')
Traceback (most recent call last):
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\protocol_handler.py",
line 90, in handle
    work = await handler(message, connection)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 67,
in _needs_document_lock_wrapper
    result = func(self, *args, **kwargs)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\server\session.py", line 261, in _handle_patch
    message.apply_to_document(self.document, self)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in apply_to_document
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 1169, in _with_self_as_curdoc
    return f()
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 100, in <lambda>
    doc._with_self_as_curdoc(lambda: doc.apply_json_patch(self.content, setter))
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\document\document.py", line 410, in apply_json_patch
    patched_obj.set_from_json(attr, value, models=references, setter=setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\has_props.py", line 345, in set_from_json
    descriptor.set_from_json(self, json, models, setter)
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\descriptors.py", line 611, in set_from_json
    self.property.from_json(json, models),
  File "C:\repos\private\awesome-panel\.venv\lib\site-packages\bokeh\core\property\container.py",
line 176, in from_json
    raise DeserializationError("%s expected a dict or None, got %s" % (self, json))
bokeh.core.property.bases.DeserializationError: Dict(String, Any) expected a dict or None, got

Screenshots or screencasts of the bug in action

plotly_click

bug

Most helpful comment

Not sure if it's the proper fix but changing the _process_param_change method of the panel LiteralInput widget by this:

import json
from six import string_types
def _process_param_change(self, msg):
        msg = super(pn.widgets.LiteralInput, self)._process_param_change(msg)
        if 'value' in msg:
            value = '' if msg['value'] is None else msg['value']
            if isinstance(value, string_types) and not self.serializer == 'json':
                value = repr(value)
            elif self.serializer == 'json':
                value = json.dumps(value, sort_keys=True) if value != '' else 'null'
            else:
                value = pn.util.as_unicode(value)
            msg['value'] = value
        msg['title'] = self.name
        return msg
pn.widgets.LiteralInput._process_param_change = _process_param_change

produce a coherent behavior:
Peek 01-11-2020 14-06

However as I'm not sure it's the correct fix I will not submit a PR

All 5 comments

hi @MarcSkovMadsen, thanks for bringing this up. This is how it looks in JupyterLab when I was trying to capture the clicked location (x,y,z) on the Plotly chart. I need this (x,y,z) location so that I could drive another chart.
You can see it is throwing error messages when I hover my mouse cursor over the data point. It's very hard to prototype my dashboard with Panel in JupyterLab.

import panel as pn
pn.extension('plotly')
import pandas as pd
import plotly.express as px

data = pd.DataFrame([
    ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),
    ('Friday', 4), ('Saturday', 4), ('Sunay', 4)], columns=['Day', 'Orders'])

fig = px.line(data, x="Day", y="Orders")
fig.update_traces(mode="lines+markers", marker=dict(size=10), line=dict(width=4))
fig.layout.autosize = True

responsive = pn.pane.Plotly(fig, config={'responsive': True})
pn.Column('# A responsive plot', responsive, sizing_mode='stretch_width')
pn.Row(responsive.controls(jslink=True), responsive)

out

I have found out that the error is related to "Hover data" giving empty strings, which the Bokeh Dict container can't handle. If I modify the container to set object with length 0 to None, I can get it to work without the DeserializationError. The function I'm changing to get it to work is bokeh/core/property/container.py - Dict.from_json()

    def from_json(self, json, models=None):
        if json is None or len(json) == 0:
            return None
        elif isinstance(json, dict):
            return { self.keys_type.from_json(key, models): self.values_type.from_json(value, models) for key, value in json.items() }
        else:
            raise DeserializationError("%s expected a dict or None, got %s" % (self, json))

It also look like Hover data is updating pretty frequently, so I think there is a more underlying problem.
output

Not sure if it's the proper fix but changing the _process_param_change method of the panel LiteralInput widget by this:

import json
from six import string_types
def _process_param_change(self, msg):
        msg = super(pn.widgets.LiteralInput, self)._process_param_change(msg)
        if 'value' in msg:
            value = '' if msg['value'] is None else msg['value']
            if isinstance(value, string_types) and not self.serializer == 'json':
                value = repr(value)
            elif self.serializer == 'json':
                value = json.dumps(value, sort_keys=True) if value != '' else 'null'
            else:
                value = pn.util.as_unicode(value)
            msg['value'] = value
        msg['title'] = self.name
        return msg
pn.widgets.LiteralInput._process_param_change = _process_param_change

produce a coherent behavior:
Peek 01-11-2020 14-06

However as I'm not sure it's the correct fix I will not submit a PR

Thanks for tracking that down. So this doesn't seem like an issue with the Plotly pane at all then but just the linked widget.

I think there is also another problem with the dict ordering, javascript objects or not key ordered whereas python dict are so event is triggered twice, but I don't know implications of dropping json.dumps sort_key

Was this page helpful?
0 / 5 - 0 ratings