def test_get_modelid_messagename_type(): class FakeVal(): def __init__(self, v): self.value = v class FakeDoc(): def __init__(): pass def get_model_by_name(arg): if arg == MODEL_SELECTION: return FakeVal("protobuf_amazing_model") if arg == MESSAGE_SELECTION: return FakeVal("amazing_message") assert ("amazing_model", "amazing_message", "protobuf") == get_modelid_messagename_type(FakeDoc) class FakeDoc2(): def __init__(): pass def get_model_by_name(arg): if arg == MODEL_SELECTION: return FakeVal("topic_amazing_model") assert ("amazing_model", "amazing_model_messages", "jsonschema") == get_modelid_messagename_type(FakeDoc2)
def _install_callback_and_cds(sind, model_id, message_name, field_transforms={}, stream_limit=None): """ Set up a new column_data_source, install a callback to update it If it already exists do nothing """ d = curdoc() _remove_callback(d) model_id, message_name, model_type = run_handlers.get_modelid_messagename_type( d) emptyd = { k: [] for k in run_handlers.get_model_properties(model_id, message_name, model_type) } if d.get_model_by_name(sind) is None: d.add_root(ColumnDataSource(emptyd, name=sind, tags=[0])) func = partial(_bokeh_periodic_update, sind, model_id, message_name, field_transforms, stream_limit) global _last_callback _last_callback = func d.add_periodic_callback(func, CBF) _logger.debug( "_install_callback_and_cds: callback {0} added for sind {1}".format( func, sind))
def _bokeh_periodic_update(sind, model_id, message_name, field_transforms={}, stream_limit=None): """ Callback that gets called periodically *for each session*. That is, each session (user connecting via browser) will register a callback of this for their session. Here we will update the data source with new points https://bokeh.pydata.org/en/latest/docs/reference/models/sources.html field_transforms is a dict {k : [func, kwargs]} where func(k, **kwargs) will be applied for all k in the raw data before going into the column_data_source PLEASE READ ABOUT DATA REDUNDANCY: https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/m91Y2La6fS0 """ d = curdoc() column_data_source = d.get_model_by_name(sind) seq = column_data_source.tags[0] # _logger.debug("_bokeh_periodic_update: model_id {0}, message {1}, seq {2}".format(model_id, message_name, seq)) model_id, message_name, model_type = run_handlers.get_modelid_messagename_type( d) source = data.get_raw_data(model_id, message_name, seq, -1) # _logger.debug("_bokeh_periodic_update: source length {0}".format(len(source))) if source != []: # might be no data, exit callback immediately if so # this has all the properties, even ones we don't want to display # these keys may be dotted pairs sinit = { k: [] for k in run_handlers.get_model_properties(model_id, message_name, model_type) } # _logger.debug("_bokeh_periodic_update: sinit {0}".format(sinit)) newdata = sinit num_data = 0 for msg in source: # from where we left off to the end for mk in sinit.keys(): val = get_message_data(msg, mk) if val is None: pass # Ignore. For example, model_as_string property is defined but not pushed to Redis. # _logger.warning("_bokeh_periodic_update: failed to get value from message {0}, field {1}".format(message_name, mk)) elif mk in field_transforms: val = field_transforms[mk][0](val, **field_transforms[mk][1]) if isinstance(val, bytes): # this can happen in rare cases, like RAW being used to try to display an image # bokeh internally does a JSON serialization so we can't let bytes slip through # this does not affect image rendering is that is not put into the bokeh CDS, only the URL is val = "<RAW BYTES>" newdata[mk].append(val) num_data += 1 # Warning, don't check newdata = sinit because it's not a deep copy!!!!!11! d.get_model_by_name(sind).stream( newdata, stream_limit ) # after the data source is updated, some magic happens such that the new data is streamed via web socket to the browser column_data_source.tags = [seq + num_data]
def image_selection_change(): """Callback for changing the image field or mime field""" def return_image(val, model_id, message_name, field_name, mime, sind): """Returns a URL resolvable by the probe""" column_data_source = curdoc().get_model_by_name(sind) index = column_data_source.tags[0] url = "http://{0}/image/".format(_host) + "---".join( [model_id, message_name, field_name, mime, sind, str(index)]) return url d = curdoc() _remove_fig(d) model_id, message_name, _ = run_handlers.get_modelid_messagename_type(d) image_field = d.get_model_by_name(IMAGE_SELECTION).value.split(" :")[0] mime = d.get_model_by_name(MIME_SELECTION).value if image_field != DEFAULT_UNSELECTED and mime != DEFAULT_UNSELECTED: plot = figure(plot_width=500, plot_height=500, title="", x_range=Range1d(start=0, end=1), y_range=Range1d(start=0, end=1), name=FIGURE_MODEL) sind = run_handlers.get_source_index(d.session_context.id, model_id, message_name, image_field + mime) _install_callback_and_cds(sind, model_id, message_name, { image_field: [ return_image, { "model_id": model_id, "message_name": message_name, "field_name": image_field, "mime": mime, "sind": sind } ] }, stream_limit=1) plot.image_url(url=image_field, x=0, y=1, h=1, w=1, source=d.get_model_by_name(sind)) d.add_root(plot)
def column_selection_change(): """Creates a datatable when user changes the selected table column(s)""" d = curdoc() _remove_fig(d) model_id, message_name, _ = run_handlers.get_modelid_messagename_type(d) sind = run_handlers.get_source_index(d.session_context.id, model_id, message_name) source = d.get_model_by_name(sind) _install_callback_and_cds(sind, model_id, message_name, stream_limit=1) sel_cols = d.get_model_by_name(COLUMN_MULTISELECT).value columns = [TableColumn(field=c, title=c) for c in sel_cols] data_table = DataTable(source=source, columns=columns, width=500, height=500) table_widget = widgetbox(data_table, name=FIGURE_MODEL) d.add_root(table_widget)
def make_2axis_graph(): """Makes a 2 axis graph when user changes the X or Y axis selection""" d = curdoc() _remove_fig(d) graph_val = d.get_model_by_name(GRAPH_SELECTION).value model_id, message_name, _ = run_handlers.get_modelid_messagename_type(d) xval = d.get_model_by_name(X_AXIS_SELECTION).value yval = d.get_model_by_name(Y_AXIS_SELECTION).value if xval != DEFAULT_UNSELECTED and yval != DEFAULT_UNSELECTED: plot = figure(plot_width=400, plot_height=400, name=FIGURE_MODEL) sind = run_handlers.get_source_index(d.session_context.id, model_id, message_name) _install_callback_and_cds(sind, model_id, message_name, stream_limit=100000) # get the field name back from the pretty field : meta string formed above x = xval.split(" :")[0] y = yval.split(" :")[0] if graph_val == "line": plot.line(x=x, y=y, color="firebrick", line_width=2, source=d.get_model_by_name(sind)) plot.x_range.follow = "end" # don't jam all the data into the graph; "window" it plot.x_range.follow_interval = 100 plot.x_range.range_padding = 0 if graph_val == "scatter": plot.cross(x=x, y=y, size=20, color="firebrick", line_width=2, source=d.get_model_by_name(sind)) if graph_val == "step": plot.step(x=x, y=y, color="#FB8072", source=d.get_model_by_name(sind)) d.add_root(plot)
def modelselec_change(): """Callback on change of selected model/topic""" d = curdoc() _remove_fig(d) _remove_selection(d) d.remove_root(d.get_model_by_name(AFTER_MODEL_SELECTION)) modelselec = d.get_model_by_name(MODEL_SELECTION) msv = modelselec.value if msv != DEFAULT_UNSELECTED: model_id, message_name, model_type = run_handlers.get_modelid_messagename_type( d) if model_type == "protobuf": message = Select( title="Message Selection", value=DEFAULT_UNSELECTED, options=list( data.proto_data_structure[model_id]["messages"].keys()) + [DEFAULT_UNSELECTED], name=MESSAGE_SELECTION) graphs = Select(title="Graph Selection", value=DEFAULT_UNSELECTED, options=[], name=GRAPH_SELECTION) message.on_change('value', lambda attr, old, new: message_change()) graphs.on_change('value', lambda attr, old, new: graphs_change()) selec = row(widgetbox([message]), widgetbox([graphs]), name=AFTER_MODEL_SELECTION) else: # there is no message selection here graphs = Select(title="Graph Selection", value=DEFAULT_UNSELECTED, options=GRAPH_OPTIONS, name=GRAPH_SELECTION) graphs.on_change('value', lambda attr, old, new: graphs_change()) selec = row(widgetbox([graphs]), name=AFTER_MODEL_SELECTION) d.add_root(selec) d.add_root(selec)
def graphs_change(): """Callback on change of selected graph type""" d = curdoc() _remove_fig(d) _remove_selection(d) graph_val = d.get_model_by_name(GRAPH_SELECTION).value model_id, message_name, model_type = run_handlers.get_modelid_messagename_type( d) props = run_handlers.get_model_properties(model_id, message_name, model_type) if graph_val in ["line", "scatter", "step"]: # never want to plot this special string field field_options = [ "{0} : {1}".format(k, props[k]) for k in props if not any(apv in k for apv in [APV_MODEL]) ] xselect = Select(title="X Axis", value=DEFAULT_UNSELECTED, options=field_options + [DEFAULT_UNSELECTED], name=X_AXIS_SELECTION) yselect = Select(title="Y Axis", value=DEFAULT_UNSELECTED, options=field_options + [DEFAULT_UNSELECTED], name=Y_AXIS_SELECTION) xselect.on_change('value', lambda attr, old, new: make_2axis_graph()) yselect.on_change('value', lambda attr, old, new: make_2axis_graph()) d.add_root( column(Div(text=""), row(widgetbox([xselect]), widgetbox([yselect])), name=FIELD_SELECTION)) if graph_val in ["image"]: # alter the field options for known non-image fields field_options = [ "{0} : {1}".format(k, props[k]) for k in props if not any(apv in k for apv in [APV_RECVD, APV_SEQNO, APV_MODEL]) ] imageselect = Select(title="Image Field", value=DEFAULT_UNSELECTED, options=[DEFAULT_UNSELECTED] + field_options, name=IMAGE_SELECTION) mimeselect = Select(title="MIME Type", value=DEFAULT_UNSELECTED, options=[DEFAULT_UNSELECTED] + SUPPORTED_MIME_TYPES, name=MIME_SELECTION) imageselect.on_change('value', lambda attr, old, new: image_selection_change()) mimeselect.on_change('value', lambda attr, old, new: image_selection_change()) d.add_root( column(Div(text=""), widgetbox([imageselect, mimeselect]), name=IMAGE_MIME_SELECTION)) if graph_val in ["table"]: # TODO: limit selectable columns to whose of the same size (table height) # use just the field name; don't show properties in the multi-select box col_options = [ k for k in props if not any(apv in k for apv in [APV_RECVD, APV_SEQNO, APV_MODEL]) ] columnmultiselect = MultiSelect(title="Columns:", value=[], options=col_options, name=COLUMN_MULTISELECT) columnmultiselect.on_change( 'value', lambda attr, old, new: column_selection_change()) d.add_root( column(Div(text=""), widgetbox([columnmultiselect]), name=COLUMN_SELECTION)) if graph_val in ["raw"]: p = figure(plot_width=500, plot_height=500, background_fill_color="white", y_range=(-40, 0), title="", name=FIGURE_MODEL) p.xaxis.visible = False p.yaxis.visible = False sind = run_handlers.get_source_index(d.session_context.id, model_id, message_name) _install_callback_and_cds(sind, model_id, message_name, stream_limit=1) p.text(x='apv_sequence_number', y=0, text='apv_model_as_string', source=d.get_model_by_name(sind), text_font_size="10pt", text_line_height=0.7, text_baseline="top", text_align="left") p.x_range.follow = "end" # don't jam all the data into the graph; "window" it p.x_range.follow_interval = 1 # don't jam all the data into the graph; "window" it p.x_range.range_padding = 0 d.add_root(p)