Custom CSS in Bokeh server embedded in Flask Application - flask

I want to change the way tabs are rendered in a bokeh application embedded within a Flask Application, and cannot seem to find a way to do this with my current schema, which is as follows:
Bokeh is created in create_app(), borrowed from their server embed example:
def create_app():
#------------------IRRELEVANT FLASK APP STUFF--------------------------------
bkapp = Application(FunctionHandler(my_gui))
# This is so that if this app is run using something like "gunicorn -w 4" then
# each process will listen on its own port
sockets, port = bind_sockets("localhost", 0)
app._bokehport = port
def bk_worker():
asyncio.set_event_loop(asyncio.new_event_loop())
bokeh_tornado = BokehTornado({'/bkapp': bkapp},
extra_websocket_origins=["localhost:8000"])
bokeh_http = HTTPServer(bokeh_tornado)
bokeh_http.add_sockets(sockets)
server = BaseServer(IOLoop.current(), bokeh_tornado, bokeh_http)
server.start()
server.io_loop.start()
from threading import Thread
Thread(target=bk_worker).start()
return app
my_gui is a bokeh app, very simplified version here:
def my_gui(doc):
def toggleMakeCallback():
rootLayout = doc.get_model_by_name('rootLayout')
listOfSubLayouts = rootLayout.children
p1 = figure(width=300, height=300)
p1.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
tab1 = TabPanel(child=p1, title="circle")
p2 = figure(width=300, height=300)
p2.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=3, color="navy", alpha=0.5)
tab2 = TabPanel(child=p2, title="line")
tabs = Tabs(tabs=[ tab1, tab2 ])
listOfSubLayouts.append(tabs)
make_piece = Button(label="Make Piece", button_type="success")
make_piece.on_click(toggleMakeCallback)
rootLayout = layout(make_piece, name='rootLayout', sizing_mode="scale_both")
doc.add_root(rootLayout)
return doc
In my Flask application this is how I route to the app.
#main_bp.route('/bokehexample', methods = ['GET', 'POST'])
#login_required
def bokehRoute():
script = server_document('http://localhost:%d/bkapp' % current_app._bokehport,
arguments={"id": unsplitId, "teamId": teamId})
return render_template("embed.html", script=script, template="Flask")
Finally, this is embed.html:
<!DOCTYPE html>
<html lang='en'>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='css/embed.css') }}">
<title>PeachPortal - Split Pieces</title>
</head>
<body>
{{ script|safe }}
</body>
</html>
An example of a component of end goal CSS to change the tab design would be as follows:
.bk-root .bk-tabs-header .bk-tab.bk-active{
background-color: green;
color: red;
font-style: normal;
font-weight: bold;
}
I've read and tried and failed with several ways of accomplishing custom CSS (the app just renders the default way no matter what I try):
Adding this css directly in a <style></style> tag in the head of embed.html
Adding this css into the embed.css file referenced in the head of embed.html
Adding this css in the head of embed.html with a django {%include customcss.css%}
Adding a css class such as css_classes =['custom_tab_bokeh'] to my tabs
I've seen another example using curstate, which I don't know how to use in an app context.
I am hoping somebody has successfully changed the CSS for a Bokeh widget using this schema or something similar.
Thank you.

Related

Django Html and plots to PDF

I am using the Django application to generate some graphs using Plotly so
I tried to generate a pdf file of the following plots of
my HTML page is:
<center> <p class="lead"><strong><em> All Plots </em></strong></p></center>
<div class="row">
<div class="col-md-6" >
{{ sunburst|safe}}
</div>
<div class="col-md-6" >
{{ sunburst2|safe}}
</div>
</div>
and my url.py :
path('export_pdf/', views.export_pdf, name='export_pdf'),
and the views.py;
def export_pdf(request):
df = px.data.gapminder().query("country=='Canada'")
fig = px.line(df, x="year", y="lifeExp", title='Life expectancy in Canada')
df2 = px.data.gapminder().query("continent=='Oceania'")
fig2 = px.line(df2, x="year", y="lifeExp", color='country')
chart = fig.to_html()
chart2 = fig2.to_html()
context= {
'sunburst':chart,
'sunburst2':chart2,
}
return render(request, 'Home/Reporting/part_pdf.html',context)
I tried some code from this page but I can't generate the file any help?
https://plotly.com/python/v3/pdf-reports/
are there any other methods for doing that (html page with plots and tables)?
All my best
I searched high and low and could not find anything. Hacked together a solution that worked very well for me:
import plotly.graph_objs as go
import plotly.io as pio
labels = ['Alice','Bob','Carl']
vals = [2,5,4]
data = [go.Bar(x=vals, y=labels, orientation='h')]
layout = go.Layout(margin_pad=10)
fig = go.Figure(data=data,layout=layout)
svg = pio.to_image(fig, format="svg")
context["svg"] = svg.decode("utf-8")
and then in the template something like this
<div>
{{ svg|safe }}
</div>
The first solution was the one you referenced as well. The quality was very bad though. This solution is a lot more crisp.
I used it with weasyprint, but I'm sure it will work with other solutions as well.

Bokeh Wont Render

I have written a very simple flask application to test out Bokeh, however my Bokeh line chart just wont render. All it shows is :
My html is:
<html>
<head>
<meta charset="UTF-8">
<link href="http://cdn.bokeh.org/bokeh/release/bokeh-2.0.0.min.css" rel="stylesheet" type="text/css">
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-2.0.0.min.js"></script>
</head>
<body>
{{graph | safe}}
</body>
</html>
My flask function is:
app.route('/result')
def result():
x = [1, 3, 5, 7]
y = [2, 4, 6, 8]
p = figure()
p.circle(x, y, size=10, color='red', legend='circle')
p.line(x, y, color='blue', legend='line')
p.triangle(y, x, color='gold', size=10, legend='triangle')
return render_template("result.html", graph=p)
The version of bokeh currently installed by pip is 2.0.0
You can't just pass a bare Bokeh object to a Jinja template. There are various Bokeh APIs for embedding the content in different ways. You will need to use one of those:
https://docs.bokeh.org/en/2.0.0/docs/user_guide/embed.html
For standalone content like this you either want json_items, components, or autoload_static.

Changing the favicon in Flask/Dash

Trying to get the favicon to load I have followed suggestions from the internet:
server = Flask(__name__, static_folder='static')
app = dash.Dash(external_stylesheets=external_stylesheets, server=server)
app.css.config.serve_locally = False
app.scripts.config.serve_locally = True
#server.route('/favicon.ico')
def favicon():
print('Server root path', server.root_path)
return send_from_directory(os.path.join(server.root_path, 'static'),
'dice.ico', mimetype='image/vnd.microsoft.icon')
...
app.run_server(debug=True)
If I browse to the favicon, I see it:
http://www.example.com/favicon.ico
However, when I browse to
http://www.example.com
I see the dash default icon with it's own description. How do I ensure my ownfavicon loads correctly?
To simply change the favicon all you need to do is to create a folder called assets next to your app.py and place your favicon.ico inside of that folder and it will work perfectly.
app.py:
import flask
import dash
import dash_html_components as html
server = flask.Flask(__name__)
#server.route('/')
def index():
return 'Hello Flask app'
app = dash.Dash(
__name__,
server=server,
routes_pathname_prefix='/dash/'
)
app.layout = html.Div("My Dash app")
if __name__ == '__main__':
app.run_server(debug=True)
Here is the docs link for more information: Dash docs
An alternative used in Dash is:
app = dash.Dash()
app._favicon = ("path_to_folder/(your_icon).co")
You can just put title="My Title" as an argument when establishing the Dash app instance. i.e.
app = dash.Dash(
__name__,
title="My Title",
server=server,
routes_pathname_prefix='/dash/'
)
Adding for the sake of completeness. Nowadays, it's recommended to use a bundle of icons with different resolutions to please different browsers and ensure the best picture quality. You can produce such a bundle with the help of, say, realfavicongenerator.net Or you simply might want to use a non-standard .png or .svg icon.
For that, you can subclass Dash and add your much desired link rel and meta tags to its interpolate_index method:
import dash
class CustomDash(dash.Dash):
def interpolate_index(self, **kwargs):
return '''
<!DOCTYPE html>
<html>
<head>
{metas}
<title>{title}</title>
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicons/favicon-16x16.png">
<link rel="manifest" href="assets/favicons/site.webmanifest">
<link rel="mask-icon" href="assets/favicons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
{css}
</head>
<body>
{app_entry}
<footer>
{config}
{scripts}
{renderer}
</footer>
</body>
</html>
'''.format(**kwargs)
app = CustomDash()
Do not forget to place the unzipped bundle into your assets/favicons subfolder!
You should create an "assets" folden and then update the "__favicon" property in your Dash app:
app._favicon = "favico.ico"
Even if OP's accepted answer doesn't work, refer to official document which says:
It is recommended to add name to the dash init to ensure the resources in the assets folder are loaded, eg: app = dash.Dash(name, meta_tags=[...]). When you run your application through some other command line (like the flask command or gunicorn/waitress), the main module will no longer be located where app.py is. By explicitly setting name, Dash will be able to locate the relative assets folder correctly.
Include __name__ in Dash object:
app = Dash(__name__)

Bokeh - multiple figures on same page

I have a need to have 2 bokeh figures on a page. I need them to separate from each other. Currently I can have only one figure (with multiple plots using grid/rows/columns) but not with multiple figures.
See the documentation on how to append figures in rows or columns.
For an example on how to plot figures in the same row see
from bokeh.io import output_file, show
from bokeh.layouts import row
from bokeh.plotting import figure
output_file("layout.html")
x = list(range(11))
y0 = x
y1 = [10 - i for i in x]
y2 = [abs(i - 5) for i in x]
# create a new plot
s1 = figure(plot_width=250, plot_height=250, title=None)
s1.circle(x, y0, size=10, color="navy", alpha=0.5)
# create another one
s2 = figure(plot_width=250, plot_height=250, title=None)
s2.triangle(x, y1, size=10, color="firebrick", alpha=0.5)
# create and another
s3 = figure(plot_width=250, plot_height=250, title=None)
s3.square(x, y2, size=10, color="olive", alpha=0.5)
# put the results in a row
show(row(s1, s2, s3))
Likewise, you could put the results in a column using
show(column(s1, s2, s3))
Of course, you can combine the two to create a grid, so if you have a list of figures, say graphs, you could do something like
cols = []
row_num = 3
for i in range(0, len(graphs), row_num):
r = row(graphs[i: i + row_num])
cols.append(r)
show(column(cols))
Try having different script and div tag for the plots.
Example using Django:
from django.shortcuts import render_to_response
from bokeh.plotting import figure,output_file,show
from bokeh.embed import components
import random
**Define your View function this way**
def plot(request,*args,**kwargs):
PLOT_OPTIONS = dict(plot_width=800, plot_height=300)
SCATTER_OPTIONS = dict(size=12, alpha=0.5)
data = lambda: [random.choice([i for i in range(100)]) for r in range(10)]
red = figure(sizing_mode='scale_width', tools='pan', **PLOT_OPTIONS)
red.scatter(data(), data(), color="red", **SCATTER_OPTIONS)
blue = figure(sizing_mode='fixed', tools='pan', **PLOT_OPTIONS)
blue.scatter(data(), data(), color="blue", **SCATTER_OPTIONS)
green = figure(sizing_mode='scale_width', tools='pan', **PLOT_OPTIONS)
green.scatter(data(), data(), color="green", **SCATTER_OPTIONS)
script1, div1 = components(red)
script2, div2 = components(blue)
script3, div3 = components(green)
context = {'script1':script1,'div1':div1,'script2':script2,'div2':div2,'script3':script3,'div3':div3}
return render_to_response('graph.html',context=context)
**Then your Template should look like this:**
first load the dependencies inside the HEAD tag
<link href="http://cdn.bokeh.org/bokeh/release/bokeh-1.3.4.min.css" rel="stylesheet" type="text/css">
<link href="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.3.4.min.css" rel="stylesheet" type="text/css">
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-1.3.4.min.js"></script>
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.3.4.min.js"></script>
{{ script1 | safe }}
{{ script2 | safe }}
{{ script3 | safe }}
Inside the body or wherever you want to display the plots
<div> {{ div1 | safe }} </div>
<div> {{ div2 | safe }} </div>
<div> {{ div3 | safe }} </div>
from bokeh.plotting import figure
from bokeh.embed import file_html
from bokeh.resources import CDN
x = [1,4,6]
y = [4,6,9]
plot = figure(width=300, height=300)
plot.line(x, y)
html1 = file_html(plot, CDN, 'my plot')
This way you can create multiple plots and insert them using standard jinja2 syntax
Like:
<h1> First plot </h1>
{{ html1 }}
<h1> Second plot </h1>
{{ html2 }}
More information you can find here
Also you can try using Tab Panels

how to embed standalone bokeh graphs into django templates

I want to display graphs offered by the bokeh library in my web application via django framework but I don't want to use the bokeh-server executable because it's not the good way. so is that possible? if yes how to do that?
Using the Embedding Bokeh Plots documentation example as suggested by Fabio Pliger, one can do this in Django:
in the views.py file, we put:
from django.shortcuts import render
from bokeh.plotting import figure
from bokeh.resources import CDN
from bokeh.embed import components
def simple_chart(request):
plot = figure()
plot.circle([1,2], [3,4])
script, div = components(plot, CDN)
return render(request, "simple_chart.html", {"the_script": script, "the_div": div})
in the urls.py file we can put :
from myapp.views import simple_chart
...
...
...
url(r'^simple_chart/$', simple_chart, name="simple_chart"),
...
...
in the template file simple_chart.html we'll have :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Experiment with Bokeh</title>
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-0.8.1.min.js"></script>
<link rel="stylesheet" href="http://cdn.bokeh.org/bokeh/release/bokeh-0.8.1.min.css">
</head>
<body>
{{ the_div|safe }}
{{ the_script|safe }}
</body>
</html>
And it works.
You don't need to use bokeh-server to embed bokeh plots. It just means you'll not be using (and probably don't need) the extra features it provides.
In fact you can embed bokeh plots in many ways like generating standalone html, by generating bokeh standalone components that you can then embed in you django app when rendering templates or with the method we call "autoloading" which makes bokeh return a tag that will replace itself with a Bokeh plot. You'll find better details looking at the documentation.
Another good source of inspiration is the embed examples you can find in the repository.
It is also possible to have it work with AJAX requests. Let's say we have a page loaded and would like to show a plot on button click without reloading the whole page. From Django view we return Bokeh script and div in JSON:
from django.http import JsonResponse
from bokeh.plotting import figure
from bokeh.resources import CDN
from bokeh.embed import components
def simple_chart(request):
plot = figure()
plot.circle([1,2], [3,4])
script, div = components(plot, CDN)
return JsonResponse({"script": script, "div": div})
When we get AJAX response in JS (in this example Jquery is used) the div is first appended to the existing page and then the script is appended:
$("button").click(function(){
$.ajax({
url: "/simple_chart",
success: function(result){
var bokeh_data = JSON.parse(result);
$('#bokeh_graph').html(bokeh_data.div);
$("head").append(bokeh_data.script);
}});
});
It must put {{the_script|safe}} inside the head tag
Here's a flask app that uses jquery to interract with a bokeh plot. Check out the templates/ for javascript you can reuse. Also search for bokeh-demos on github.