Getting this error ValueError: failed to validate Line(id='p1043', ...).line_color: expected either None or a value of type Color

Hello

I am doing some realtime plotting and I ran into a problem. My actual code is far more complex than this but I have an MRE that shows the problem at the bottom of this post. When I run this code and click on the toggle button, I get errors like shown below. I only get this when deployed as a Server app. The odd thing is that the line color does change as I expect, it is just that I get this error which shows up in the browser Console and also the terminal.

What am I doing wrong? Thanks in advance!

Here is the error:

ERROR:bokeh.server.protocol_handler:error handling message
 message: Message 'PATCH-DOC' content: {'events': [{'kind': 'ModelChanged', 'model': {'id': 'p1043'}, 'attr': 'line_color', 'new': {'type': 'value', 'value': '#CC79A7'}}]}
 error: ValueError("failed to validate Line(id='p1043', ...).line_color: expected either None or a value of type Color, got Value(value='#CC79A7', transform=Unspecified, units=Unspecified)")
Traceback (most recent call last):
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/server/protocol_handler.py", line 94, in handle
    work = await handler(message, connection)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/server/session.py", line 94, in _needs_document_lock_wrapper
    result = func(self, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/server/session.py", line 286, in _handle_patch
    message.apply_to_document(self.document, self)
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/protocol/messages/patch_doc.py", line 104, in apply_to_document
    invoke_with_curdoc(doc, lambda: doc.apply_json_patch(self.payload, setter=setter))
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/document/callbacks.py", line 453, in invoke_with_curdoc
    return f()
           ^^^
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/protocol/messages/patch_doc.py", line 104, in <lambda>
    invoke_with_curdoc(doc, lambda: doc.apply_json_patch(self.payload, setter=setter))
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/document/document.py", line 391, in apply_json_patch
    DocumentPatchedEvent.handle_event(self, event, setter)
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/document/events.py", line 244, in handle_event
    event_cls._handle_event(doc, event)
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/document/events.py", line 376, in _handle_event
    model.set_from_json(attr, value, setter=event.setter)
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/core/has_props.py", line 448, in set_from_json
    descriptor.set_from_json(self, value, setter=setter)
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/core/property/descriptors.py", line 418, in set_from_json
    value = self.property.prepare_value(obj, self.name, value)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/hschilli/miniforge3/envs/openmdao/lib/python3.11/site-packages/bokeh/core/property/bases.py", line 363, in prepare_value
    raise ValueError(f"failed to validate {obj_repr}.{name}: {error}")
ValueError: failed to validate Line(id='p1043', ...).line_color: expected either None or a value of type Color, got Value(value='#CC79A7', transform=Unspecified, units=Unspecified)

Here is the code:

from bokeh.models import ColumnDataSource, Toggle, Row, CustomJS, SingleIntervalTicker
from bokeh.plotting import figure
from bokeh.server.server import Server
from bokeh.application.application import Application
from bokeh.application.handlers import FunctionHandler
import numpy as np

_time_between_callbacks_in_ms = 1000
varname = "distance"

class RealTimeOptPlot(object):
    def __init__(self, callback_period, doc):

        self.iteration = 0

        p = figure(title=f"Real-time Optimization Progress Plot")
        p.xaxis.ticker = SingleIntervalTicker(interval=1)

        _source_dict = {"iteration": []}
        _source_dict[varname] = []
        self._source = ColumnDataSource(_source_dict)

        line = p.line(
            x='iteration',
            y=varname,
            visible=True,
            color='black',
            source=self._source,
        )

        toggle = Toggle(
            label=varname,
            active=False,
        )

        callback_code=f"""
        line.glyph.line_color = '#CC79A7';
        """
        legend_item_callback = CustomJS(
            args=dict(line=line),
            code=callback_code,
        )
        toggle.js_on_change("active", legend_item_callback)

        def update():
            source_stream_dict = {"iteration": [self.iteration]}
            x = self.iteration
            y = np.sin(x) * np.exp(-0.1 * x)
            source_stream_dict[varname] = [y]

            self.iteration += 1
            self._source.stream(source_stream_dict)

        doc.add_periodic_callback(update, callback_period)
        graph = Row(p, toggle, sizing_mode="stretch_both")
        doc.add_root(graph)

def _make_realtime_opt_plot_doc(doc):
    RealTimeOptPlot(_time_between_callbacks_in_ms, doc=doc)

_port_number = 5000
try:
    server = Server(
        {"/": Application(FunctionHandler(_make_realtime_opt_plot_doc))},
        port=_port_number,
    )
    server.start()
    server.io_loop.add_callback(server.show, "/")

    print(f"Bokeh server running on http://localhost:{_port_number}")
    server.io_loop.start()
except KeyboardInterrupt as e:
    print(f"Server stopped due to keyboard interrupt")
except Exception as e:
    print(f"Error starting Bokeh server: {e}")
finally:
    print("Stopping server")
    if "server" in globals():
        server.stop()

@herbcle This seems like a bug, please open a GitHub Issue. It’s possible we can suggest a short-term WAR in the issue.

Submitted! Thanks

1 Like

I know you are super busy, but a short-term WAR would be great. I have many internal customers waiting for the plot I am creating. Thanks.

@herbcle I’d suggest asking on the issue, there are more core devs that will it there.