create list of widgets of n length where n is set by another widget - python-3.x

I want to have the output of the size widget determine the number of widgets created in the output of the return_widgets function. The code below works to a point, but does not update when the value of the intslider changes.
import ipywidgets as widgets
def return_widgets(size):
return [widgets.IntText(value=x) for x in range(size)]
size = widgets.IntSlider(value=3, min=0, max=5, step=1, description='size:')
w = return_widgets(size.value)
widgets.VBox([size, *w])

I did this, not sure it's a very good solution but kind of works:
import ipywidgets as widgets
from IPython.display import display
def return_widgets(size):
return [widgets.IntText(value=x) for x in range(size)]
out = widgets.Output()
def on_value_change(change):
out.clear_output()
with out:
w = return_widgets(change['new'])
display(*w)
size = widgets.IntText(value=3, min=1, max=5, description='size')
size.observe(on_value_change, 'value')
widgets.VBox([size, out])

Related

Holoviews heatmap animation labelling slider

I have created an animated heatmap using holoviews with the 'bokeh' renderer. The code is below and runs in a Jupyter Notebook. Basically I compose a dictionary of heatmaps then I use 'hv.DynamicMap' and 'hv.streams' to create the animation ie. stream keys of dict and render the associated heatmap.
The code runs successfully as an animation but produces an output where a single frame looks like this:
I have two questions:
I want the slider to show the 'key' of the dictionary not just the
index of the list of keys as it does now. So in the image above 'a0' not '0'. How can I do that?
there are warnings being thrown about 'fixed_width'. I have set the
height and width of all the objects so why is there a warning?
import numpy as np
import holoviews as hv
from bokeh.io import show, curdoc, output_notebook
from bokeh.layouts import layout
from bokeh.models import Slider, Button
renderer = hv.renderer('bokeh').instance(mode='server')
output_notebook()
# generate a dict of heatmaps
heatmap_dict = {}
for i in range(10):
heatmap = hv.HeatMap((np.random.randint(0, 10, 100), np.random.choice(['A', 'B', 'C', 'D', 'E'], 100),
np.random.randn(100), np.random.randn(100)), vdims=['z', 'z2']).sort().aggregate(function=np.mean)
heatmap.opts(height=400, width=400)
heatmap_dict['a' + str(i)] = heatmap
heatmap_keys = list(heatmap_dict.keys())
# Create the holoviews app again
def mapping(phase):
key = heatmap_keys[phase]
return heatmap_dict[key]
stream = hv.streams.Stream.define('Phase', phase=0)()
dmap = hv.DynamicMap(mapping, streams=[stream])
# Define valid function for FunctionHandler
# when deploying as script, simply attach to curdoc
def modify_doc(doc):
# Create HoloViews plot and attach the document
hvplot = renderer.get_plot(dmap, doc)
# Create a slider and play buttons
def animate_update():
year = slider.value + 1
if year > end:
year = start
slider.value = year
def slider_update(attrname, old, new):
# Notify the HoloViews stream of the slider update
stream.event(phase=new)
start, end = 0, len(heatmap_keys) - 1
slider = Slider(start=start, end=end, value=start, step=1, title="Phase", height=30, width=180)
slider.on_change('value', slider_update)
callback_id = None
def animate():
global callback_id
if button.label == '► Play':
button.label = '❚❚ Pause'
callback_id = doc.add_periodic_callback(animate_update, 50)
else:
button.label = '► Play'
doc.remove_periodic_callback(callback_id)
button = Button(label='► Play', width=60, height=30)
button.on_click(animate)
# Combine the holoviews plot and widgets in a layout
plot = layout([
[hvplot.state],
[slider, button]], sizing_mode='fixed')
doc.add_root(plot)
return doc
# To display in the notebook
show(modify_doc, notebook_url='localhost:8888')

How can the `_property_values` of an element of a bokeh `figure.renderers` be changed directly?

How can the _property_values of an element of a bokeh figure.renderers be changed directly? I learned that the lements of renderers have an id, so I expect to do something like renderers['12345']. But as it is a list (a PropertyValueList to be more precise), this doesn't work. Instead, the only solution I found is to iterate over the list, storing the correct element in a new pointer (?), modifying the pointer and thus modifying the original element.
Here is my toy example where a vertical line in a histogram is updated based on some widget's value:
import hvplot.pandas
import ipywidgets as widgets
import numpy as np
from bokeh.io import push_notebook, show, output_notebook
from bokeh.models import Span
from bokeh.plotting import figure
%matplotlib inline
hist, edges = np.histogram([1, 2, 2])
p = figure()
r = p.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:])
vline = Span(location=0, dimension='height')
p.renderers.extend([vline])
def update_hist(x):
myspan = [x for x in p.renderers if x.id==vline.id][0]
myspan._property_values['location'] = x
show(p, notebook_handle=True)
widgets.interact(update_hist, x = widgets.FloatSlider(min=1, max=2))
Bigreddot pointed me into the right direction: I don't have to update p directly, but the elements used to generate p (here the Span). By this I found the this question where the code bears the solution: update vline.location.
Full code:
import hvplot.pandas
import ipywidgets as widgets
import numpy as np
from bokeh.io import push_notebook, show, output_notebook
from bokeh.models import Span
from bokeh.plotting import figure
%matplotlib inline
hist, edges = np.histogram([1, 2, 2])
p = figure()
r = p.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:])
vline = Span(location=0, dimension='height')
p.renderers.extend([vline])
show(p, notebook_handle=True)
def update_hist(x):
vline.location = x
push_notebook()
widgets.interact(update_hist, x = widgets.FloatSlider(min=1, max=2, step = 0.01))
As a Python beginner, I still often oversee, that Python does not have variables. So we can change an element x by changing y.
x = ['alice']
y = x
y[0] = 'bob'
x # is now ['bob] too

How to animate multiple figures at the same time

I made an animation for sorting algorithms and it it works great for animating one sorting algorithm, but when I try to animate multiple at the same time both windows come up but none of them are moving. I was wondering how I could go around to fix this.
When I run the code the first figure is stuck on the first frame and the second figure jumps to the last frame
import matplotlib.pyplot as plt
from matplotlib import animation
import random
# my class for getting data from sorting algorithms
from animationSorters import *
def sort_anim(samp_size=100, types=['bubblesort', 'quicksort']):
rndList = random.sample(range(1, samp_size+1), samp_size)
anim = []
for k in range(0, len(types)):
sort_type = types[k]
animation_speed = 1
def barlist(x):
if sort_type == 'bubblesort':
l = bubblesort_swaps(x)#returns bubble sort data
elif sort_type == 'quicksort':
l = quicksort_swaps(x)#returns quick sort data
final = splitSwaps(l, len(x))
return final
fin = barlist(rndList)
fig = plt.figure(k+1)
plt.rcParams['axes.facecolor'] = 'black'
n= len(fin)#Number of frames
x=range(1,len(rndList)+1)
barcollection = plt.bar(x,fin[0], color='w')
anim_title = sort_type.title() + '\nSize: ' + str(samp_size)
plt.title(anim_title)
def animate(i):
y=fin[i]
for i, b in enumerate(barcollection):
b.set_height(y[i])
anim.append(animation.FuncAnimation(fig,animate, repeat=False,
blit=False, frames=n, interval=animation_speed))
plt.show()
sort_anim()
As explained in the documentation for the animation module:
it is critical to keep a reference to the instance object. The
animation is advanced by a timer (typically from the host GUI
framework) which the Animation object holds the only reference to. If
you do not hold a reference to the Animation object, it (and hence the
timers), will be garbage collected which will stop the animation.
Therefore you need to return the references to your animations from your function, otherwise those objects are destroyed when exiting the function.
Consider the following simplification of your code:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def my_func(nfigs=2):
anims = []
for i in range(nfigs):
fig = plt.figure(num=i)
ax = fig.add_subplot(111)
col = ax.bar(x=range(10), height=np.zeros((10,)))
ax.set_ylim([0, 1])
def animate(k, bars):
new_data = np.random.random(size=(10,))
for j, b in enumerate(bars):
b.set_height(new_data[j])
return bars,
ani = animation.FuncAnimation(fig, animate, fargs=(col, ), frames=100)
anims.append(ani)
return anims
my_anims = my_func(3)
# calling simply my_func() here would not work, you need to keep the returned
# array in memory for the animations to stay alive
plt.show()

Interaction with multiple widgets Bokeh

EDIT: Some users have mentioned that the question is unclear. My objective is to keep track of previous states.
I am trying to create a plot that can be modified using 3 widgets. However, every new widget change does not take into consideration previous widget selections (for example, if a selection is made using widget 1 and then widget 2 is modified, the modification of widget 2 considers the original graph and not the changes made with widget 1).
I am trying to avoid using Custom_JS as I have no experience with Javascript. Is there any way to combine the functions so that any change in the widgets takes into consideration previous widget interactions?
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
from datetime import date
from ipywidgets import interact
from bokeh.io import push_notebook, show, output_notebook, curdoc
from bokeh.plotting import figure
from bokeh.layouts import column, layout, widgetbox, row
from bokeh.models import ColumnDataSource, HoverTool, CustomJS, Panel
from bokeh.layouts import widgetbox
from bokeh.models.widgets import RangeSlider, Slider, Select,
DateRangeSlider, Tabs
df = pd.read_csv("/Users/danielmontillanavas/Desktop/Tiller/00_Data/SF_may_correct_decimal.csv", sep =',',decimal=',')
df = df.drop(['Unnamed: 0'], axis=1)
df.rename(columns = {'Stamped phone from HS':'phone','Contact Email':'Email','Account Name':'Account_name',
'Opportunity ID':'ID', 'Close Date':'Close_date','Stamped-date of closed (DO NOT USE)':'Stamped_date',
'Quote Amount':'Quote_Amount', 'Lead Source':'Source','Desired activation date':'Activ_date'},
inplace=True)
df.Close_date = pd.to_datetime(df.Close_date, format='%Y-%m-%d')
cols_num = ['Quote_Amount','DISCOUNT']
df[cols_num] = df[cols_num].apply(pd.to_numeric)
df_closed = df[df['Stage']=='Closed']
df_closed.fillna("Unknown", inplace=True)
start_point = min(df['Quote_Amount'])
end_point = max(df['Quote_Amount'])
TOOLS = 'pan,wheel_zoom,box_zoom,reset,tap,save,box_select,lasso_select'
source = ColumnDataSource(df_closed)
hover = HoverTool(
tooltips=[
("Quote", "$x"),
("Discount", "$y")
]
)
p = figure(title='Quotes per Source - Closed deals',tools=[hover,TOOLS],
plot_height=800, plot_width=800)
p.circle('Quote_Amount','DISCOUNT',source=source, size = 8, color = 'CornflowerBlue', alpha = 0.6)
N = 20000
slider = Slider(start=start_point, end=end_point, step=10, value=N,
title='Select Quote Amount Cutoff')
dfList = df_closed.Source.unique().tolist()
All_view = ['All']
source_options = All_view + dfList
menu = Select(title = "Select Lead Source",options=source_options, value = 'All')
first_date = min(df['Close_date'])
last_date = max(df['Close_date'])
date_range_slider = DateRangeSlider(title="Select Date Range ", start=first_date, end=date.today(), value=(date(2017, 9, 7), date(2017, 10, 15)), step=1)
def slider_callback(attr, old, new):
N = new # this works also with slider.value but new is more explicit
new1 = ColumnDataSource(df_closed.loc[(df_closed.Quote_Amount < N)])
source.data = new1.data
slider.on_change('value',slider_callback)
def menu_callback(attr, old, new):
if menu.value == 'All': new2 = ColumnDataSource(df_closed)
else: new2 = ColumnDataSource(df_closed.loc[(df_closed.Source == menu.value)])
source.data = new2.data
menu.on_change('value',menu_callback)
def date_callback(attr, old, new):
start = date_range_slider.value_as_datetime[0].strftime("%Y-%m-%d")
end = date_range_slider.value_as_datetime[1].strftime("%Y-%m-%d")
df_closed_new = df_closed[df_closed['Close_date'] >= start]
df_closed_new = df_closed[df_closed['Close_date'] <= end]
new3 = ColumnDataSource(df_closed_new)
source.data = new3.data
date_range_slider.on_change('value',date_callback)
# Put controls in a single element
controls = widgetbox(menu, slider, date_range_slider)
# Create a row layout
layout = row(controls, p)
curdoc().add_root(layout)
You question is not very clear to me. Are you talking about the previous state of widgets? The callbacks have access to the current state of all the other widgets so if you want to maintain a history of their previous states you'll have to explicitly keep track of that.
However I immediately notice on thing that should not be done, so I am going to post an answer just to draw attention to it. Don't create new CDS objects just to use their .data attribute and throw them away:
new1 = ColumnDataSource(df_closed.loc[(df_closed.Quote_Amount < N)])
source.data = new1.data
There is alot of machinery under the covers that affords all the automatic synchronization other features of Bokeh. CDS in particular are extremely heavyweight, complicated objects, and doing this above is a known anti-pattern that can break things. Instead, if you just need a new suitable .data dict, then use from_df:
new_data = ColumnDataSource.from_df(df_closed.loc[(df_closed.Quote_Amount < N)])
source.data = new_data

PyQtGraph SpotItem returns 'NoneType' when calling user data

I am trying to get the associated user data of a point (which is a SpotItem instance) in a scatter plot when clicked on it. While methods listed in the documentation (like pos() or size()) seem to work fine, I recieve a NoneType object when I apply the data() method. I actually expected it to return my user data, but it doesn't.
So, how can I retrieve my associated original data that I put in?
What I actually need is something like an index i of the original input lists for a clicked point that would allow me to track back the corresponding x[i] y[i] set.
Here is my code sample:
import pyqtgraph as pg
#some dummy data
x=[0,1,2,3,4,5,3.5,3.4]
y=[5,4,3,2,1,0,3.4,3.5]
win=pg.GraphicsWindow()
p1=win.addPlot(row=1, col=1)
my_data=pg.ScatterPlotItem(x,y,symbol='o',size=30)
p1.addItem(my_data)
def clicked(items,points):
print("point data: ",points[0].data())
my_data.sigClicked.connect(clicked)
I am using Python 3.6 (with Spyder 3.1.4), Qt 5.6 and PyQt 5
sigClicked gave us the item (ScatterPlotItem) that has been pressed and the points (SpotItem) where they have been pressed, with the seconds we can obtain the element Point() that gives us the position, and this has the methods x() y y() that return the coordinates. From item we can get all the x and y that you have initially placed through data['x'] and data['y'], respectively, then we have the points pressed and all the points possible so to find the indexes we use np.argwhere() and then we intersect the values with np.intersect1d(), at the end we eliminate the repeated points with set.
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
app = QtGui.QApplication([])
x=[0,1,2,3,4,5,3.5,3.4, 3.4]
y=[5,4,3,2,1,0,3.4,3.5, 3.5]
win=pg.GraphicsWindow()
p1=win.addPlot(row=1, col=1)
my_data=pg.ScatterPlotItem(x,y,symbol='o',size=30)
p1.addItem(my_data)
def clicked(item, points):
indexes = []
for p in points:
p = p.pos()
x, y = p.x(), p.y()
lx = np.argwhere(item.data['x'] == x)
ly = np.argwhere(item.data['y'] == y)
i = np.intersect1d(lx, ly).tolist()
indexes += i
indexes = list(set(indexes))
print(indexes)
my_data.sigClicked.connect(clicked)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
like this:
def clicked(items,obj, points):
now_point = point[0].pos()
x = now_point[0]
y = now_point[1]
Then you can get what you want.

Resources