I am trying to embed a streaming bokeh plot into an HTML file using the autoload_server function:
from bokeh.client import push_session
from bokeh.embed import autoload_server
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
data = dict(x=[], y=[])
source = ColumnDataSource(data)
plot = figure()
plot.circle(source=source, x='x', y='y')
counter = -1
def update_data():
global xDate, yWind, counter
counter += 1
xDate = counter
yWind = counter
new_data_wind = dict(x=[xDate], y=[yWind])
source.stream(new_data_wind, 300)
curdoc().add_root(plot)
curdoc().add_periodic_callback(update_data, 300)
session = push_session(curdoc())
script = autoload_server(plot, session_id=session.id)
print(script)
I basically start a bokeh server by using: "bokeh serve" and then run the code and insert the given script into an HTML file.
At first, no plot would be displayed, but after adding --allow-websocket-origin=localhost:63342 to the bokeh serve command, the page would show the plot grid, but no data is displayed.
Does someone have an idea as to why the data streaming function doesn't seem to work or what I can change to make the embedded plot stream the data?
I'm thankful for any further input, since I have yet to find some on the Internet.
EDIT
I've found the solution to my problem and will leave it here if anyone encounters something similar:
The code fragment:
session.loop_until_closed()
needs to be added to the end of the example above, so the session is looped and the final plot gets updated inside the browser.
I'll just post my answer as seen above, so this won't show up as unanswered question anymore:
The code fragment:
session.loop_until_closed()
needs to be added to the end of the example above, so the session is looped and the final plot gets updated inside the browser.
Related
I try to do back testing on stock data using backtrading library in python. and I use this simple strategy
class CrossOver(bt.SignalStrategy):
def __init__(self):
sma=bt.ind.SMA(period=50)
price=self.data
crossover=bt.ind.CrossOver(price,sma)
self.signal_add(bt.SIGNAL_LONG,crossover)
Then I run it and try to plot it and display in streamlit
cerebro=bt.Cerebro()
cerebro.addstrategy(CrossOver)
cerebro.adddata(data)
cerebro.run()
pl=cerebro.plot()
st.pyplot(pl)
But I am not able to see the graph in streamlit. does anyone know how to display backtrader's graph in streamlit? thanks in advance.
I'm not that familiar with backtrader so i took an example from their documentation on how to create a plot. The data used in the plot can be downloaded from their github repository.
The solution contains the following steps:
make sure we use a matplotlib backend that doesn't display the plots to the user because we want to display it in the Streamlit app and the plot() function of backtrader displays the plot. this can be done using:
matplotlib.use('Agg')
get the matplotlib figure from the plot() function of backtrader. this can be done using:
figure = cerebro.plot()[0][0]
display the plot in streamlit. this can be done using:
st.pyplot(figure)
All together:
import streamlit as st
import backtrader as bt
import matplotlib
# Use a backend that doesn't display the plot to the user
# we want only to display inside the Streamlit page
matplotlib.use('Agg')
# --- Code from the backtrader plot example
# data can be found in there github repo
class St(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data)
data = bt.feeds.BacktraderCSVData(dataname='2005-2006-day-001.txt')
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(St)
cerebro.run()
figure = cerebro.plot()[0][0]
# show the plot in Streamlit
st.pyplot(figure)
Output:
I'm trying to update a figure that is being run on a bokeh server. I found out that you can use figure.extra_y_ranges = {'name':Range1d(0,10)} and then calling figure.add_layout(LinearAxis(y_range_name='name'),'right') to add a new y-axis with the range of 0-10 to my figure. However this only works for me if I do it right after initiating the figure. What I'm trying to do however is that the new y-axis is being added on a button click.
Here is a simplified version of the code I'm running:
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models import LinearAxis, Range1d
from bokeh.models.widgets import Button
from bokeh.layouts import column, row, grid
def update_fig(fig):
fig.extra_y_ranges = {'a':Range1d(0,10)}
fig.add_layout(LinearAxis(y_range_name='a', axis_label='a'), 'left')
def update_axis():
update_fig(fig)
fig = figure()
fig.circle_dot([1,2,3,4],[1,2,3,4])
update_fig(fig) # This works
value_sel = Button(label='Add y-axis')
value_sel.on_click(update_axis) # This doesn't work
layout = row(
column(value_sel),
grid(fig)
)
curdoc().add_root(layout)
I'm then running the function with bokeh serve --show example.py
I'd appreciate any help, thanks!
I am now using a workaround where I remove the figure from the layout, then add the second y axis and then add the figure back to the layout incase anyone is having the same problem.
A better version that was suggested to me is setting the different y axes and the start and then activating/deactivating their visibility
I am trying to build an interactive data visualization tool using bokeh layouts, but I am running into issues when generating and visualizing the plots. When running bokeh serve --show MWE1.py, I get the following error message "Only LayoutDOM items can be inserted into a column. Tried to insert: None of type " and no plots are generated in my browser window.
When running the code from the command python MWE1.py a plot is generated in a browser window, but no slider bar is present. I have also tried to remove the column layout tool from curdoc() but this didn't seem to help. Is there an issue passing functions that generate plots through curdoc(), and if so, is there an alternative solution?
(As an aside, I have also tried several of the tutorials and examples available online, all of which have worked as intended)
See MWE below:
import bokeh
from bokeh.io import curdoc
from bokeh import layouts
from bokeh.layouts import column,row,gridplot
from bokeh.models import ColumnDataSource, Slider
from bokeh.io import output_file
from bokeh.plotting import figure,show
x=[1, 2, 3]
y=[4, 5, 6]
def p(x,y):
p = figure()
p.line(x,y)
show(p)
q = p(x,y)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
curdoc().add_root(column(freq,q))'''
You function p is wrong:
It doesn't return the plot
It calls show which doesn't work with bokeh serve
Try this instead
def p(x,y):
p = figure()
p.line(x,y)
return p
Sorry if this is a basic question, but I haven't been able to find an answer in the bokeh documentation. I want to be able to plot a bokeh plot without the long GlyphRenderer list displaying.
I have tried saving the p.hexbin line to a variable called 'test'. However, this new 'test' variable is being saved as a tuple and can no longer be used with the 'show()' function to display a bokeh plot. The example code I am using here is straight from the bokeh documentation site.
import numpy as np
from bokeh.models import HoverTool
from bokeh.plotting import figure, show
x = 2 + 2*np.random.standard_normal(500)
y = 2 + 2*np.random.standard_normal(500)
p = figure(match_aspect=True, tools="wheel_zoom,reset")
p.background_fill_color = '#440154'
p.grid.visible = False
p.hexbin(x, y, size=0.5, hover_color="pink", hover_alpha=0.8)
hover = HoverTool(tooltips=[("count", "#c"), ("(q,r)", "(#q, #r)")])
p.add_tools(hover)
show(p)
I only want the hexbin plot to display when I run the code, not the Glyph tuple.
I have tried saving the p.hexbin line to a variable called 'test'. However, this new 'test' variable is being saved as a tuple and can no longer be used with the 'show()' function to display a bokeh plot.
Printing outputs is standard Python behavior, there is nothing we can do about that. The function returns a list, so Python will print a list. The only thing to suppress that behavior, as you have noted, is to assign the output to a variable. However, since you don't care about its value, it can/should be ignored. There is no reason to pass it to show, you should continue to call show, on p, exactly the way you have been without any change:
rs = p.hexbin(x, y, size=0.5, hover_color="pink", hover_alpha=0.8)
show(p)
I've spent the last few weeks learning the Bokeh package (which for visualizations, is excellent in my opinion).
Unfortunately, I have come across a problem that I can't for the life of me, figure out how to solve.
The below two links have been helpful, but I can't seem to replicate for my problem.
Using bokeh to plot interactive pie chart in Jupyter/Python - refer to answer #3
https://github.com/bokeh/bokeh/blob/0.12.9/examples/howto/notebook_comms/Jupyter%20Interactors.ipynb
The below code (in Jupyter) displays the graph correctly and displays the slider correctly, but I'm unsure how to connect the two as when I move the slider, the graph remains static.
I am using Python 3.6 and Bokeh 12.9
N = 300
source = ColumnDataSource(data={'x':random(N), 'y':random(N)})
plot = figure(plot_width=950, plot_height=400)
plot.circle(x='x', y='y', source=source)
callback = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
var kernel = IPython.notebook.kernel;
cmd = "update_plot(" + cb_obj.value + ")";
kernel.execute(cmd, {}, {})};
""")
slider = Slider(start=100, end=1000, value=N, step=10, callback=callback)
def callback(attr, old, new):
N = slider.value
source.data={'x':random(N), 'y':random(N)}
slider.on_change('value', callback)
layout = column(slider, plot)
curdoc().add_root(layout)
show(widgetbox(slider, width = 300))
show(plot)
After reading the bokeh documentation and reading a view threads on GitHub, the 'callback' function is a little unclear for me as I'm not entirely sure what to parse to it (if in fact attr, old, new need certain elements parsed too it)
Any help would be greatly appreciated
Hopefully, I haven't missed anything glaringly obvious.
Kind Regards,
Adrian
You are currently mixing different ways for interactivity but unfortunately you always miss something for each different way.
The slider you use is from bokeh, but unfortunately it looks like slider.on_change only works if you run through the bokeh server. From the documentation:
Use bokeh serve to start the Bokeh server and set up event handlers with .on_change (or for some widgets, .on_click).
I couldn't really find that much on running jupyter notebook and bokeh server, but this issue seems to discuss that possibility. It also mentions bokeh.application but I've never used that, so no idea how that works.
You also use additionally a custom js callback, which calls into the jupyter kernel and tries to execute update_plot(value), but you never defined such a function, so it does nothing.
Then you need a method to push the data to the output. I guess bokeh server can somehow do that nativly, for jupyter notebooks without the bokeh server push_notebook seems to be the solution. Note that you need show(..., notebook_handle=True) to be able to push.
Solution 1 use the bokeh server
Sliders and others widgets automatically sync their state back to python, so you can use slider.on_change. You don't need the CustomJS. Data flow should look as following:
python script -> bokeh server -> html -> userinput -> bokeh server -> python callbacks -> bokeh server updates plots
Solution 2 use bokeh sliders but sync via CustomJS
If you don't want to run a seperate process you can use the jupyter kernel to execute code in your python notebook. Dataflow:
jupyter notebook -> html -> user input -> customjs -> jupyter kernel -> python callbacks -> push_notebook to update plots
output_notebook()
N = 300
source = ColumnDataSource(data={'x':random(N), 'y':random(N)})
plot = figure(plot_width=950, plot_height=400)
plot.circle(x='x', y='y', source=source)
callback = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
var kernel = IPython.notebook.kernel;
cmd = "update_plot(" + cb_obj.value + ")";
kernel.execute(cmd, {}, {})};
""")
slider = Slider(start=100, end=1000, value=N, step=10, callback=callback)
# must have the same name as the function that the CustomJS tries to call
def update_plot(N):
source.data={'x':random(N), 'y':random(N)}
# push notebooks to update plots
push_notebook()
layout = column(slider, plot)
# notebook_handle must be true, otherwise push_notebook will not work
h1 = show(layout, notebook_handle=True)
Solution 3 use ipywidgets
If you are not married to the bokeh widgets you can use the ipywidgets which are designed for interactivity in the jupyter notebook. The data flow is as following:
jupyter notebook -> html -> user input -> ipywidgets sync automatically -> python callbacks -> push_notebook
I use here interact but the other widgets should work as expected.
from ipywidgets import interact
output_notebook()
N = 300
source = ColumnDataSource(data={'x':random(N), 'y':random(N)})
plot = figure(plot_width=950, plot_height=400)
plot.circle(x='x', y='y', source=source)
def update_plot(v):
N = v
print(N)
source.data={'x':random(N), 'y':random(N)}
# push changed plots to the frontend
push_notebook()
# notebook_handle must be true so that push_notebook works
show(plot, notebook_handle=True)
Note that you need to install ipywidgets properly, which inlcudes calling jupyter nbextension enable --py --sys-prefix widgetsnbextension if you are not using conda. For details see the documentation
I suppose your question relates to the server although you have both a CustomJS and a server callback.
I am not familiar with the previous way of doing bokeh server in notebook (push_notebook).
The new way would be like this: you wrap your code in a function taking one parameter (a document) and your call to add_layout is made on that document. Then you build an app with that function and show it.
This gives:
from bokeh.models import ColumnDataSource, Slider
from bokeh.layouts import column
from bokeh.plotting import figure, show, output_notebook
from numpy.random import random
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
output_notebook()
def modify_doc(doc):
N = 300
source = ColumnDataSource(data={'x':random(N), 'y':random(N)})
plot = figure(plot_width=950, plot_height=400)
plot.circle(x='x', y='y', source=source)
slider = Slider(start=100, end=1000, value=N, step=10)
def callback(attr, old, new):
N = new # but slider.value would also work
source.data={'x': random(N), 'y': random(N)}
slider.on_change('value', callback)
layout = column(slider, plot)
doc.add_root(layout)
app = Application(FunctionHandler(modify_doc))
show(app, notebook_url="localhost:8888")