I'm playing around with the Bokeh sliders demo (source code here), and I'm trying to change the background color of the entire page. Though changing the color of the figure is easy using background_fill_color and border_fill_color, the rest of the layout still appears on top of a white background. Is there an attribute I can add to the theme that will allow me to set the color via curdoc().theme?
There's not currently any Python property that would control the HTML background color. HTML and CSS is vast territory, so instead of trying to make a corresponding Python property for every possible style option, Bokeh provides a general mechanism for supplying your own HMTL templates so that any standard familiar CSS can be applied.
This is most easily accomplished by adding a templates/index.html file to a Directory-style Bokeh App. The template should be Jinja2 template. There are two substitutions required to be defined in the <head>:
{{ bokeh_css }}
{{ bokeh_js }}
as well as two required in <body>:
{{ plot_div }}
{{ plot_script }}
The app will appear wherever the plot_script appears in the template. Apart from this, you can apply whatever HTML and CSS you need. You can see a concrete example here:
https://github.com/bokeh/bokeh/blob/master/examples/app/crossfilter
A boiled down template that changes the page background might look like this:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body { background: #2F2F2F; }
</style>
<meta charset="utf-8">
{{ bokeh_css }}
{{ bokeh_js }}
</head>
<body>
{{ plot_div|indent(8) }}
{{ plot_script|indent(8) }}
</body>
</html>
Changing the .bk-root style worked for me:
from bokeh.resources import Resources
from bokeh.io.state import curstate
from bokeh.io import curdoc, output_file, save
from bokeh.util.browser import view
from bokeh.models.widgets import Panel, Tabs
from bokeh.plotting import figure
class MyResources(Resources):
#property
def css_raw(self):
return super().css_raw + [
""".bk-root {
background-color: #000000;
border-color: #000000;
}
"""
]
f = figure(height=200, width=200)
f.line([1,2,3], [1,2,3])
tabs = Tabs( tabs=[ Panel( child=f, title="TabTitle" ) ], height=500 )
output_file("BlackBG.html")
curstate().file['resources'] = MyResources(mode='cdn')
save(tabs)
view("./BlackBG.html")
If you are using row or column for displaying several figures in the document, a workaround is setting the background attribute like this:
curdoc().add_root(row(fig1, fig2, background="beige"))
I know it is not the cleanest way to do it, but a workaround would be to modify file.html inside bokeh template folder
FILE PATH
CODE SNIPPET
From Bokeh documentation:
The background fill style is controlled by the background_fill_color
and background_fill_alpha properties of the Plot object:
from bokeh.plotting import figure, output_file, show
output_file("background.html")
p = figure(plot_width=400, plot_height=400)
p.background_fill_color = "beige"
p.background_fill_alpha = 0.5
p.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], size=10)
show(p)
Related
So, I'm actually making all computations in backend, generate a chart in (.png), save it to a pathfile, and communicate through AJAX the link to this newly generated image. However, such process allows me to transfer an image only. I'm basically converting the plot to an image.
I wonder if there is a way to transfer the entire plotly output, as an interactive chart through AJAX.
import yfinance as yf
import plotly.graph_objects as go
aapl = yf.Ticker('AAPL')
ainfo = aapl.history(start=datemin, end=datemax)
#Plot the Chart
fig = go.Figure(data=go.Scatter(x=ainfo.index,y=ainfo.Close, mode='lines'),)
#DB inject plot
fig.write_image("/Users/Xlibidish/Desktop/Django/static/"+tickerZ+rx+".png")
#And then communicate the path through AJAX etc.
I'd like to send to my Webapp the plotly output. I have some hints:
Generate the plot in my Webapp directly in JS, so the backend sends only the data from yfinance and a directive to generate it. (Quite complex, especially knowing that I have various types of plots, which are all generated in python so the Webappp is only receiving Images at this time without differentiating them).
Create an iframe directing to the plotly output ports, but not sure about this one ! And also, I need to save the plot results in a DB.
Just to clarify:
#in the previous example:
fig.view()
# will be very different from
fig.write_image()
#One will be a png file, the other a pretty cool interactive chart.
```
try fig.to_html()
To be honest, I do not even know what AJAX is. So I am not using AJAX but I still manage to display the entire interactive chart on my "website".
Maybe this gives you some guidance and you can figure out the AJAX part for yourself.
def _give_fig():
data = # input your data here
layout = # input your layout here
fig = go.Figure(data=data, layout=layout) # build your figure with data and layout
fig.update_layout(template='simple_white') # modify your fig until ready
return fig.to_html(config={'displaylogo': False}, include_plotlyjs=False, default_width=790, default_height=600)
# returns chart as html
# displaylogo False to hide the plotly logo in chart
# default width and hight self explanatory
# include_plotlyjs --> important!
def your_view(request):
context = {"your_plot": _give_fig()}
return render(request, 'app/your_template.html', context)
Read more about include_plotlyjs here. You can put it to True and then it will directly include the javascript. It is about 3 mb I think. I personally use the CDN. So have a look at my template:
your_template.html:
{% extends 'base.html' %}
{% block styles %}
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
{% endblock %}
{% block content %}
{% autoescape off %}
{{ your_plot }}
{% endautoescape %}
{% endblock %}
Yeah, that works without the AJAX. Good luck trying to fiddle it in.
I have a django based web application; and I am using the Python wrappers of plotly to generate plots. When I try to use "ajax style" to fetch only the plot into a <div> </div> with a javascript call no plot is rendered. My situation is as follows:
<head>
<script>
async function generate_plot() {
const url = "/path/to/plot/generator/";
let response = await fetch(url);
let data = await response.text();
let element = document.getElementById('plot_div');
element.innerHTML = data;
}
</script>
</head>
<body>
<div id="plot_div"> </div>
...
</body>
At the /path/to/plot/generator/ endpoint there is a python function which looks like:
def plot_generator(request):
...
return plotly.offline.plot(fig, include_plotlyjs=False, output_type="div")
I am quite certain this is basically sound, because when I manually paste the return value from plot_generator() into <div id="plot_div"></div> the plot is displayed correctly, and also If I let the plot_generator() just return a dummy text like: This should have been a plot - the text is correctly displayed. But the plotly plot is not displayed in any way - the Firefox debug console shows no warnings/errors/anything ...
Something trivially wrong?
Update: When looking in the firefox console the plotly generated content seems to have arrived correctly in the browser/DOM.
I have seen similar questions up before without answer.
The aim is to show the image on the Flask application but with the altered config.
How is the config setting on Plotly charts altered when converting the graph into JSON and not using the fig.show()? If the fig.show() was used then the config would be amended inside there.
Flask Code:
Of course, if this was just to show the graph fig.show would be used but this is passing it through to JSON.
#example flask application with graph
from flask import Flask, render_template, request
import plotly.express as px
import plotly
import numpy as np
import json
#Flask settings.
secret_key = 'verysecretkey123lol' #secret key
app = Flask(__name__)
#simple image
img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
[[0, 255, 0], [0, 0, 255], [255, 0, 0]]
], dtype=np.uint8)
fig = px.imshow(img_rgb)
#Flask
#app.route("/", methods=['GET', 'POST'])
def create():
image = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
return render_template('example.html', image = image)
#Run
if __name__ == "__main__":
app.secret_key = secret_key
app.run(debug = False)
HTML Code (example.html):
I have added the Config as a javascript variable below.
<!DOCTYPE html>
<html>
<head lang="en">
<title>Dissertation</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="chart" id="image">
<script>
var graphs = {{image | safe}};
var config = {"displaylogo": FALSE, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']};
Plotly.plot('image', graphs, {});
</script>
</div>
</body>
</html>
Any help would be greatly appreciated, thanks
Andrew
I struggled with trying to pass config options to Plotly when embedding the plots in a Flask application. In particular, I wanted to remove modebar buttons in my application. I discovered the way to achieve this from this related question.
Specifically, in the HTML code where you call the JS to display the plot, you set the plot config using the command Plotly.setPlotConfig().
Using your code example, you can set the config and display the plot with
<body>
<div class="chart" id="image">
<script>
var graphs = {{image | safe}};
var config = {"displaylogo": FALSE, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']};
Plotly.setPlotConfig(config);
Plotly.plot('image', graphs, {});
</script>
</div>
</body>
If you have already included some config file in the HTML by using Plotly.setPlotConfig(config); and use Plotly.newPlot("image", graphs) to reload data, it won't work again.
You must embed the config in your graphs object
//... your ajax call
graphs.config = {'displayModeBar': false}
Plotly.newPlot('image', graphs);
//... your ajax call
You can get this to work for layout options and config options without any hacky extra code in the HTML.
The key is remembering what render_template accepts as parameters (valid json).
Convert the image, layout, and config dictionaries to json. Your "image" here could also be a "graph_object" or "figure".
image = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
layout = json.dumps({'margin': dict(l=0, r=0, t=50, b=0)})
config = json.dumps({'displaylogo': False, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']})
Pass those to render_template.
return render_template('example.html', image=image, layout=layout, config=config)
Use all three in your html template, following the safe convention if you trust the data.
<script>
Plotly.newPlot('image', {{ image | safe }}, {{ layout | safe }}, {{ config | safe }});
</script>
Put that all together, and your final code would look like this
#app.route("/", methods=['GET', 'POST'])
def create():
image = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
layout = json.dumps({'margin': dict(l=0, r=0, t=50, b=0)})
config = json.dumps({'displaylogo': False, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']})
return render_template('example.html', image=image, layout=layout, config=config)
<body>
<div class="chart" id="image">
<script>
Plotly.newPlot('image', {{ image | safe }}, {{ layout | safe }}, {{ config |
safe }});
</script>
</div>
</body>
That's it! No need to set the config/layout somewhere other than the place where you set your figure.
I have been struggling trying to find a way to update graphs on a flask webserver. I stored the images in the static file of my directory and accessed them by {{url_for('static',filname=ph_plot.png) }} but everytime I would send a post request to fetch a new range of data the graph would not update on my webserver but on my filesystem it would. I know I can change the name of the file everytime I save it to make it appear but I dont know if that is an optimal way to display a dynamically changing photo.
Currently I have been using the send_from_directory method in flask but with it hasnt worked for me either. Below is my code.
I have been working on this for a while and would love some help! Thank you
Notes: all_plots.ph_plot() is calling a function from another python program.
FLASK CODE:
#app.route('/read_ph', methods=["GET", "POST"])
def ph_plot():
if request.method == "POST":
a = request.form['read_ph']
all_plots.ph_plot(a)
time.sleep(3)
ph_plot = os.path.join(os.getcwd(), "images/ph_plot.png")
return render_template('live_stream.html', image_name=ph_plot)
#app.route('/read_ph/<ph_plot>', methods=["GET", "POST"])
def send_ph(ph_plot):
return send_from_directory("images", ph_plot)
HTML:
<html>
<body>
<h1>Data Monitoring Station</h1>
<h2>PH</h2>
<form method="POST" action="read_ph" >
<input name="read_ph" placeholder="Instances" type="text">
</form>
<button type="button">PH Graph</button>
<img src="{{ url_for('send_ph',ph_plot=image_name) }}" id="plot" width ="220" height ="220">
<hr>
<h5> Return to main page RETURN</h5>
<hr>
</body>
</html>
send_from_directory is generally for files that have actually been uploaded into a directory from a user. This is not what you're actually trying to do; you're:
Generating the plot data
Creating a plot and spending time with matplotlib rendering it
Saving this plot image to disk
Loading that image back off disk and sending it to a user
Cut out the middleman here of disk storage: create the data and send it straight to the template. Here's a crude example using plotly.js in a single file to get the data rendered on the front end. You can keep refreshing the page to get different graphs. But note, each plot is interactive; you can zoom, for example, export etc. things with the menu in the top right, show/hide the plot (which would make more sense if there were multiple traces). You don't get any of that by rendering a plot image.
from flask import Flask, render_template_string
import random
app = Flask(__name__)
# Normally you'd have this in the templates directory but for simplicity of
# putting everything into one file for an example, I'm using a template string
template = """
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="plot_div" style="width: 100%; height: 100%"></div>
<script type="text/javascript">
var trace1 = {
x: {{ plot_data.x_axis }},
y: {{ plot_data.y_axis }},
type: 'scatter',
name: 'Example'
};
var layout = {
title: "Test",
titlefont: {
family: 'Poppins',
size: 18,
color: '#7f7f7f'
},
showlegend: true,
xaxis: {
title: 'Axis Unit',
},
yaxis: {
title: 'Other Axis Unit',
},
margin: {
l: 70,
r: 40,
b: 50,
t: 50,
pad: 4
}
};
var data = [trace1];
Plotly.newPlot("plot_div", data, layout);
</script>
"""
def get_plot_data():
x = list(range(10))
y = [random.randint(0, 100) for i in range(10)]
return {'x_axis': x, 'y_axis': y}
#app.route('/')
def home():
plot_data = get_plot_data()
return render_template_string(template,
plot_data=plot_data)
if __name__ == '__main__':
app.run()
A common issue here is in passing datetime strings because they will be escaped. In this case, you'll need to use x: {{ plot_data.x_axis | safe }}. See Passing HTML to template using Flask/Jinja2 and How to make html markup show up?
I am trying to use matplotlib and mpld3 to produce some html plots on my Django report app.
Basically I have a controller for the plot that is the following:
from django.shortcuts import render
import mpld3
from matplotlib.pyplot import figure, title, bar
def cpfLogin(request):
mpl_figure = figure(1)
xvalues = (1,2,3,4,5)
yvalues = (1,2,3,4,5)
width = 0.5 # the width of the bars
title(u'Custom Bar Chart')
bar(xvalues, yvalues, width)
fig_html = mpld3.fig_to_html(mpl_figure)
context = {
'figure': fig_html,
}
return render(request, 'reports/CPFReport.html', context)
The code for reports/CPFReport.html is:
{% load i18n %}
{% block extrahead %}
<style type="text/css">
.chart_title {
font-weight: bold;
font-size: 14px;
}
</style>
{% endblock %}
{% block content %}
<div id="content-main">
<div class="chart_title">
{% trans "Custom Bar Chart" %}
</div>
{{ figure|safe }}
</div>
{% endblock %}
The code is executed right and the plot is displayed correctly but after a couple of seconds the app terminates with the following error:
Assertion failed: (NSViewIsCurrentlyBuildingLayerTreeForDisplay() !=
currentlyBuildingLayerTree), function
NSViewSetCurrentlyBuildingLayerTreeForDisplay, file
/BuildRoot/Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1561.20.106/AppKit.subproj/NSView.m,
line 14480.
I found out that if I comment all the code this exception is thrown when any of the matplotlib libraries are called.
Does anyone has a workaround or solution for this problem?
In my case I had to avoid importing :
import matplotlib.pyplot as plt
fig,ax = plt.subplots(figsize=(8,9))
l = plt.plot(x,s, 'y-', label="line")
and substitute it with:
from matplotlib.figure import Figure
fig = Figure()
ax = fig.add_subplot(111))
l = ax.plot(x,s, 'y-', label="line")
Maybe I find the solution,
just add the follow code in top.
import matplotlib
matplotlib.use('Agg')
For my case, I use python3, flask and matplotlib.
reference:
https://gist.github.com/tebeka/5426211
Adding plt.close() after saving the figure using fig.savefig('../static/images/something.png') helped me.
To complete mapp mapp's answer, in this case it's linked to using matplotlib with a webserver. The solution recommended by matplotlib documentation is to use the Agg backend :
import matplotlib
matplotlib.use('Agg')
# then import pyplot and mpld3
import mpld3
from matplotlib.pyplot import figure, title, bar