Im using bokeh server to plot a line graph, where theres a checkbox button that will flip the line if its checked. If its unchecked I want to see the original version of the line (unflipped). Following the flip/unflip, a second function is called to perform some other calculations:
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox, layout
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import CheckboxGroup
from bokeh.plotting import figure
def flip_signal(signal, flip):
if flip:
signal = -signal
else:
signal = signal
return signal
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
plot = figure(plot_height=500, plot_width=850, title="test",
tools="crosshair,pan,reset,save,wheel_zoom")
line_orig = plot.line('x', 'y', source=source, line_width=1, line_alpha=1)
flip_signal_btn = CheckboxGroup(labels=["Flip signal"])
def update_flip(attrname, old, new):
if 0 in flip_signal_btn.active:
flip = True
else:
flip = False
# Update plot
source.data = dict(x=source.data['x'], y=flip_signal(source.data['y'], flip))
def update_peaks(attrname, old, new):
# do something else
pass
for w in [flip_signal_btn]:
w.on_change('active', update_flip)
w.on_change('active', update_peaks)
options = widgetbox(flip_signal_btn)
doc_layout = layout(row([options], height=200))
curdoc().add_root(row(plot, doc_layout, width=800))
curdoc().title = "checkbox"
The checkbox only seems to call update_flip when its checked, so in order to flip (or unflip) the signal I need to click it twice. For example, when I uncheck the box nothing happens, but I'm expecting it to unflip the signal. Rather it only unflips the signal if I uncheck and then check the box again
The callback is being invoked with the correct values on every button click, as can be verified with some print statements. The error is in your logic. Since you are operating on the current signal, rather than some original signal, you presumably always want to flip every time, unconditionally. Currently, you are only flipping every other button push, because this flip_signal(..., False) just returns the signal passed in, as-is. Changing the update_flip callback to always flip yields the behavior you want:
def update_flip(attrname, old, new):
# flip the *current* data on *every* button toggle
source.data = dict(x=source.data['x'], y=flip_signal(source.data['y'], True))
For your logic to work you would need a different function than your current flip_signal. You would need a function that always returns the original unflipped signal on False, and always returns the flipped signal on True. Contrast this with the current flip_signal, if you call it with False it gives you back whatever you passed in, regardless of whether it is the flipped or unflipped signal.
Related
I would like to use HoloViews DynamicMap with a widget to select data for two curves, and a widget to control whether the curves are shown separately or as a filled area. It almost works, but sometimes shows the wrong data, depending on the order in which the widgets are manipulated.
The code snippet below demonstrates the issue, if run in a Jupyter notebook. It creates two identical DynamicMaps to show how they get out of sync with the widgets.
For this demo, if 'fill', an Area chart is shown. Otherwise, two Curve elements show the top and bottom bounds of the same area.
If 'higher', the area or curves are shifted upwards along the vertical axis (higher y values).
First, one DynamicMap is displayed. The code snippet then toggles the widget for 'fill' followed by 'higher', in that order (alternatively, the user could manually toggle the widgets). The DynamicMap should show a filled area in the higher position, but actually shows a filled area in the lower position. The image below the code snippet shows this incorrect DynamicMap on the left.
The second DynamicMap (shown on the right) is added to the display after the widgets are toggled. It correctly displays a chart corresponding to the state of the widgets at that point.
Code snippet
import holoviews as hv
import numpy as np
import panel as pn
pn.extension()
# Make two binary widgets to control whether chart
# data is high or low, and whether chart shows
# an area fill or just a pair of lines.
check_boxes = {name: pn.widgets.Checkbox(value=False, name=name) \
for name in ["higher", "fill"]}
# Data for charts.
xvals = [0.10, 0.90]
yvals_high = [1, 1.25]
yvals_low = [0.25, 0.40]
# Declare horizontal and vertical dimensions to go on charts.
xdim = hv.Dimension("x", range=(-0.5, 1.5), label="xdim")
ydim = hv.Dimension("y", range=(0, 2), label="ydim")
def make_plot(higher, fill):
"""Make high or low, filled area or line plot"""
yvals_line1 = np.array(yvals_high if higher else yvals_low)
yvals_line2 = 1.2*yvals_line1
if fill:
# Make filled area plot with x series and two y series.
area_data = (xvals, yvals_line1, yvals_line2)
plot = hv.Area(area_data,
kdims=xdim,
vdims=[ydim, ydim.clone("y.2")])
plot = hv.Overlay([plot]) # DMap will want an overlay.
else:
# Make line plot with x series and y series.
line_data_low = (xvals, yvals_line1)
line_data_high = (xvals, yvals_line2)
plot = hv.Curve(line_data_low,
kdims=xdim,
vdims=ydim) \
* hv.Curve(line_data_high,
kdims=xdim,
vdims=ydim)
return plot
# Map combinations of higher and fill to corresponding charts.
chart_dict = {(higher, fill): make_plot(higher, fill) \
for higher in [False,True] for fill in [False,True]}
def chart_func(higher, fill):
"""Return chart from chart_dict lookup"""
return chart_dict[higher, fill]
# Make two DynamicMaps linked to the check boxes.
dmap1 = hv.DynamicMap(chart_func, kdims=["higher", "fill"], streams=check_boxes)
dmap2 = hv.DynamicMap(chart_func, kdims=["higher", "fill"], streams=check_boxes)
# Show the check boxes, and one of the DMaps.
widget_row = pn.Row(*check_boxes.values(), width=150)
dmap_row = pn.Row(dmap1, align='start')
layout = pn.Column(widget_row,
dmap_row)
display(layout)
## Optionally use following line to launch a server, then toggle widgets.
#layout.show()
# Toggle 'fill' and then 'higher', in that order.
# Both DMaps should track widgets...
check_boxes["fill"].value = True
check_boxes["higher"].value = True
# Show the other DMap, which displays correctly given the current widgets.
dmap_row.append(dmap2)
# But first dmap (left) is now showing an area in wrong location.
Notebook display
Further widget toggles
The code snippet below can be run immediately afterwards in another cell. The resulting notebook display is shown in an image below the code snippet.
The code here toggles the widgets again, 'fill' and 'higher', in that order (alternatively, the user could manually toggle the widgets).
The left DynamicMap correctly displays a chart corresponding to the state of the widgets at that point, that is, two lines in the lower position.
The right DynamicMap incorrectly shows the two lines in the higher position.
# Toggle 'fill' and then 'higher' again, in that order.
# Both DMaps should track widgets...
check_boxes["fill"].value = False
check_boxes["higher"].value = False
# But now the second DMap shows lines in wrong location.
Am I just going about this the wrong way?
Thanks for the detailed, reproducible report!
After running your example, I noticed two things:
Switching from pn.extension to hv.extension at the start seems to fix the strange behavior that I also observing when using the panel extension. Could you confirm that things work as expected when using the holoviews extension?
I was wondering why your DynamicMaps work via chart_dict and chart_func when you can just use your make_plot callback in the DynamicMaps directly, without modification.
If you can confirm that the extension used changes the behavior, could you file an issue about this? Thanks!
I have a time history of images (2 + 1)D arrays that I take various slices of and examine using ipython and each view is a matplotlib figure.
I have a custom class that uses matplotlib widgets (specifically a Slider) to allow an interactive window to open and view the images frame by frame as selected by the Slider. The widget works fine, but uses the plt.show() command to block, which is also fine until I'm done with the widget.
In order for control to pass back to the ipython command line, I have to close all matplotlib figures--I would like to be able to only close the window associated with the widget. Is there some method to enable this functionality?
Something like fig.show(blocking=True) would be what I imagine I want, i.e. limit the blocking of the GUI mainloop to only look for plt.close() of that window, but that does not appear to be currently implemented.
#ImportanceOfBeingEarnest, thanks for the response. I've added the code I use for the viewer widget. To initialize the object, you just need to provide a 3D array of [frames (t), y, x] values. i.e.
randomData = np.random.rand((5,5,5))
class showFrames(object):
def __init__(self, timeData):
self.data = timeData # 3D array of t, y, x values
self.fig, self.ax = plt.subplots(1)
self.im = None
self.frameStr = None
self.start()
def start(self):
# initialize GUI
Tmin = self.data.min()
Tmax = self.data.max()
frameInit = self.data.shape[0] - 1
self.im = self.ax.imshow(self.data[frameInit])
self.im.set_clim(Tmin, Tmax)
self.fig.colorbar(self.im)
self.frameStr = self.ax.text(0.1, 0.1, '', transform=self.ax.transAxes, color='white')
axis_color = 'yellow'
# Add frame and radius slider for tweaking the parameters
frame_slider_ax = self.fig.add_axes([0.25, 0.05, 0.65, 0.03], axisbg=axis_color)
frame_slider = Slider(frame_slider_ax, 'Frame', 0, frameInit, valinit=frameInit)
frame_slider.on_changed(self.frame_slider_on_changed)
plt.show()
def frame_slider_on_changed(self, i):
self.im.set_data(self.data[int(i)])
self.frameStr.set_text(str(int(i)))
self.fig.canvas.draw_idle()
Your Slider instance is being garbage collected because you don't retain a reference to it.
From the Slider documentation:
For the slider to remain responsive you must maintain a reference to it.
In this case self.slider=Slider(...) instead of slider=Slider(...).
Consider this Jupyter Python code, which uses Plotly:
import plotly.graph_objs as go
import numpy as np
from ipywidgets import widgets
from IPython.display import display
import random
mybutton = widgets.Button(description="Redraw")
xs = np.linspace(start=0, stop=10, num=100)
fig = go.FigureWidget( layout=go.Layout() )
# NB: function needs to be written in a way, that returns np.array for input np.array!
# or - use np.vectorize, to apply it element-by-element
def TestFunc(inval):
return inval+2*random.random()
fig.add_trace(go.Scatter(x=xs, y=np.vectorize(TestFunc)(xs),
mode='lines',
name='Test'))
def on_button_clicked(b):
fig.add_trace(go.Scatter(x=xs, y=np.vectorize(TestFunc)(xs),
mode='lines',
name='Test'))
mybutton.on_click(on_button_clicked)
widgets.VBox([mybutton, fig])
What I want to do, is redraw the function anew, whenever I click the button. However, since I use add_trace in the button callback, I get new traces added - I don't get the original one replaced:
So, my question is:
How do I obtain a reference to a "trace", added with add_trace, so that I could replace it? (say, fig.traces[0] = ...)
What is the best way to redraw the figure with a new retrace, with the minimal amount of object instantiation (I guess, I could do fig = go.FigureWidget( ... ) ... upon each button click, but that would have to recreate everything; I'd think, just recreating the y array, and triggering a redraw would be more "optimized")
OK, found something - still not sure if this is the way to do it, so if someone knows better, please post...
But anyways, fig.add_trace returns a reference that you can store in a variable; eventually that variable also contains the .x and .y arrays, and the .y array can be directly replaced, like so:
import plotly.graph_objs as go
import numpy as np
from ipywidgets import widgets
from IPython.display import display
import random
mybutton = widgets.Button(description="Redraw")
xs = np.linspace(start=0, stop=10, num=100)
fig = go.FigureWidget( layout=go.Layout() )
# NB: function needs to be written in a way, that returns np.array for input np.array!
# or - use np.vectorize, to apply it element-by-element
def TestFunc(inval):
return inval+2*random.random()
mytrace = fig.add_trace(go.Scatter(x=xs, y=np.vectorize(TestFunc)(xs),
mode='lines',
name='Test'))
print(repr(mytrace))
def on_button_clicked(b):
mytrace.data[0].y = np.vectorize(TestFunc)(xs)
mybutton.on_click(on_button_clicked)
widgets.VBox([mybutton, fig])
The above code works as intended - but I'm not yet sure whether it's the most optimized way to do it...
I plotted a graph with matplotlib, and I want to interact with it when I clicked some of the points. I have many points overlapping on each other.
def foo():
plist = list()
data = data_generator()
for ind in range(0, len(data)):
x_plot, y_plot = generator()
paths = ax.plot(x_plot, y_plot, alpha=alpha)
plist.append(paths[0])
class AbstractPlotPage(tk.Frame):
def __init__():
self.paths = foo()
self.canvas = FigureCanvasTkAgg(f, self)
self.canvas.mpl_connect('pick_event', self.on_pick)
self.canvas.show()
def on_pick():
print('test')
The thing is, I noticed matplotlib does not run the on_pick once for all the overlapped points. But it runs once for a single points then run it again.
So is there a way to check when the event queue is done? Or how can I watch this event queue?
Just if anyone is interested, I found some explaination but not a real solution.
The following is the code from artist, everytime when the self.figure.canvas.pick_event is called, the event will run the callback.
Basically this recursion iterates over all the elements on a figure, so it's actually true that if you selected multiple points, the callback will be run multiple times.
How to handle it? I guess you should re-write some code from the matplotlib. I think my version is bit too old, it's 1. something. Maybe the newer version already did some change.
def pick(self, mouseevent):
"""
call signature::
pick(mouseevent)
each child artist will fire a pick event if *mouseevent* is over
the artist and the artist has picker set
"""
# Pick self
if self.pickable():
picker = self.get_picker()
if six.callable(picker):
inside, prop = picker(self, mouseevent)
else:
inside, prop = self.contains(mouseevent)
if inside:
self.figure.canvas.pick_event(mouseevent, self, **prop)
# Pick children
for a in self.get_children():
# make sure the event happened in the same axes
ax = getattr(a, 'axes', None)
if mouseevent.inaxes is None or ax is None or \
mouseevent.inaxes == ax:
# we need to check if mouseevent.inaxes is None
# because some objects associated with an axes (e.g., a
# tick label) can be outside the bounding box of the
# axes and inaxes will be None
# also check that ax is None so that it traverse objects
# which do no have an axes property but children might
a.pick(mouseevent)
I have a plot that allows user to click on a data point. Then it generates a continuous loop that was supposed to be closed by clicking on the close button. The example below will trigger a print message of "Still Looping" every 1second. Then I created a button hoping to close the loop by changing loopBool to True. But it doesn't work because once the loop starts I am not able to interact with the figure window. How can I solve this? Many thanks
import numpy as np
from matplotlib import pyplot as plt
import time
import matplotlib.widgets as widgets
fig, ax = plt.subplots()
# it is not always column 0 and 1
sctPlot = ax.scatter([-0.3,0,0.3], [0.3,0.3,0.3], c="blue", picker = 2, s=[50]*3)
fig.subplots_adjust(bottom=0.3, left=0.1)
plt.grid(False)
plt.axis([-0.6, 0.6, -0.6, 0.6])
loopBool = True
def closeLooping(event):
global loopBool
loopBool = False
def looping(event):
global loopBool
while (loopBool == True):
print "Still Looping!"
time.sleep(1)
print "Stop!!"
axCloseButton = plt.axes([0.1, 0.15, 0.2, 0.06])
bClose = Button(axCloseButton, "Close", color = axcolor, hovercolor = '0.975')
bClose.on_clicked(closeLooping)
fig.canvas.mpl_connect('pick_event', looping)
It seems to me that a process stuck in an infinite loop is in a sense incompatible with a GUI. GUIs themselves perform a loop, checking for and responding to events happening. The best solution would probably be to remove your infinite loop, by transforming that part of code to an event-based one.
However, I also found a solution to your actual problem. I'm not very familiar with the programming constructs involved, so I can't tell you how efficient or elegant this solution is. The point is to run your infinite loop in a separate thread, thereby preventing your main python process from being stuck in the loop. This will keep the GUI responsive. However, this can lead to problems if you want to interrupt your program during its run.
The code:
import time
import threading #this is new
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.widgets as widgets
fig, ax = plt.subplots()
# it is not always column 0 and 1
sctPlot = ax.scatter([-0.3,0,0.3], [0.3,0.3,0.3], c="blue", picker = 2, s=[50]*3)
fig.subplots_adjust(bottom=0.3, left=0.1)
plt.grid(False)
plt.axis([-0.6, 0.6, -0.6, 0.6])
loopBool = True
def closeLooping(event):
global loopBool
loopBool = False
def looping(event):
global loopBool
while (loopBool == True):
print("Still Looping!")
time.sleep(1)
print("Stop!!")
def looping_pre(event): #this is new
thread = threading.Thread(target=looping, args=(event,))
#thread.daemon = True #might or might not be needed
thread.start()
axCloseButton = plt.axes([0.1, 0.15, 0.2, 0.06])
bClose = widgets.Button(axCloseButton, "Close", hovercolor = '0.975')
bClose.on_clicked(closeLooping)
plt.show() #this is new
fig.canvas.mpl_connect('pick_event', looping_pre) #this is changed
Note that I changed a few things because your exact code didn't seem to run properly for me. I removed color=axcolor from the Button call; and I added a plt.show() before the event connect, otherwise a figure window didn't appear for me (neither through ipython, nor with python).
The relevant addition is the threading module and the looping_pre front-end, which calls the looping function as a separate Thread. For this reason the 'pick_event' doesn't call looping, but rather looping_pre.
This code will (when running in ipython) show the figure window, start looping on a click to the data, then stop looping on button click. However, when I push ctrl+c, the loop keeps on going, since it is a separate thread. I only managed to kill it by using a reset, thereby removing the value of the global loopBool. The commented line specifying whether the Thread should be deamonized should affect this behaviour (by which I mean that it would seem logical to me), but I didn't see any effect.