Set the size of the boxes for Ipython wigets - python

I'm woring on a simple interface where I have a text box and an interactive plot.
The idea of the text box in to used for some logs.
I'm able to get the plot and update the text from the same function, but my issue is that the text box is to large, I need to change its width.
Below is my current code and a screenshot of the result:
from IPython.display import display
from ipywidgets import Button, Layout, Textarea, HBox, VBox
import numpy as np
from ipywidgets import interact, interactive, fixed, interact_manual
import plotly.graph_objects as go
def f(x):
x = np.arange(x)
t1.value = "".join([str(c) for c in x.tolist()] )
fig = go.Figure(data=go.Scatter(x=x, y=x**2))
fig.show()
x = np.linspace(10,100,1)
interactive_plot = interactive(f, x=(1, 20))
l = Layout(flex='0 1 auto', height='400px', min_height='40px', width='10')
t1 = Textarea(value='TA: height=40px', layout=l)

The layout attribute exposes CSS properties like height, width, border, margin of the ipywidgets. This allows one to control how the ipywidgets are laid out. It is possible to control the width and height of the textbox by tweaking the values in the Layout snippet below as required. The parameters accept values in pixels(px) or percentage of available space(%) or even 'auto' to allow these values to be set automatically. You can find an example in the docs. I've set the height as 30px and width as 'auto' to automatically set a width:
l = Layout(flex='0 1 auto', height='30px', min_height='40px', width='auto')
Output:

Related

How to put interactive bokeh HTML image in Jupyterlab page

I'm using Jupyterlab (v 3.2.1) and bokeh to create a webpage that allows a user to load a .csv file containing a matrix, and a slider to optionally set a threshold on displayed results. The matrix contains simply some numerical values. The result would be an interactive heatmap displayed below the confirmation button. Whit my code the webpage is displayed correctly but the final plot is displayed in a new tab:
import warnings
warnings.filterwarnings('ignore')
import jupyter_bokeh
import ipywidgets as widgets
import pandas as pd
import io
from bokeh.io import show
from bokeh.models import ColorBar, ColumnDataSource, CategoricalColorMapper
from bokeh.plotting import figure
from bokeh.transform import transform
import bokeh.palettes
from IPython.display import display, clear_output, display_html
from bokeh.resources import CDN
from bokeh.embed import file_html
from bokeh.layouts import layout
#Display the webpage
file = widgets.FileUpload(accept=".txt, .csv, .dat", multiple=False)
threshold=widgets.IntSlider(value=0, min=0, max=20, step=1, description="Threshold:", disabled=False, continuous_update=False, orintation='horizontal', readout=True, readout_format="d")
button = widgets.Button(description='Run code')
text_0 = widgets.HTML(value="<header><h1>Phenotype Major Categories vs Genes Heatmap</h1></header>")
text_1 = widgets.HTML(value="<h3>Welcome to the heatmap plotter. By loading a csv file containing the counts of phenoypes for a gene into an IMPC major phenotype category, it will display an interactive heatmap.</h3>")
text_2 = widgets.HTML(value="Please load yor file (accepted formats: csv, txt, dat):")
text_3 = widgets.HTML(value="If desired, set a threshold for counts to be displayed:")
text_4 = widgets.HTML(value="<h2>Heatmap:</h2>")
vbox_head = widgets.VBox([text_0, text_1])
page_layout_plot = [text_2, file, text_3, threshold, button]
vbox_text = widgets.VBox(page_layout_plot)
page = widgets.VBox([vbox_head,vbox_text])
display(page)
#Set the endpage button to run the code
def on_button_clicked(result):
#Load the file and set the threshold
inp = list(file.value.values())[0] #if multiple setted to true, will not work!
content = inp['content']
content = io.StringIO(content.decode('utf-8'))
mat = pd.read_csv(content, sep="\t", index_col=0)
mat.index.name = 'MGI_id'
mat.columns.name = 'phen_sys'
#filtering phase
rem=[]
x = int(threshold.value)
if x != 0:
for i in mat.index:
if mat.loc[i].max() < x:
rem.append(i)
mat.drop(rem,inplace=True,axis=0)
#Create a custom palette and add a specific mapper to map color with values, we are converting them to strings to create a categorical color mapper to include only the
#values that we have in the matrix and retrieve a better representation
df = mat.stack(dropna=False).rename("value").reset_index()
fact= df.value.unique()
fact.sort()
fact = fact.astype(str)
df.value = df.value.astype(str)
mapper = CategoricalColorMapper(palette=bokeh.palettes.inferno(len(df.value.unique())), factors= fact, nan_color = 'gray')
#Define a figure
p = figure(
plot_width=1280,
plot_height=800,
x_range=list(df.phen_sys.drop_duplicates()[::-1]),
y_range=list(df.MGI_id.drop_duplicates()),
tooltips=[('Phenotype system','#phen_sys'),('Gene','#MGI_id'),('Phenotypes','#value')],
x_axis_location="above",
output_backend="webgl")
#Create rectangles for heatmap
p.rect(
x="phen_sys",
y="MGI_id",
width=1,
height=1,
source=ColumnDataSource(df),
fill_color=transform('value', mapper))
p.xaxis.major_label_orientation = 45
#Add legend
color_bar = ColorBar(
color_mapper=mapper,
label_standoff=6,
border_line_color=None)
p.add_layout(color_bar, 'right')
show(p)
button.on_click(on_button_clicked)
I already tried to use output_notebook() at the beginning but in that case nothing is displayed.
How can I fix it? It would be useful to display in real time the plot by changing the threshold without the need to click the confirmation button every time.
Thank you for all the help.
You might need to observe the value attribute of your treshold object to refresh your plot. So add something like this at the end of your code:
def on_value_change(change):
on_button_clicked(None)
threshold.observe(on_value_change, names='value')
More from the doc: https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#Signatures

Matplotlib: Toolbar vs Toolmanager, ToolbarQt vs NavigationToolbar2QT?

I'm trying to embed the "home" function (i.e., "reset original review") to a widget button combined with other operations. Below is an example code I have.
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.widgets import Button
from random import randrange
matplotlib.use('QtAgg')
plt.rcParams['toolbar'] = 'toolmanager' # switch this row to see the difference.
fig, ax = plt.subplots(nrows=1, figsize=(4,6))
ax.imshow([[1, 2], [5, 6]])
def change_color(event):
ax.imshow([[randrange(10), randrange(10)], [randrange(10), randrange(10)]])
plt.get_current_fig_manager().toolbar.home()
axprev0 = plt.axes([0.35, 0.05, 0.3, 0.0375])
bprev0 = Button(axprev0, 'Change Color')
bprev0.on_clicked(change_color)
plt.show()
My problem is that when plt.rcParams['toolbar'] = 'toolmanager' is on, it shows error message toolbar.home() won't work. The reason I need plt.rcParams['toolbar'] = 'toolmanager' is because it allows me to scroll the mouse wheel for zooming when I'm on the zoom or drag tool.
It seems that when we specify plt.rcParams['toolbar'] = 'toolmanager', it is using ToolbarQt. The ToolbarQt supports mouse wheel scrolling for zooming. While the name of the home function of ToolbarQt is different.
If we do not specify plt.rcParams['toolbar'] = 'toolmanager', plt is using NavigationToolbar2QT. However, it does not support mouse wheel scrolling for zooming.
plt.get_current_fig_manager().toolmanager.tools['home'].trigger(fig, event) # use this function when toolmanager

Linking zoom tools in Bokeh

I'm working on visualizations that require very precise zooming in on X axis - down to 0.001 units, when full range can span hundreds of units. To enable this, I'd like to use Bokeh with Spinner widgets. Here's the minimum example where X range responds to the spinners. Conveniently, manually entering the value into the spinner works as you would expect.
import numpy as np
from bokeh.io import show
from bokeh.layouts import column, row
from bokeh.models import Spinner
from bokeh.plotting import figure, output_notebook
output_notebook()
x = np.random.rand(10)
y = np.random.rand(10)
p = figure(width=400, height=200, x_range=(0, 1), y_range=(0, 1))
points = p.scatter(x=x, y=y, size=4)
spinner_xmin = Spinner(title="min X", low=0, high=1, step=0.05, value=0, width=80)
spinner_xmax = Spinner(title="max X", low=0, high=1, step=0.05, value=1, width=80)
spinner_xmin.js_link('value', p.x_range, 'start')
spinner_xmax.js_link('value', p.x_range, 'end')
show(column(p, row([spinner_xmin, spinner_xmax], width=400, sizing_mode='stretch_both')))
Is there a way for the spinners to update their values when I use Bokeh's default Zoom tool, so that both tools can be used together?
Is it easier to do in other libraries like Plotly or anything else?
If you want to display the changes of the x-axis in the spinner you can simply add another js-link in the other direction as you did before.
Add these two lines to your code to connect the x_range with the spinner.
p.x_range.js_link('start', spinner_xmin, 'value')
p.x_range.js_link('end', spinner_xmax, 'value')
Now you have a two-way connection.
Comment
The tools don't know anything about your step-size. Therefor the values can look a litte odd. But this is also the case if you enter a value by hand.

interactive scatter highlight in bokeh

I am trying to visualise sensor output in relation to its path.
I plot path as scatter in one figure and some range of signal amplitude in the second figure. I need to visualise (highlight) a path point at which the particular reading was taken.
I started using bokeh as a backend and in general, got very good results with visualisations I need. But I am stuck on this particular interaction.
I would like to have some marker like a vertical line anchored in the middle of the figure. When I move/scroll the amplitude plot (the bottom one), I would like to highlight the point on the path plot where the reading closest to the marker line was taken.
The example code:
(I would like to anchor the marker line and add interaction between the red dot and the vertical line taking an index of the signal, which is not implemented.)
import numpy as np
import pandas as pd
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, HoverTool, Span
from bokeh.plotting import figure, show
from bokeh.layouts import gridplot
output_file('interactive_path_sig.html', title="interactive path")
class InteractivePath():
def __init__(self):
x = np.arange(0, 1000, 0.5)
self.df = pd.DataFrame({"x": x,
"y": np.sin(x),
"z": np.cos(x)})
self.source = ColumnDataSource(self.df)
def plot_path(self):
plt = figure(title = "Sensor Path")
plt.scatter(x="x", y="y",source=self.source,
line_color=None, size = 6)
# TODO implement interaction instead of hard coded index
index=500 # this is where I think I need to create working callback
print("x={}, y={}".format(self.df['x'][index], self.df['y'][index]))
plt.circle(x=self.df['x'][index], y=self.df['y'][index],
fill_color="red", size=15)
hover = HoverTool()
hover.tooltips=[("index", "#index"), ("senosr","#z")]
plt.add_tools(hover)
return plt
def plot_signal(self):
plt = figure(x_range=(450, 550), title="Signal Amplitude")
plt.line(x="index", y="z", source=self.source, line_color="black", line_width=2)
# TODO implement interaction instead of hard coded index
index = 500 # I think this needs emit some singal to other plot
vline = Span(location=index, dimension='height', line_color='red', line_width=3)
plt.renderers.extend([vline])
return plt
def get_grid(self):
""" place visualisation in a grid and display"""
grid = gridplot([[self.plot_path()], [self.plot_signal()]],
sizing_mode='stretch_both',)
return grid
def vis_main(self):
""" use all visualisations"""
show(self.get_grid())
if __name__=="__main__":
vis = InteractivePath()
vis.vis_main()
So a few pointers:
I think you'll want both of those plots in the same method because the columndatasource is common between them, and you can set CustomJS behaviors between them if they're in the same scope.
The index that you're using already exists within your self.df which will be easier to interact with once it's on your plot, since you can handle it with JS plot behavior instead of going back to a python variable and reloading data.
Instead of drawing a new glyph for your 'highlighted' point, consider using the 'hover' or 'selected' functionality built in. hover_color='red' for example could replace drawing and moving another class of glyph. If you want to leave statically selected so you can generate a nice report without a mouse in a screenshot, defining a callback using the built-in selected property of ColumnDataSource
I can post some actual code blocks with more specific examples, but if any of these points is a hard stop for your actual use case, it'll drive solution.
Edit:
So I got pretty close using one class method - the issue is being able to edit the second plot from the first method, not the actual change to the ColumnDataSource itself.
def plot_it(self):
self.plot_signal = figure(x_range=(450, 550), y_range=(-1, 1), title='signal')
self.plot_signal.line(x='index', y='z', source=self.source)
self.plot_signal.segment(x0=500, y0=-2, x1=500, y1=2, source=self.source)
self.plot_path = figure(title='sensor')
self.plot_path.scatter(x='x', y='y', source=self.source, hover_color='red')
jscode='''
var data = source.data;
var plot_signal = plot_signal;
var index = cb_data.index['1d'].indices;
var xmin = 0;
var xmax = 0;
if (index > 0) {
xmin = index[0] - 50;
xmax = index[0] + 50;
plot_signal.x_range.end = xmax;
plot_signal.x_range.start = xmin;
plot_signal.change.emit();
}
hover_callback = CustomJS(args=dict(source=self.source, plot_signal=self.plot_signal), code=jscode)
hover.tooltips = [('index', '#index'), ('sensor', '#z')]
self.plot_path.add_tools(hover)
def get_grid(self):
self.plot_it()
grid = gridplot([[self.plot_path], [self.plot_signal]])
return grid
That should do everything but move the line segment. I couldn't find the segment naming convention to add plot_signal.SOMEOBJECT.x0 and .x1 but it would just get added to the if (index > 0) block just like using index[0]. I took some of the style options out because I'm transcribing from another computer.
This question on moving a line segment might give you the syntax on the segment JSON object.

Return the value of Panel's slider widget

I'm using Panel's slider widget to inspect some data and I want to use the values of this widget as an argument of other functions.
I built my own code based on this one:
import hvplot.pandas
import panel as pn
import holoviews as hv
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
pn.extension()
bins = pn.widgets.IntSlider(name='Number of bins', start=5, end=25, step=5, value=10)
kde = pn.widgets.Checkbox(name='Show density estimate')
observations = pn.widgets.Checkbox(name='Show individual observations')
bandwidth = pn.widgets.FloatSlider(name='KDE Bandwidth', start=0.1, end=1)
#pn.depends(bins.param.value, kde.param.value,
observations.param.value, bandwidth.param.value)
def get_plot(bins, kde, obs, bw):
plot = sea_surface_temperature.hvplot.hist(bins=bins, normed=True, xlabel='Temperature (C)', padding=0.1)
if kde:
plot *= sea_surface_temperature.hvplot.kde().opts(bandwidth=bw)
if obs:
plot *= hv.Spikes(sea_surface_temperature, 'temperature').opts(
line_width=0.1, alpha=0.1, spike_length=-0.01)
return plot
widgets = pn.WidgetBox('## Sea surface temperatures', bins, observations, kde)
def add_bandwidth(event):
if event.new:
widgets.append(bandwidth)
else:
widgets.remove(bandwidth)
kde.param.watch(add_bandwidth, 'value')
pn.Row(widgets, get_plot).servable()
Putting my question in the context of the previous code, I would like to use the value of bins. In order to do this, I changed the final output to:
output = (pn.Row(widgets, get_plot).servable(), bins)
Doing output[1] shows the plot, but output[2] shows the slider widget instead of the chosen integer.
If I change the output of the get_plot function to return (plot,bins) the final plot shows the widget plus the same as do print(plot) instead of the histogram.
Here's the source of the code I used to develope mine:
https://panel.pyviz.org/gallery/simple/temperature_distribution.html#gallery-temperature-distribution
You defined bins to be the widget, so naturally it shows the widget.
In your case, bins.value is probably what you're looking for, following the docs here: https://panel.pyviz.org/user_guide/Widgets.html

Categories