def link_plot(adata, key, genes=None, basis=['umap', 'pca'], components=[1, 2], subsample=None, steps=[40, 40], sample_size=500, distance=2, cutoff=True, highlight_only=None, palette=None, show_legend=False, legend_loc='top_right', plot_width=None, plot_height=None, save=None): """ Display the distances of cells from currently highlighted cell. Params -------- adata: AnnData annotated data object key: str key in `adata.obs_keys()` to color the static plot genes: list(str), optional (default: `None`) list of genes in `adata.var_names`, which are used to compute the distance; if None, take all the genes basis: list(str), optional (default:`['umap', 'pca']`) list of basis to use when plotting; only the first plot is hoverable components: list(int); list(list(int)), optional (default: `[1, 2]`) list of components for each basis subsample: str, optional (default: `None`) subsample strategy to use when there are too many cells possible values are: `"density"`, `"uniform"`, `None` steps: int; list(int), optional (default: `[40, 40]`) number of steps in each direction when using `subsample="uniform"` sample_size: int, optional (default: `500`) number of cells to sample based on their density in the respective embedding when using `subsample="density"`; should be < `1000` distance: int; str, optional (default: `2`) for integers, use p-norm, for strings, only `'dpt'` is available cutoff: bool, optional (default: `True`) if `True`, do not color cells whose distance is further away than the threshold specified by the slider highlight_only: 'str', optional (default: `None`) key in `adata.obs_keys()`, which makes highlighting work only on clusters specified by this parameter palette: matplotlib.colors.Colormap; list(str), optional (default: `None`) colormap to use, if None, use plt.cm.RdYlBu show_legend: bool, optional (default: `False`) display the legend also in the linked plot legend_loc: str, optional (default `'top_right'`) location of the legend seed: int, optional (default: `None`) seed when `subsample='density'` plot_width: int, optional (default: `None`) width of the plot plot_height: int, optional (default: `None`) height of the plot save: Union[os.PathLike, Str, NoneType], optional (default: `None`) path where to save the plot Returns -------- None """ assert key in adata.obs.keys(), f'`{key}` not found in `adata.obs`.' if subsample == 'uniform': adata, _ = sample_unif(adata, steps, basis[0]) elif subsample == 'density': adata, _ = sample_density(adata, sample_size, basis[0], seed=seed) elif subsample is not None: raise ValueError(f'Unknown subsample strategy: `{subsample}`.') palette = cm.RdYlBu if palette is None else palette if isinstance(palette, matplotlib.colors.Colormap): palette = to_hex_palette(palette(range(palette.N), 1., bytes=True)) if not isinstance(components[0], list): components = [components] if len(components) != len(basis): assert len(basis) % len(components) == 0 and len(basis) >= len(components) components = components * (len(basis) // len(components)) if not isinstance(components, np.ndarray): components = np.asarray(components) if highlight_only is not None: assert highlight_only in adata.obs_keys(), f'`{highlight_only}` is not in adata.obs_keys().' genes = adata.var_names if genes is None else genes gene_subset = np.in1d(adata.var_names, genes) if distance != 'dpt': d = adata.X[:, gene_subset] if issparse(d): d = d.A dmat = distance_matrix(d, d, p=distance) else: if not all(gene_subset): warnings.warn('`genes` is not None, are you sure this is what you want when using `dpt` distance?') dmat = [] ad_tmp = adata.copy() ad_tmp = ad_tmp[:, gene_subset] for i in range(ad_tmp.n_obs): ad_tmp.uns['iroot'] = i sc.tl.dpt(ad_tmp) dmat.append(list(ad_tmp.obs['dpt_pseudotime'].replace([np.nan, np.inf], [0, 1]))) dmat = pd.DataFrame(dmat, columns=list(map(str, range(adata.n_obs)))) df = pd.concat([pd.DataFrame(adata.obsm[f'X_{bs}'][:, comp - (bs != 'diffmap')], columns=[f'x{i}', f'y{i}']) for i, (bs, comp) in enumerate(zip(basis, components))] + [dmat], axis=1) df['hl_color'] = np.nan df['index'] = range(len(df)) df['hl_key'] = list(adata.obs[highlight_only]) if highlight_only is not None else 0 df[key] = list(map(str, adata.obs[key])) start_ix = '0' # our root cell ds = ColumnDataSource(df) mapper = linear_cmap(field_name='hl_color', palette=palette, low=df[start_ix].min(), high=df[start_ix].max()) static_fig_mapper = _create_mapper(adata, key) static_figs = [] figs, renderers = [], [] for i, bs in enumerate(basis): # linked plots fig = figure(tools='pan, reset, save, ' + ('zoom_in, zoom_out' if i == 0 else 'wheel_zoom'), title=bs, plot_width=400, plot_height=400) _set_plot_wh(fig, plot_width, plot_height) kwargs = {} if show_legend and legend_loc is not None: kwargs['legend_group'] = 'hl_key' if highlight_only is not None else key scatter = fig.scatter(f'x{i}', f'y{i}', source=ds, line_color=mapper, color=mapper, hover_color='black', size=8, line_width=8, line_alpha=0, **kwargs) if show_legend and legend_loc is not None: fig.legend.location = legend_loc figs.append(fig) renderers.append(scatter) # static plots fig = figure(title=bs, plot_width=400, plot_height=400) fig.scatter(f'x{i}', f'y{i}', source=ds, size=8, color={'field': key, 'transform': static_fig_mapper}, **kwargs) if legend_loc is not None: fig.legend.location = legend_loc static_figs.append(fig) fig = figs[0] end = dmat[~np.isinf(dmat)].max().max() if distance != 'dpt' else 1.0 slider = Slider(start=0, end=end, value=end / 2, step=end / 1000, title='Distance ' + '(dpt)' if distance == 'dpt' else f'({distance}-norm)') col_ds = ColumnDataSource(dict(value=[start_ix])) update_color_code = f''' source.data['hl_color'] = source.data[first].map( (x, i) => {{ return isNaN(x) || {'x > slider.value || ' if cutoff else ''} source.data['hl_key'][first] != source.data['hl_key'][i] ? NaN : x; }} ); ''' slider.callback = CustomJS(args={'slider': slider, 'mapper': mapper['transform'], 'source': ds, 'col': col_ds}, code=f''' mapper.high = slider.value; var first = col.data['value']; {update_color_code} source.change.emit(); ''') h_tool = HoverTool(renderers=renderers, tooltips=[], show_arrow=False) h_tool.callback = CustomJS(args=dict(source=ds, slider=slider, col=col_ds), code=f''' var indices = cb_data.index['1d'].indices; if (indices.length == 0) {{ source.data['hl_color'] = source.data['hl_color']; }} else {{ var first = indices[0]; source.data['hl_color'] = source.data[first]; {update_color_code} col.data['value'] = first; col.change.emit(); }} source.change.emit(); ''') fig.add_tools(h_tool) color_bar = ColorBar(color_mapper=mapper['transform'], width=12, location=(0,0)) fig.add_layout(color_bar, 'left') fig.add_tools(h_tool) plot = column(slider, row(*static_figs), row(*figs)) if save is not None: save = save if str(save).endswith('.html') else str(save) + '.html' bokeh_save(plot, save) else: show(plot)
plot.select_one('save_confirmation').update(text=_text) if g.BOKEH_DEV: print "data_save:", data_save, "\n" print "req.content:", req.content[:20], "\n" def clear_message(attr, old, new): plot.select_one('save_confirmation').update(text="") source.on_change('data', clear_message) hover.callback = CustomJS(args=dict(cb=checkbox), code=""" if(!cb.active.includes(%d)) { document.getElementsByClassName('bk-tooltip')[%d].style.display = 'none'; } """ % (1, 0)) checkbox.callback = CustomJS(args=dict( p0=circle, p1=line_regr, p2=circle_fit, ), code=""" //Toggle glyph visibility based on checkbox status p0.visible = cb_obj.active.includes(0); p1.visible = cb_obj.active.includes(1); p2.visible = cb_obj.active.includes(2); """)
def _init_figure(self): ''' Initializes the figure ''' ftype = self.get_type() if ftype == FigureType.IND: aspectratio = self._scheme.ind_aspectratio elif ftype == FigureType.OBS: aspectratio = self._scheme.obs_aspectratio elif ftype == FigureType.VOL: aspectratio = self._scheme.vol_aspectratio elif ftype == FigureType.DATA: aspectratio = self._scheme.data_aspectratio else: raise Exception(f'Unknown type "{ftype}"') f = figure( width=1000, tools=Figure._tools, x_axis_type='linear', # backend webgl removed due to this bug: # https://github.com/bokeh/bokeh/issues/7568 # also line styles do not work with webgl # output_backend='webgl', aspect_ratio=aspectratio) f.y_range.range_padding = self._scheme.y_range_padding # remove any spacing if there is no title, so there is no spacing # between plots if not self._scheme.plot_title: f.min_border_bottom = 0 f.min_border_top = 0 f.border_fill_color = convert_color(self._scheme.border_fill) f.xaxis.axis_line_color = convert_color(self._scheme.axis_line_color) f.yaxis.axis_line_color = convert_color(self._scheme.axis_line_color) f.xaxis.minor_tick_line_color = convert_color( self._scheme.tick_line_color) f.yaxis.minor_tick_line_color = convert_color( self._scheme.tick_line_color) f.xaxis.major_tick_line_color = convert_color( self._scheme.tick_line_color) f.yaxis.major_tick_line_color = convert_color( self._scheme.tick_line_color) f.xaxis.major_label_text_color = convert_color( self._scheme.axis_label_text_color) f.yaxis.major_label_text_color = convert_color( self._scheme.axis_label_text_color) f.xgrid.grid_line_color = convert_color(self._scheme.grid_line_color) f.ygrid.grid_line_color = convert_color(self._scheme.grid_line_color) f.title.text_color = convert_color(self._scheme.plot_title_text_color) f.left[0].formatter.use_scientific = False f.background_fill_color = convert_color(self._scheme.background_fill) # mechanism for proper date axis without gaps, thanks! # https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/t3HkalO4TGA formatter_code = pkgutil.get_data( __name__, 'templates/js/tick_formatter.js').decode() f.xaxis.formatter = FuncTickFormatter(args=dict( axis=f.xaxis[0], formatter=DatetimeTickFormatter( microseconds=['%fus'], milliseconds=['%3Nms', '%S.%3Ns'], seconds=[self._scheme.axis_tickformat_seconds], minsec=[self._scheme.axis_tickformat_minsec], minutes=[self._scheme.axis_tickformat_minutes], hourmin=[self._scheme.axis_tickformat_hourmin], hours=[self._scheme.axis_tickformat_hours], days=[self._scheme.axis_tickformat_days], months=[self._scheme.axis_tickformat_months], years=[self._scheme.axis_tickformat_years]), source=self._fp.cds, ), code=formatter_code) hover_code = pkgutil.get_data( __name__, 'templates/js/hover_tooltips.js').decode() h = HoverTool( tooltips=[('Time', f'@datetime{{{self._scheme.hovertool_timeformat}}}')], mode='vline', formatters={'@datetime': 'datetime'}, ) callback = CustomJS(args=dict(source=self.cds, hover=h), code=hover_code) h.callback = callback f.tools.append(h) self._hover = h # set figure self.figure = f
for (var i = 0, len = tooltips.length; i < len; i ++) { tooltips[i].style.top = ""; // unset what bokeh.js sets tooltips[i].style.right = ""; tooltips[i].style.bottom = ""; tooltips[i].style.left = ""; tooltips[i].style.bottom = "0px"; tooltips[i].style.right = "-150px"; } """) # bind widget callbacks color_class_select.on_change('value', update_color_class) toggle_class_select.on_change('value', update_toggle_class) class_toggle_multi_select.on_change('value', update_class_selection) file_select_dropdown.on_click(file_select_handler) hover_tip_tool.callback = tooltip_fix_callback update_toggle_class('value', '', features_list[0]) control_panel = column([ file_select_dropdown, color_class_select, toggle_class_select, class_toggle_multi_select ], sizing_mode="stretch_height", width=200) p.add_tools(wheel_zoom_tool) p.add_tools(hover_tip_tool) p.toolbar.active_scroll = wheel_zoom_tool curdoc().add_root(row(control_panel, p, sizing_mode='stretch_both')) ''' filelist_refresh_button = Button(label="Refresh data list", button_type="success")