def plot(tables, output_filename): ''' This is the plot function that uses Bokeh functions and widgets to make an interactive hexagon plot. This function recieves: - tables: dictionary with tables used to create arrays of repeated x, y coordinates (depending on the counts) for the hexagon plot. - output_filename: filename of .html output in the plots folder The coordinate arrays are used to create a pandas dataframe with Bokeh functions. This dataframe contains the q, r coordinates and counts used to plot the hexagons. To this dataframe, extra information is added (e.g. most common chemicals), which is displayed in the hover tooltip. Gaussian blur is added to copies of this dataframe and given as input to the Bokeh slider widget. Other widgets are added as well, for saturation, normalisation etc. Bokeh allows to customize these widges with javascript code. The hexagon plot is saved as a .html file and also shown in the browser. ''' file_name = 'plots/' + str(output_filename) + '.html' output_file(file_name) # Blur and saturation values BLUR_MAX = 3 BLUR_STEP_SIZE = 1 SATURATION_MAX = 5 SATURATION_STEP_SIZE = 0.25 # First, create array for plot properties ( ratio, size of hexagons etc.) default_term = list(tables.keys())[0] x, y, ids = create_array(tables[default_term]['table'], normalisation=False) # Hexagon plot properties length = len(x) orientation = 'flattop' ratio = ((max(y) - min(y)) / (max(x) - min(x))) size = 10 / ratio h = sqrt(3) * size h = h * ratio title = 'Hexbin plot for ' + str( length) + ' annotated chemicals with query ' + str(default_term) # make figure p = figure(title=title, x_range=[min(x) - 0.5, max(x) + 0.5], y_range=[0 - (h / 2), max(y) + 100], tools="wheel_zoom,reset,save", background_fill_color='#440154') p.grid.visible = False p.xaxis.axis_label = "log(P)" p.yaxis.axis_label = "mass in Da" p.xaxis.axis_label_text_font_style = 'normal' p.yaxis.axis_label_text_font_style = 'normal' # source for plot term_to_source, term_to_metadata, options = make_plot_sources( tables, size, ratio, orientation, BLUR_MAX, BLUR_STEP_SIZE) # start source for plot, this is the source that is first displayed in the hexagon figure x, y, ids = create_array(tables[default_term]['table'], normalisation=False) df = hexbin(x, y, ids, size, aspect_scale=ratio, orientation=orientation) df = add_counts(df, tables[default_term]['table']) source = ColumnDataSource(df) metadata = term_to_metadata[default_term] metadata = return_html(metadata) # color mapper mapper = linear_cmap('scaling', 'Viridis256', 0, max(source.data['scaling'])) # plot hex = p.hex_tile(q="q", r="r", size=size, line_color=None, source=source, aspect_scale=ratio, orientation=orientation, fill_color=mapper) # HOVER TOOLTIPS = return_tooltip() code_callback_hover = return_code('hover') callback_hover = CustomJS(code=code_callback_hover) hover = HoverTool(tooltips=TOOLTIPS, callback=callback_hover, show_arrow=False) p.add_tools(hover) # WIDGETS slider1 = Slider(start=1, end=SATURATION_MAX, value=1, step=SATURATION_STEP_SIZE, title="Saturation", width=100) slider2 = Slider(start=0, end=BLUR_MAX, value=0, step=BLUR_STEP_SIZE, title="Blur", width=100) checkbox = CheckboxGroup(labels=["TFIDF"], active=[]) radio_button_group = RadioGroup(labels=["Viridis256", "Greys256"], active=0) button = Button(label="Metadata", button_type="default", width=100) multi_select = MultiSelect(title=output_filename, value=[default_term], options=options, width=100, height=300) # WIDGETS CODE FOR CALLBACK code_callback_slider1 = return_code('slider1') code_callback_slider2 = return_code('slider2') code_callback_checkbox = return_code('checkbox') code_callback_rbg = return_code('rbg') code_callback_button = return_code('button') code_callback_ms = return_code('multi_select') # WIDGETS CALLBACK callback_slider1 = CustomJS(args={ 'source': source, 'mapper': mapper }, code=code_callback_slider1) callback_slider2 = CustomJS(args={ 'source': source, 'mapper': mapper, 'slider1': slider1, 'multi_select': multi_select, 'checkbox': checkbox, 'term_to_source': term_to_source, 'step_size': BLUR_STEP_SIZE }, code=code_callback_slider2) callback_checkbox = CustomJS(args={ 'source': source, 'term_to_source': term_to_source, 'multi_select': multi_select, 'step_size': BLUR_STEP_SIZE, 'slider1': slider1, 'slider2': slider2, 'mapper': mapper }, code=code_callback_checkbox) callback_radio_button_group = CustomJS(args={ 'p': p, 'mapper': mapper, 'Viridis256': Viridis256, 'Greys256': Greys256 }, code=code_callback_rbg) callback_button = CustomJS(args={ 'term_to_metadata': term_to_metadata, 'multi_select': multi_select }, code=code_callback_button) callback_ms = CustomJS(args={ 'source': source, 'term_to_source': term_to_source, 'checkbox': checkbox, 'metadata': metadata, 'step_size': BLUR_STEP_SIZE, 'slider2': slider2, 'slider1': slider1, 'p': p, 'mapper': mapper }, code=code_callback_ms) # # WIDGETS INTERACTION slider1.js_on_change('value', callback_slider1) slider2.js_on_change('value', callback_slider2) checkbox.js_on_change('active', callback_checkbox) radio_button_group.js_on_change('active', callback_radio_button_group) button.js_on_event(events.ButtonClick, callback_button) multi_select.js_on_change("value", callback_ms) # LAYOUT layout = row( multi_select, p, column(slider1, slider2, checkbox, radio_button_group, button)) show(layout)
def build_graph_plot(G, title=""): """ Return a Bokeh plot of the given networkx graph Parameters ---------- G: :obj:`networkx.Graph` Networkx graph instance to be plotted. title: str Title of the final plot Returns ------- :obj:`bokeh.models.plot` Bokeh plot of the graph. """ plot = Plot(plot_width=600, plot_height=450, x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1)) plot.title.text = title node_attrs = {} for node in G.nodes(data=True): node_color = Spectral4[node[1]['n']] node_attrs[node[0]] = node_color nx.set_node_attributes(G, node_attrs, "node_color") node_hover_tool = HoverTool(tooltips=[("Label", "@label"), ("n", "@n")]) wheelZoom = WheelZoomTool() plot.add_tools(node_hover_tool, PanTool(), wheelZoom, ResetTool()) plot.toolbar.active_scroll = wheelZoom graph_renderer = from_networkx(G, nx.spring_layout, k=0.3, iterations=200, scale=1, center=(0, 0)) graph_renderer.node_renderer.glyph = Circle(size=15, fill_color="node_color") graph_renderer.edge_renderer.glyph = MultiLine(line_alpha=0.8, line_width=1) plot.renderers.append(graph_renderer) selectCallback = CustomJS(args=dict(graph_renderer=graph_renderer), code=""" let new_data_nodes = Object.assign({},graph_renderer.node_renderer.data_source.data); new_data_nodes['node_color'] = {}; let colors = ['#2b83ba','#ABDDA4','#fdae61']; let ns = cb_obj.value.reduce((acc,v)=>{ if(v=='fullGraph'){ acc.push(0); acc.push(1); acc.push(2); } if(v=='n0')acc.push(0); if(v=='n1')acc.push(1); if(v=='n2')acc.push(2); return acc; },[]) Object.keys(graph_renderer.node_renderer.data_source.data['node_color']).map((n,i)=>{ new_data_nodes['node_color'][i]='transparent'; }) ns.map(n=>{ Object.keys(graph_renderer.node_renderer.data_source.data['node_color']).map((g,i)=>{ if(graph_renderer.node_renderer.data_source.data['n'][i]==n){ new_data_nodes['node_color'][i]=colors[n]; } }) }) graph_renderer.node_renderer.data_source.data = new_data_nodes """) multi_select = MultiSelect(title="Option:", options=[("fullGraph", "Full Graph"), ("n0", "Seed Nodes"), ("n1", "N1"), ("n2", "N2")]) multi_select.js_on_change('value', selectCallback) return column(plot, multi_select)
import panel as pn from bokeh.models import MultiSelect, CustomJS from pcp.models.multiselect import PCPMultiSelect from bokeh.sampledata.airports import data options = [(str(i), str(n)) for i, n in zip(data["name"].index, data["name"])] m1 = PCPMultiSelect(options=options, height=80, width=400) m2 = MultiSelect(options=options) t = pn.widgets.Toggle(name="search") t.jslink(m1, value="searchbox") # m1.on_change("value", lambda attr, old, new: setattr(m2,"value", new)) cb1 = CustomJS(args=dict(m2=m2), code="m2.value = cb_obj.value") m1.js_on_change("value", cb1) m1.on_change("value", lambda attr, old, new: print("m1", old, new)) cb2 = CustomJS(args=dict(m1=m1), code="m1.value = cb_obj.value") # m2.on_change("value", lambda attr, old, new: setattr(m1,"value", new)) m2.js_on_change("value", cb2) m2.on_change("value", lambda attr, old, new: print("m2", old, new)) pn.Row(m1, m2, t).show()
def plot(tables, output_filename, xmin, xmax, ymin, ymax, superterm): ''' This is the plot function that uses Bokeh functions and widgets to make an interactive hexagon plot. This function recieves: - tables: dictionary with tables used to create arrays of repeated x, y coordinates (depending on the counts) for the hexagon plot. - output_filename: filename of .html output in the plots folder The coordinate arrays are used to create a pandas dataframe with Bokeh functions. This dataframe contains the q, r coordinates and counts used to plot the hexagons. To this dataframe, extra information is added (e.g. most common chemicals), which is displayed in the hover tooltip. Gaussian blur is added to copies of this dataframe and given as input to the Bokeh slider widget. Other widgets are added as well, for saturation, normalization etc. Bokeh allows to customize these widges with javascript code. The hexagon plot is saved as a .html file and also shown in the browser. ''' file_name = 'plots/' + str(output_filename) + '.html' output_file(file_name) # Blur and saturation values BLUR_MAX = 4 BLUR_STEP_SIZE = 0.25 SATURATION_MAX = 5 SATURATION_STEP_SIZE = 0.25 # Hexagon plot properties SIZE_HEXAGONS = 10 orientation = 'flattop' #bokeh alows 2 different hexagon orientations which also influences hexagon size calculations, but we currently have only calculated blur distances for this orientation ratio = ((ymax - ymin) / (xmax - xmin)) size = SIZE_HEXAGONS / ratio hexagon_height = sqrt(3) * size hexagon_height = hexagon_height * ratio # make figure p = figure(x_range=[xmin, xmax], y_range=[ymin - (hexagon_height / 2), ymax], tools="wheel_zoom,reset,save", background_fill_color='#440154') p.grid.visible = False p.xaxis.axis_label = "log(P)" p.yaxis.axis_label = "mass in Da" p.xaxis.axis_label_text_font_style = 'normal' p.yaxis.axis_label_text_font_style = 'normal' # term_to_source, term_to_metadata, options = make_plot_sources(tables, size, ratio, orientation, BLUR_MAX, BLUR_STEP_SIZE) # source for widgets term_to_source = dict() term_to_class = dict() term_to_metadata = dict() options = [] for term in tables.keys(): options.append((term, term)) table = tables[term]['table'] if superterm: source = create_class_source(table, term, size, ratio, orientation, superterm) term_to_class[term] = {} term_to_class[term]['show_class'] = True term_to_class[term]['source'] = source else: term_to_class[term] = {'show_class': False} source, title = create_data_source(table, term, size, ratio, orientation, BLUR_MAX, BLUR_STEP_SIZE) metadata = return_html(tables[term]['metadata']) term_to_source[term] = {'source': source, 'title': title} term_to_metadata[term] = metadata # hex = p.hex_tile(q='q', r="r", size=size, line_color=None, source=source, aspect_scale=ratio,orientation=orientation, # fill_color='pink' ) # show(p) # make default souce for plot, this is the first source shown in the plot, and also works like a container. Old data is thrown out and new data is thrown in. default_term = list(tables.keys())[0] # pick the first one metadata = tables[default_term]['metadata'] metadata = return_html(metadata) table = tables[default_term]['table'] source, title = create_data_source(table, default_term, size, ratio, orientation, BLUR_MAX, BLUR_STEP_SIZE) p.title.text = title # color mapper mapper = linear_cmap('scaling', 'Viridis256', 0, max(source.data['scaling'])) # plot hex = p.hex_tile(q="q", r="r", size=size, line_color=None, source=source, aspect_scale=ratio, orientation=orientation, fill_color=mapper) if superterm: source_class = term_to_class[default_term]['source'] class_hex = p.hex_tile(q='q', r="r", size=size, line_color=None, source=source_class, aspect_scale=ratio, orientation=orientation, fill_color='pink', fill_alpha=0.7) class_hex.visible = False # HOVER TOOLTIPS = return_JS_code('tooltips') TOOLTIPS_tfidf = return_JS_code('tooltips_tfidf') code_callback_hover = return_JS_code('hover') callback_hover = CustomJS(code=code_callback_hover) hover = HoverTool(tooltips=TOOLTIPS, callback=callback_hover, show_arrow=False) p.add_tools(hover) # WIDGETS slider1 = Slider(start=1, end=SATURATION_MAX, value=1, step=SATURATION_STEP_SIZE, title="Saturation", width=100) slider2 = Slider(start=0, end=BLUR_MAX, value=0, step=BLUR_STEP_SIZE, title="Blur", width=100) checkbox = CheckboxGroup(labels=["TFIDF"], active=[]) radio_button_group = RadioGroup(labels=["Viridis256", "Greys256"], active=0) button = Button(label="Metadata", button_type="default", width=100) multi_select = MultiSelect(title=output_filename, value=[default_term], options=options, width=100, height=300) if superterm: label = "Show " + str(superterm) checkbox_class = CheckboxGroup(labels=[label], active=[]) # WIDGETS CODE FOR CALLBACK code_callback_slider1 = return_JS_code('slider1') code_callback_slider2 = return_JS_code('slider2') code_callback_checkbox = return_JS_code('checkbox') code_callback_rbg = return_JS_code('rbg') code_callback_button = return_JS_code('button') code_callback_ms = return_JS_code('multi_select') if superterm: code_callback_class = return_JS_code('class') # WIDGETS CALLBACK callback_slider1 = CustomJS(args={ 'source': source, 'mapper': mapper, 'slider2': slider2, 'checkbox': checkbox }, code=code_callback_slider1) callback_slider2 = CustomJS(args={ 'source': source, 'mapper': mapper, 'slider1': slider1, 'checkbox': checkbox }, code=code_callback_slider2) callback_checkbox = CustomJS(args={ 'source': source, 'slider1': slider1, 'slider2': slider2, 'mapper': mapper, 'hover': hover, 'tooltips': TOOLTIPS, 'tooltips_tfidf': TOOLTIPS_tfidf }, code=code_callback_checkbox) callback_radio_button_group = CustomJS(args={ 'p': p, 'multi_select': multi_select, 'mapper': mapper, 'term_to_class': term_to_class, 'Viridis256': Viridis256, 'Greys256': Greys256 }, code=code_callback_rbg) callback_button = CustomJS(args={ 'term_to_metadata': term_to_metadata, 'multi_select': multi_select }, code=code_callback_button) callback_ms = CustomJS(args={ 'source': source, 'term_to_source': term_to_source, 'term_to_class': term_to_class, 'checkbox': checkbox, 'slider2': slider2, 'slider1': slider1, 'p': p, 'mapper': mapper }, code=code_callback_ms) if superterm: callback_radio_button_group = CustomJS(args={ 'p': p, 'multi_select': multi_select, 'class_hex': class_hex, 'term_to_class': term_to_class, 'mapper': mapper, 'Viridis256': Viridis256, 'Greys256': Greys256 }, code=code_callback_rbg) callback_class = CustomJS(args={ 'multi_select': multi_select, 'term_to_class': term_to_class, 'class_hex': class_hex }, code=code_callback_class) callback_ms = CustomJS(args={ 'source': source, 'term_to_source': term_to_source, 'checkbox': checkbox, 'slider2': slider2, 'slider1': slider1, 'p': p, 'mapper': mapper, 'checkbox_class': checkbox_class, 'class_hex': class_hex, 'term_to_class': term_to_class }, code=code_callback_ms) # WIDGETS INTERACTION slider1.js_on_change('value', callback_slider1) slider2.js_on_change('value', callback_slider2) checkbox.js_on_change('active', callback_checkbox) radio_button_group.js_on_change('active', callback_radio_button_group) button.js_on_event(events.ButtonClick, callback_button) multi_select.js_on_change("value", callback_ms) if superterm: checkbox_class.js_on_change('active', callback_class) # LAYOUT if superterm: layout = row( multi_select, p, column(slider1, slider2, checkbox, checkbox_class, radio_button_group, button)) else: layout = row( multi_select, p, column(slider1, slider2, checkbox, radio_button_group, button)) show(layout)
from bokeh.io import show from bokeh.models import CustomJS, MultiSelect OPTIONS = [("1", "foo"), ("2", "bar"), ("3", "baz"), ("4", "quux")] multi_select = MultiSelect(value=["1", "2"], options=OPTIONS) multi_select.js_on_change("value", CustomJS(code=""" console.log('multi_select: value=' + this.value, this.toString()) """)) show(multi_select)