Bokeh Sliders associated with a plot (without bokeh server) - python

I'm trying to plot my 1D PDE in time using bokeh. I have a nparray U where each line is a time slice( dt ) and each column is a space slice (dx), therefore U[0] is my initial condition and U[n] is my n-th iteration.
Of course if I ask to plot(x,U[t]) for every t, I get the correct plot (as good as my approximations can be ;-), but I would like associate this with a time slider for interactivity (of course in future I would like to "play" it as animation ;-)
For now I would like to avoid to use bokeh server, since I want that this HTML be an stand alone "application"
The issue here is that callbacks just don't work, or at least I'm not being able to update the graph. I don't want to run a "bokeh" server since all my data is already inside a numpy data structure (U array). Every line is a iteration in time and every column is a dx.
#!/usr/bin/env python
from __future__ import division
from bokeh.models import ColumnDataSource, HBox, VBoxForm, HoverTool
from bokeh.models.widgets import Slider, TextInput
from bokeh.plotting import Figure, output_file, show
import numpy as np
def linearconv(nx,c=1,sigma=0.5,tmax=1,xmax=3):
nt=int((tmax/xmax)*((nx-1)/(c*sigma))+1) # Time Grid
x,dx=np.linspace(0,xmax,nx,retstep=True)
t,dt=np.linspace(0,tmax,nt,retstep=True)
# Initial conditions
#
# u=2 if 0.5 <= x <= 1
# u=1 everywhere else in the support
U = np.ones((nt,nx))
U[0][np.where((.5<=x) & (x<=1))]=2
# Calculate the wave over the time
for n in range(1,nt):
for i in range(1,nx):
U[n][i]= U[n-1][i] - c*dt/dx* ( U[n-1][i]-U[n-1][i-1] )
return U,x,t,dx,dt,nt
def prepareplot(height=400, width=400,title="Wave #"):
plot = Figure(plot_height=height, plot_width=width, title=title,
tools="crosshair,pan,reset,resize,save,wheel_zoom")
return plot
def update_ttime(attrname, old, new):
plot.title = "Wave #{}s".format(title.value)
def update_graph(attrname, old, new):
# Get the current slider values
t = time.value
source = ColumnDataSource(data=dict(x=x, t=U[t]))
plot.line('x','t', source=source)
plot.line('x','t', source=source)
nx=101
# Set up data
U,x,t,dx,dt,nt = linearconv(nx)
plot = prepareplot()
time = Slider(title="Time", value=t[-1], start=t[0], end=t[-1], step=dt)
ttime = TextInput(title="Time", value="{}".format(t[-1]))
source = ColumnDataSource(data=dict(x=x, t=U[-1]))
plot.line('x','t', source=source)
# Set up callbacks
ttime.on_change('value', update_ttime)
time.on_change('value', update_graph)
# Setup layouts
inputs = VBoxForm(children=[ttime,time])
layout = HBox(children=[inputs,plot], width=800)
# Plot the plot
output_file("sli.html")
show(layout)

Related

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.

Bokeh: linking a line plot and a scatter plot

I have a line plot and a scatter plot that are conceptually linked by sample IDs, i.e. each dot on the 2D scatter plot corresponds to a line on the line plot.
While I have done linked plotting before using scatter plots, I have not seen examples of this for the situation above - where I select dots and thus selectively view lines.
Is it possible to link dots on a scatter plot to a line on a line plot? If so, is there an example implementation available online?
Searching the web for bokeh link line and scatter plot yields no examples online, as of 14 August 2018.
I know this is a little late - but maybe this snippet of code will help?
import numpy as np
from bokeh.io import output_file, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.models import Circle,MultiLine
def play():
x = np.linspace(0,10,100)
y = np.random.rand(100)
xs = np.random.rand(100,3)
ys = np.random.normal(size=(100,3))
xp = [list(xi) for xi in xs] # Because Multi-List does not like numpy arrays
yp = [list(yi) for yi in ys]
output_file('play.html')
source = ColumnDataSource(data=dict(x=x,y=y,xp=xp,yp=yp))
TOOLS = 'box_select'
left = figure(tools=TOOLS,plot_width=700,plot_height=700)
c1 = left.circle('x','y',source=source)
c1.nonselection_glyph = Circle(fill_color='gray',fill_alpha=0.4,
line_color=None)
c1.selection_glyph = Circle(fill_color='orange',line_color=None)
right = figure(tools=TOOLS,plot_width=700,plot_height=700)
c2 = right.multi_line(xs='xp',ys='yp',source=source)
c2.nonselection_glyph = MultiLine(line_color='gray',line_alpha=0.2)
c2.selection_glyph = MultiLine(line_color='orange')
p = gridplot([[left, right]])
show(p)
As things turn out, I was able to make this happen by using HoloViews rather than Bokeh. The relevant example for making this work comes from the Selection1d tap stream.
http://holoviews.org/reference/streams/bokeh/Selection1D_tap.html#selection1d-tap
I will do an annotated version of the example below.
First, we begin with imports. (Note: all of this assumes work is being done in the Jupyter notebook.)
import numpy as np
import holoviews as hv
from holoviews.streams import Selection1D
from scipy import stats
hv.extension('bokeh')
First off, we set some styling options for the charts. In my experience, I usually build the chart before styling it, though.
%%opts Scatter [color_index=2 tools=['tap', 'hover'] width=600] {+framewise} (marker='triangle' cmap='Set1' size=10)
%%opts Overlay [toolbar='above' legend_position='right'] Curve (line_color='black') {+framewise}
This function below generates data.
def gen_samples(N, corr=0.8):
xx = np.array([-0.51, 51.2])
yy = np.array([0.33, 51.6])
means = [xx.mean(), yy.mean()]
stds = [xx.std() / 3, yy.std() / 3]
covs = [[stds[0]**2 , stds[0]*stds[1]*corr],
[stds[0]*stds[1]*corr, stds[1]**2]]
return np.random.multivariate_normal(means, covs, N)
data = [('Week %d' % (i%10), np.random.rand(), chr(65+np.random.randint(5)), i) for i in range(100)]
sample_data = hv.NdOverlay({i: hv.Points(gen_samples(np.random.randint(1000, 5000), r2))
for _, r2, _, i in data})
The real magic begins here. First off, we set up a scatterplot using the hv.Scatter object.
points = hv.Scatter(data, ['Date', 'r2'], ['block', 'id']).redim.range(r2=(0., 1))
Then, we create a Selection1D stream. It pulls in points from the points object.
stream = Selection1D(source=points)
We then create a function to display the regression plot on the right. There's an empty plot that is the "default", and then there's a callback that hv.DynamicMap calls on.
empty = (hv.Points(np.random.rand(0, 2)) * hv.Curve(np.random.rand(0, 2))).relabel('No selection')
def regression(index):
if not index:
return empty
scatter = sample_data[index[0]]
xs, ys = scatter['x'], scatter['y']
slope, intercep, rval, pval, std = stats.linregress(xs, ys)
xs = np.linspace(*scatter.range(0)+(2,))
reg = slope*xs+intercep
return (scatter * hv.Curve((xs, reg))).relabel('r2: %.3f' % slope)
Now, we create the DynamicMap which dynamically loads the regression curve data.
reg = hv.DynamicMap(regression, kdims=[], streams=[stream])
# Ignoring annotation for average - it is not relevant here.
average = hv.Curve(points, 'Date', 'r2').aggregate(function=np.mean)
Finally, we display the plots.
points * average + reg
The most important thing I learned from building this is that the indices for the points have to be lined up with the indices for the regression curves.
I hope this helps others building awesome viz using HoloViews!

Bokeh (0.12.1) Update DataColumnSource Selection programmatically with bokeh serve (Python only)

using Bokeh, I am trying to update the .selected dictionary of a ColumnDataSource programmatically, via the callback of a Slider, but cannot manage to get the selection reflected in the plot.
In the following snippet, the idea is that I want to be able to make a y-axis selection both via the ybox_select tool and/or by adjusting the sliders that control the position of a pair of min/max lines (NOTE: for brevity, in this example I only included the 'max' slider and line). If possible, I want to achieve this without using CustomJS callbacks.
I got as far as adjusting the horizontal line and the slider value (and of course the selection, which happens implicitly) when I operate the ybox_select tool (which triggers the selection_change function). Instead, when I operate the slider (triggering the slider_selection function), I manage to control the horizontal line but, apparently, not the source selection. In other words, the modification of source.data that occurs in slider_selection is reflected in the plot (i.e. the modified position of the horizontal line) but the modification of source.selected is NOT reflected in the plot (nor in a DataTable, as I verified separately).
Following the suggestion in this thread (where I've asked a shorter version of this question but didn't get any answers so far), I've worked on a copy of source.selected and then copied back to .selected (same for .data), but this didn't have any effects.
I must be missing something rather fundamental, but cannot figure out what. Any idea? Please avoid suggestions based on CustomJS, unless you're sure that there is no pure-Python alternative.
Thanks a lot for any feedback!
(Note: run this code as a script with bokeh serve --show script.py)
from bokeh.io import curdoc
from bokeh.models import BoxSelectTool, Slider
from bokeh.plotting import figure, ColumnDataSource
from bokeh.sampledata.glucose import data
from bokeh.layouts import column
import numpy as np
#===============================================================================
# Data and source
y = data.ix['2010-10-06']['glucose']
x = np.arange(len(y))
maxval=[max(y)]*len(x)
source = ColumnDataSource(dict(x=x, y=y, maxval=maxval))
#===============================================================================
# Basic plot setup
tools = 'wheel_zoom,ybox_select,reset'
p = figure(plot_width=800, plot_height=400, tools=tools, title='Min/max selection')
# Plot data
cr = p.circle('x', 'y', color="blue", source = source,
selection_color="blue", nonselection_color="gray",
size=6, alpha=0.8)
# Plot max horizontal line
p.line('x', 'maxval', line_color='blue', line_width=0.5, source=source,
nonselection_alpha=1.0, nonselection_color='blue')
#===============================================================================
# Callbacks
def selection_change(attrname, old, new):
ixs = new['1d']['indices']
if ixs:
arr = np.asarray(source.data['y'])[ixs]
max_slider.value = np.max(arr)
source.data['maxval'] = [np.max(arr)]*len(source.data['x'])
def slider_selection(attrname, old, new):
selected = source.selected.copy()
data = source.data.copy()
data['maxval'] = [max_slider.value]*len(data['x'])
yy = np.asarray(data['y'])
maxi = np.asarray(data['maxval'])
# Below is the new selection I would to visualize
selected['1d']['indices'] = np.where(yy <= maxi)[0].tolist()
# Updated data is reflected in the plot (horizontal line at 'maxval' moves)
source.data = data.copy()
# Updated selection is NOT reflected in the plot
# (nor in a DataTable, as tested separately)
source.selected = selected.copy()
#===============================================================================
# Slider
max_slider = Slider(start=min(y), end=max(y),
value=max(y), step=0.1, title="Maximum")
#===============================================================================
# Trigger callbacks
source.on_change('selected', selection_change)
max_slider.on_change('value', slider_selection)
#===============================================================================
# Layout
plot_layout = column(p, max_slider)
curdoc().add_root(plot_layout)
curdoc().title = "Demo"
Adding the following line to slider_selection seems to do what you want:
source.trigger("selected", old, selected)
the new function definition:
def slider_selection(attrname, old, new):
selected = source.selected.copy()
data = source.data.copy()
data['maxval'] = [max_slider.value]*len(data['x'])
yy = np.asarray(data['y'])
maxi = np.asarray(data['maxval'])
# Below is the new selection I would to visualize
selected['1d']['indices'] = np.where(yy <= maxi)[0].tolist()
# Updated data is reflected in the plot (horizontal line at 'maxval' moves)
source.data = data.copy()
# Updated selection is NOT reflected in the plot
# (nor in a DataTable, as tested separately)
source.selected = selected.copy()
source.trigger("selected", old, selected)
(Though it's a bit late, I found your question trying to find a similar answer, I figured this might be useful to others).

Animate GMapPlot w/ Python/Bokeh

I'm a very nooby programmer and this is my first Stack Overflow question. :)
So I'm trying to animate a car's trip on google maps using Python. I used matplotlib at first and could get a dot animated over the path line... then I tried using bokeh and successfully got the path to overlay on google maps...
My problem is that I haven't found a good way to do both (animate plot over google maps).
My data is in the form of Lat/Long coordinates.
Any advice? Thanks in advance!
EDIT: Here's my code that does the gmapplot... I'd rather have this and no animation than animation with no GMAP. My goal is to animate that "car" dot.
import numpy as np
from bokeh.io import output_file, show, vform
from bokeh.models.widgets import Dropdown
from bokeh.models import (GMapPlot, GMapOptions, ColumnDataSource, Line, Circle,
DataRange1d, PanTool, WheelZoomTool, BoxSelectTool, HoverTool)
data = np.genfromtxt('Desktop\Temp Data for Python\test data 3.csv', delimiter=',',
names=True)
map_options = GMapOptions(lat=np.average(data['Latitude']),
lng=np.average(data['Longitude']), map_type="roadmap", zoom=13)
plot = GMapPlot(x_range=DataRange1d(), y_range=DataRange1d(), map_options=map_options,
title="My Drive")
source = ColumnDataSource(data=dict(lat=data['Latitude'], lon=data['Longitude'],
speed=data['GpsSpeed'],))
path = Line(x="lon", y="lat", line_width = 2, line_color='blue')
car = Circle(x=data['Longitude'][0], y=data['Latitude'][0], size=5, fill_color='red')
plot.add_glyph(source, path)
plot.add_glyph(source, car)
plot.add_tools(PanTool(), WheelZoomTool(), BoxSelectTool(),
HoverTool(tooltips=[("Speed", "#speed"),]))
output_file("gmap_plot.html")
show(plot)
This may not be exactly what you are looking for, but you could have a slider widget that controls the position of your car dot. The slider example found in the bokeh docs (or github repository, I can't remember) helped me when I started using sliders.
Just so you are aware, I was having problems with latlng points showing up in the correct locations. There is about a 10px offset. This is an open issue (github issue 2964).
The following code currently is just producing a generic bokeh Figure, but in theory, if you change it from a Figure to a GMapPlot it should work. I wasn't able to get this working with GMapPlots directly. I think this may be because of github issue 3737. I can't even run the Austin example from the bokeh docs.
Hopefully this is what you had in mind
from bokeh.plotting import Figure, ColumnDataSource, show, vplot
from bokeh.io import output_file
from bokeh.models import (Slider, CustomJS, GMapPlot,
GMapOptions, DataRange1d, Circle, Line)
import numpy as np
output_file("path.html")
# Create path around roundabout
r = 0.000192
x1 = np.linspace(-1,1,100)*r
x2 = np.linspace(1,-1,100)*r
x = np.hstack((x1,x2))
f = lambda x : np.sqrt(r**2 - x**2)
y1 = f(x1)
y2 = -f(x2)
y = np.hstack((y1,y2))
init_x = 40.233688
init_y = -111.646784
lon = init_x + x
lat = init_y + y
# Initialize data sources.
location = ColumnDataSource(data=dict(x=[lon[0]], y=[lat[0]]))
path = ColumnDataSource(data=dict(x=lon, y=lat))
# Initialize figure, path, and point
"""I haven't been able to verify that the GMapPlot code below works, but
this should be the right thing to do. The zoom may be totally wrong,
but my latlng points should be a path around a roundabout.
"""
##options = GMapOptions(lat=40.233681, lon=-111.646595, map_type="roadmap", zoom=15)
##fig = GMapPlot(x_range=DataRange1d(), y_range=DataRange1d(), map_options=options)
fig = Figure(plot_height=600, plot_width=600)
c = Circle(x='x', y='y', size=10)
p = Line(x='x', y='y')
fig.add_glyph(location, c)
fig.add_glyph(path, p)
# Slider callback
callback = CustomJS(args=dict(location=location, path=path), code="""
var loc = location.get('data');
var p = path.get('data');
t = cb_obj.get('value');
/* set the point location to the path location that
corresponds to the slider position */
loc['x'][0] = p['x'][t];
loc['y'][0] = p['y'][t];
location.trigger('change');
""")
# The way I have written this, 'start' has to be 0 and
# 'end' has to be the length of the array of path points.
slider = Slider(start=0, end=200, step=1, callback=callback)
show(vplot(fig, slider))

How to make chaco plots use predefined colormap scale?

I have a set of data that represents thermocouple values at multiple points over time. Using Chaco I have managed to plot a heatmap of the thermocouple locations, with a slider that lets me pick the time step I want displayed. The purpose of this is to compare how the dataset changes over time.
The problem I am having is that the colormap scale changes based on the max and min values shown on screen, but I would like the colormap scale to stay fixed with a predetermined min and max. Is there an easy way to do this using Chaco and Traits? I have looked through the Chaco documentation, but none of the examples I have found cover this situation.
For simplicity, here is a pared down copy of my code. I have replaced my data with a generated dataset of the same shape, with the same value min and max.
import numpy as np
from enable.api import *
from chaco.shell import *
from traits.api import *
from traitsui.api import *
from chaco.api import *
from chaco.tools.api import *
## Random Data
data11 = np.ones([3,5,100])
print data11.shape
for i in range(3):
for j in range(5):
for k in range(100):
data11[i,j,k] = np.random.random_integers(1100)
class heatplots(HasTraits):
plot = Instance(Plot)
time = Range(1,99)
traits_view = View(Item('time'),
Item('plot', editor=ComponentEditor()),
width=1000, height=600, resizable=True, title='heatmap')
def datamaker(self): ## Setting the data time
t = self.time
## Defining the data picker sources
self.data = data11[...,...,t]
def heatplotter(self): ##Making the Plots
## The first heatmap
self.plotdata1 = ArrayPlotData(hm1 = self.data)
plot1 = Plot(self.plotdata1)
plot1.img_plot("hm1", colormap=hot)
self.plot = plot1
#_heatplotter()
#on_trait_change('time')
def _new_time(self):
self.datamaker()
self.heatplotter()
def start(self):
self._new_time()
self.configure_traits()
thing = heatplots()
thing.start()
If some of my formating seems odd or round-about its probably because my complete plot includes a datapicker where I can pick between data sets, and it has 2 heatmaps side-by-side in an HPlotContainer. I have removed that because it is not relevant to my question.
Thank you for your time.

Categories