Updating a graph using a slider with python and bokeh - python

Hello people of the internet,
So i tried to make a simple interactive visualisation where I can use a slide bar to change the radius of some dots on a plot using python and bokeh.
However, when I try to make an update function it seems that I get an error message saying:
500: Internal Server Error.
I also tried to make the update function such that it made a meaningless variable but the error message would still come up and show no plot or slider.
So my question is: How can I make a slider that will change the radius of the circle using a bokeh server?
# imports
from bokeh.io import push_notebook, show, output_notebook
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.layouts import column
from bokeh.models import Slider
from bokeh.plotting import figure
output_notebook()
# Webpage function, this function will be called upon creating the webpage
def make_page(doc):
#source = ColumnDataSource(data=dict(radius=20))
size=20
def update(attr, old, new):
s = new
radius = s
size.data = dict(radius)
plot = figure(plot_width=400, plot_height=400)
plot.circle([1,2], [4,8], size = size, color='blue', alpha=0.5);
slider = Slider(start=0, end=30, value=20, step=1, title="Number")
slider.on_change(update)
# adding the slider and plot to the document shown on the webpage
# all elements of the webpage have to be added below
doc.add_root(column(slider, plot))
# creating the application and running the server local, (http://localhost:5000), port 5000 can be changed
apps = {'/': Application(FunctionHandler(make_page))}
server = Server(apps, port=5000)
server.start()
Thanks in advance!

Already fixed it, here is the code
# imports
from bokeh.io import push_notebook, show, output_notebook
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.layouts import column
from bokeh.models import Slider
from bokeh.plotting import figure
from bokeh.plotting import ColumnDataSource
output_notebook()
# Webpage function, this function will be called upon creating the webpage
def make_page(doc):
#source = ColumnDataSource(data=dict(radius=20))
data = dict(
x=[1,2],
y=[4,8],
radius=[20,20]
)
source = ColumnDataSource(data=data)
def update(attr, old, new):
radius = new
new_data = dict(
x=[1,2],
y=[4,8],
radius=[radius,radius]
)
source.data= new_data
plot = figure(plot_width=400, plot_height=400)
plot.circle(x='x', y='y', size = 'radius', color='blue', alpha=0.5, source=source);
slider = Slider(start=0, end=30, value=20, step=1, title="Number")
slider.on_change('value', update)
# adding the slider and plot to the document shown on the webpage
# all elements of the webpage have to be added below
doc.add_root(column(slider, plot))
# creating the application and running the server local, (http://localhost:5000), port 5000 can be changed
apps = {'/': Application(FunctionHandler(make_page))}
server = Server(apps, port=5000)
server.start()

Related

Bokeh Server - Change color of glyph on select with Tap Tool

I want to use the TapTool with Bokeh server to run a callback with some additional functionality.
The glyphs I want to select are on top of a background image.
If I select a glyph with the TapTool the glyph keeps its opacity but all other glyphs get reduced in opacity. The problem is that those glyphs can not be seen well on the background image when they become more opace.
Is there a way to leave the alpha values of the glyphs all at 100% and instead change the color of the selected glyphs?
Here is some example code as a start that I found here
from bokeh import plotting as bplt
from bokeh import layouts as blayouts
from bokeh import models as bmodels
from bokeh import io as bio
fig = bplt.figure(tools="tap")
source = bmodels.ColumnDataSource(dict(x=[0,1], y=[0,1]))
r = fig.circle('x', 'y', source=source, size=10)
def handler(attr, old, new):
print('attr: {} old: {} new: {}'.format(attr, old, new))
# r.data_source.on_change('selected', handler)
r.data_source.selected.on_change('indices', handler)
bio.curdoc().add_root(blayouts.layout([[fig]]))
Here is a working example for bokeh 2.0.1:
from bokeh import plotting as bplt
from bokeh import layouts as blayouts
from bokeh import models as bmodels
from bokeh import io as bio
fig = bplt.figure(tools="tap")
source = bmodels.ColumnDataSource(dict(x=[0,1], y=[0,1]))
r = fig.circle('x', 'y', source=source, size=10, color='#000000',
# set visual properties for selected glyphs
selection_color="#2bff00",
# set visual properties for non-selected glyphs
nonselection_fill_alpha=1.0,
nonselection_fill_color="#000000")
def handler(attr, old, new):
print('attr: {} old: {} new: {}'.format(attr, old, new))
# r.data_source.on_change('selected', handler)
r.data_source.selected.on_change('indices', handler)
bio.curdoc().add_root(blayouts.layout([[fig]]))

curdoc().add_root() causes bokeh plot rendering to fail silently

The bokeh application below is intended to generate a random dataset when the button is pushed. I am trying to serve the app using the bokeh.client style, where there is one session that may be shared between simultaneous viewers.
If I include the line: curdoc().add_root(column(p,button)) the plot will not be in the browser. I get a blank page with happy messages in JS console. If I remove it, I get a static plot, with no button. Can anyone explain what's wrong with my approach here?
I should add that the app works in the other server style with multiple distinct sessions. There I call bokeh serve myapp.py and don't make calls to the session object.
import numpy as np
from bokeh.plotting import figure, curdoc
from bokeh.layouts import column
from bokeh.models import Button
from bokeh.client import push_session, pull_session
points = 100*np.random.rand(3,100)
points_x = points[0].tolist()
points_y = points[1].tolist()
p = figure(x_range=(0,100), y_range=(0,100))
circle_p = p.circle(x = points_x,
y = points_y,
size = 20,
color = "navy",
alpha = 0.5)
ds = circle_p.data_source
#callback function to update circles
def button_callback():
new_data = dict()
new_points = 100*np.random.rand(3,100)
new_data['x'] = new_points[0].tolist()
new_data['y'] = new_points[1].tolist()
new_data['z'] = new_points[2].tolist()
ds.data = new_data
#Add the button widget thingie to trigger the update
button = Button(label="Update")
button.on_click(button_callback)
# Put the button and plot in a layout in document
curdoc().add_root(column(p,button))
#create a session
session = push_session(curdoc())
session.show(p)
session.loop_until_closed()
You just want
session.show()
not
session.show(p)
Because you want to show the whole document, not just the plot. The first version works for me with Bokeh 0.12.6 (the latter also kind of works, but the plot is duplicated twice. My guess is you are using an older version that also had some layout bugs)

Embedding matplotlib into a tkinter application

I've got a program that on launch will open a matplotlib graph of bitcoin prices and will update every time the price changes. Then when I close it a fullscreen tkinter application opens with a background image and a updating clock in the corner. What I would like to happen is when I launch the program a fullscreen tkinter application opens with a background image, an updating clock in the corner, and the matplotlib graph in the middle. Basically I want to embed the matplotlib graph into my application. I've tried putting the graph code into my class and calling it but that just gives the results mentioned earlier. I tried replacing the plt.show() with this:
canvas = FigureCanvasTkAgg(fig, self)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
but then the program wasn't launching at all. No matter where I put it in the script it would give errors like ''self' is not defined' when I directly replaced plt.show()with it and ''canvas' is not defined' when I tried to integrate it into the class. Here is the full code below:
from tkinter import *
from PIL import ImageTk, Image
import os
import time
import requests
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
from tkinter import ttk
import urllib
import json
import pandas as pd
import numpy as np
from PyQt5 import QtWidgets
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
class App(tk.Tk):
def __init__(self):
# call the __init__ method of Tk class to create the main window
tk.Tk.__init__(self)
# background image
img = ImageTk.PhotoImage(Image.open("0.png"))
panel = Label(self, image=img)
panel.pack()
# clock
self.label = tk.Label(self, text="", font=('comic',50,'bold'),
bg='#464545', fg='#1681BE')
self.label.place(height=204, width=484, x=1384, y=824)
self.update_clock()
# window geometry
w, h = self.winfo_screenwidth(), self.winfo_screenheight()
self.geometry("%dx%d+0+0" % (w, h))
self.overrideredirect(True)
self.mainloop()
def update_clock(self):
now = time.strftime('%H:%M:%S')
self.label.configure(text=now)
self.after(1000, self.update_clock)
def animate(self):
dataLink = 'https://btc-e.com/api/3/trades/btc_usd?limit=2000d'
data = urllib.request.urlopen(dataLink)
data = data.read().decode("utf-8")
data = json.loads(data)
data = data["btc_usd"]
data = pd.DataFrame(data)
buys = data[(data['type']=="ask")]
buys["datestamp"] = np.array(buys["timestamp"]).astype("datetime64[s]")
buyDates = (buys["datestamp"]).tolist()
plot(buyDates, buys["price"])
xlabel('time')
ylabel('Temperature')
title('Temperature Chart')
grid(True)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
app = App()
There are a few dependable's like '0.png' which is the background image and of course the many modules but any feedback would be much appreciated.
plt.show() will open a new dedicated GUI window to host the figure(s) currently present in the matplotlib state machine. If you don't want that, don't use plt.show().
To be on the save side, best don't use pyplot at all as even such innocent commands as plt.xticks() can cause a new window to open.
Also, get rid of from pylab import *. Pylab is a construct which includes several packages like numpy and pyplot in a single namespace. This may be desired for interactive notebook-like applications, but is usually a bad idea for scripts, as it makes debugging very cumbersome. Instead, try to fully stick to matplotlib API commands.
This linked question actually is an example of how to use FigureCanvasTkAgg and NavigationToolbar2Tk to embed a figure into tk.
A better start might be to look at the matplotlib example page where a minimal example for tk embedding is presented.

Adding objects dynamically in bokeh server application

I would like to add objects dynamically on the bokeh server. The example I am trying to run is the following bokeh server app:
from bokeh.layouts import column
from bokeh.plotting import curdoc
from bokeh.models import Button
def add_button():
print("adding button")
curdoc().add_root(column(button, button2))
button = Button(label="Start", button_type="success")
button.on_click(add_button)
button2 = Button(label="Next", button_type="success")
curdoc().add_root(column(button))
Many thanks for any help.
Did you want to keep adding a new button each time?
if so try this :
from bokeh.layouts import column, layout
from bokeh.plotting import curdoc
from bokeh.models import Button
from bokeh.models.widgets import Div
def add_button():
print("adding button")
layout.children.append(Button(label="Hi I am another button", button_type="success"))
button = Button(label="Click to add a button", button_type="success")
button.on_click(add_button)
layout = layout([[button]])
curdoc().add_root(layout)
If you only wanted to add a new button once, then just append Button2.

Python Bokeh send additional parameters to widget event handler

I want to send additional data to a bokeh event handler (e.g. an on_change or on_click method). A minimal example that increments or decrements an integer is below (I run this app with 'bokeh serve --show app.py). I had to write separate event handlers that do almost identical things in this example. To write this app with just one event handler function, I need to pass additional data or the event handler must know the calling object. How do I do that?
from bokeh.plotting import curdoc
from bokeh.models.widgets import Button, Paragraph
from bokeh.layouts import widgetbox
minus = Button(label='-')
plus = Button(label='+')
text = Paragraph(text='0')
def minus_callback():
text.text = str(int(text.text) - 1)
def plus_callback():
text.text = str(int(text.text) + 1)
minus.on_click(minus_callback)
plus.on_click(plus_callback)
# I would prefer to just use one callback and pass additional data to it:
# minus.on_click(callback, action='decrement')
# plus.on_click(callback, action='increment')
layout = widgetbox(minus, plus, text)
curdoc().add_root(layout)
The standard functools.partial facility that is built into python works fine with Bokeh callbacks.
from functools import partial
from bokeh.plotting import curdoc
from bokeh.models.widgets import Button, Paragraph
from bokeh.layouts import widgetbox
minus = Button(label='-')
plus = Button(label='+')
text = Paragraph(text='0')
def callback(foo):
print(foo)
minus.on_click(partial(callback, foo="minus"))
plus.on_click(partial(callback, foo="plus"))
layout = widgetbox(minus, plus, text)
curdoc().add_root(layout)
Extending on #bigreddot's answer, this code outputs the updated value to the text attribute of the Paragraph object.
from functools import partial
from bokeh.plotting import curdoc
from bokeh.models.widgets import Button, Paragraph
from bokeh.layouts import column
def callback(update):
text.text = str(int(text.text) + update)
minus = Button(label='-')
plus = Button(label='+')
text = Paragraph(text='0')
minus.on_click(partial(callback, update=-1))
plus.on_click(partial(callback, update=+1))
layout = column(minus, plus, text)
curdoc().add_root(layout)

Categories

Resources