网页#
本章探讨了将独立的Bokeh文档和Bokeh应用程序嵌入网页的多种方法。首先,以下是独立文档与应用程序的区别:
- 独立文档
这些文档不需要Bokeh服务器即可工作。它们可能包含许多工具和交互,例如自定义JavaScript回调,但除此之外,它们只是HTML、CSS和JavaScript。这些文档可以作为一个大文档嵌入到其他HTML页面中,或者作为一组具有单独模板的子组件嵌入。
- Bokeh 应用程序
这些应用程序需要一个Bokeh服务器才能工作。拥有一个Bokeh服务器可以让您将事件和工具连接到在服务器上执行的实时Python回调。有关创建和运行Bokeh应用程序的更多信息,请参阅Bokeh server。
独立文档#
本节介绍了发布和嵌入独立Bokeh文档的不同方法。
HTML文件#
Bokeh 可以使用 file_html() 函数为 Bokeh 文档生成完整的 HTML 页面。此函数可以从其通用模板或您提供的模板创建 HTML 文档。这些 HTML 文件包含绘图数据,并且是完全可移植的,同时仍然为您的绘图提供交互式工具(平移、缩放等)。以下是一个示例:
from bokeh.plotting import figure
from bokeh.resources import CDN
from bokeh.embed import file_html
plot = figure()
plot.circle([1,2], [3,4])
html = file_html(plot, CDN, "my plot")
您可以使用标准的Python文件操作将返回的HTML文本保存到文件中。您还可以为HTML输出提供自己的模板,并传入自定义或额外的模板变量。有关更多详细信息,请参阅file_html()文档。
这是一种低级别、显式生成HTML文件的方式,对于诸如Flask应用程序之类的Web应用程序非常有用。
在使用bokeh.plotting接口的脚本和Jupyter笔记本中,你可以调用output_file()函数与show()或save()一起使用。show()函数创建一个HTML文档并在网页浏览器中显示它,而save()则创建一个HTML文档并将其保存在本地。
JSON 项目#
Bokeh 还可以提供 JSON 数据,BokehJS 可以使用这些数据在指定的 中渲染一个独立的 Bokeh 文档。json_item() 函数接受一个 Bokeh 模型(例如,一个图表)和一个可选的 目标 ID。
p = figure()
p.circle(x, y)
item_text = json.dumps(json_item(p, "myplot"))
embed_item() 函数可以在网页上使用这个输出:
item = JSON.parse(item_text);
Bokeh.embed.embed_item(item);
这将在ID为“myplot”的中渲染图表。
你也可以在调用json_item()时省略目标ID:
p = figure()
p.circle(x, y)
item_text = json.dumps(json_item(p)) # no target ID given
然后你可以在JavaScript中指定ID:
item = JSON.parse(item_text);
Bokeh.embed.embed_item(item, "myplot");
这是一个更完整的Flask应用程序示例,它从/plot端点提供Bokeh JSON项目:
@app.route('/plot')
def plot():
p = make_plot('petal_width', 'petal_length')
return json.dumps(json_item(p, "myplot"))
这会生成看起来像这样的JavaScript代码:
<script>
fetch('/plot')
.then(function(response) { return response.json() })
.then(function(item) { return Bokeh.embed.embed_item(item) })
</script>
或者,使用现代语法,像这样:
<script>
const response = await fetch('/plot')
const item = await response.json()
Bokeh.embed.embed_item(item)
</script>
有关完整示例,请参见 examples/output/apis/json_item.py。
组件#
你也可以让Bokeh返回独立文档的各个组件,使用components()函数逐个嵌入它们。这个函数返回一个包含你的图表数据的
from bokeh.plotting import figure
from bokeh.embed import components
plot = figure()
plot.circle([1,2], [3,4])
script, div = components(plot)
返回的
<script type="text/javascript">
(function() {
const fn = function() {
Bokeh.safely(function() {
const docs_json = { DOCUMENT DATA HERE };
const render_items = [{
"docid":"6833819f-9b5b-4904-821e-3f5eec77de9b",
"elementid":"9574d123-9332-4b5f-96cc-6323bef37f40",
"modelid":"7b328b27-9b14-4f7b-a5d8-0138bc7b0f59"
}];
Bokeh.embed.embed_items(docs_json, render_items);
});
};
if (document.readyState != "loading") fn();
else document.addEventListener("DOMContentLoaded", fn);
})();
</script>
请注意,Jupyter笔记本不允许在同一笔记本单元格中使用components()和show()函数。
docs_json 包含所有数据以及绘图或小部件对象(此处为简洁起见省略)。生成的 看起来像这样:
<div id="9574d123-9332-4b5f-96cc-6323bef37f40"></div>
你可以在HTML文档中插入或模板化这个脚本及其伴随的,当脚本执行时,你的图表将替换。
为了使此功能正常工作,您首先需要加载BokehJS,无论是从本地还是从内容分发网络(CDN)加载。要从CDN加载BokehJS,请将以下行添加到您的HTML文档或模板中,并用适当的版本替换x.y.z:
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-x.y.z.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-x.y.z.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-x.y.z.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-x.y.z.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-x.y.z.min.js"
crossorigin="anonymous"></script>
只有Bokeh核心库bokeh-x.y.z.min.js是始终需要的。其他脚本是可选的,只有在您想使用相应功能时才需要包含:
只有在使用任何Bokeh widgets时,才需要
"bokeh-widgets"文件。只有在使用Bokeh的数据表时,才需要
"bokeh-tables"文件。需要
"bokeh-gl"文件来启用 WebGL支持。需要
"bokeh-mathjax"文件来启用 MathJax support。
例如,要使用支持小部件、表格和数学文本的版本 3.0.0,请在您的HTML中包含以下内容:
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.0.0.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.0.0.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.0.0.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.0.0.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.0.0.min.js"
crossorigin="anonymous"></script>
注意
始终提供闭合的标签。这是所有浏览器所必需的,没有它页面通常无法渲染。你还应该始终在script标签中包含crossorigin="anonymous"属性。
如果您希望在显式脚本标签中包含子资源完整性 (SRI) 哈希,可以通过设置integrity属性来实现,所需的哈希可以通过调用get_sri_hashes_for_version()来获取。以下是一个示例:
In [1]: import bokeh.resources
In [2]: bokeh.resources.get_sri_hashes_for_version("2.2.0")
Out[2]:
{'bokeh-2.2.0.js': 'TQAjsk2/lDn1NHjYoe8HIascd3/Cw4EWdk6GNtYXVVyAiUkbEZiuP7fEgbSwM37Y',
...
'bokeh-widgets-2.2.0.min.js': '2ltAd1cQhavmLeBEZXGgnna8fjbw+FjvDq9m2dig4+8KVS8JcYFUQaALvLT//qHE'}
这些是裸哈希值,你必须加上sha384-前缀才能使用。例如:
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.2.0.min.js"
integrity="sha384-5Y+xuMRAbgBj/2WKUiL8yzV4fBFic1HJPo2hT3pq2IsEzbsJjj8kT2i0b1lZ7C2N"
crossorigin="anonymous"></script>
您只能为完整发布版本生成SRI哈希,而不能为开发版本或候选发布版本生成。
除了单个Bokeh模型(如绘图)外,components()函数
还可以接受模型列表或元组,或键和模型的字典。
每个返回一个包含一个脚本和对应目标元素的数据结构的元组。
以下说明了不同的输入类型如何与输出相关联:
components(plot)
#=> (script, plot_div)
components((plot_1, plot_2))
#=> (script, (plot_1_div, plot_2_div))
components({"Plot 1": plot_1, "Plot 2": plot_2})
#=> (script, {"Plot 1": plot_1_div, "Plot 2": plot_2_div})
这里有一个如何使用多图生成器的示例:
# scatter.py
from bokeh.plotting import figure
from bokeh.models import Range1d
from bokeh.embed import components
# create some data
x1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y1 = [0, 8, 2, 4, 6, 9, 5, 6, 25, 28, 4, 7]
x2 = [2, 5, 7, 15, 18, 19, 25, 28, 9, 10, 4]
y2 = [2, 4, 6, 9, 15, 18, 0, 8, 2, 25, 28]
x3 = [0, 1, 0, 8, 2, 4, 6, 9, 7, 8, 9]
y3 = [0, 8, 4, 6, 9, 15, 18, 19, 19, 25, 28]
# select the tools you want
TOOLS="pan,wheel_zoom,box_zoom,reset,save"
# the red and blue graphs share this data range
xr1 = Range1d(start=0, end=30)
yr1 = Range1d(start=0, end=30)
# only the green graph uses this data range
xr2 = Range1d(start=0, end=30)
yr2 = Range1d(start=0, end=30)
# build the figures
p1 = figure(x_range=xr1, y_range=yr1, tools=TOOLS, width=300, height=300)
p1.scatter(x1, y1, size=12, color="red", alpha=0.5)
p2 = figure(x_range=xr1, y_range=yr1, tools=TOOLS, width=300, height=300)
p2.scatter(x2, y2, size=12, color="blue", alpha=0.5)
p3 = figure(x_range=xr2, y_range=yr2, tools=TOOLS, width=300, height=300)
p3.scatter(x3, y3, size=12, color="green", alpha=0.5)
# plots can be a single Bokeh model, a list/tuple, or even a dictionary
plots = {'Red': p1, 'Blue': p2, 'Green': p3}
script, div = components(plots)
print(script)
print(div)
运行 python scatter.py 会打印出以下内容:
<script type="text/javascript">
const docs_json = { DOCUMENT DATA HERE }
const render_items = [{
"docid":"33961aa6-fd96-4055-886f-b2afec7ff193",
"elementid":"e89297cf-a2dc-4edd-8993-e16f0ca6af04",
"modelid":"4eff3fdb-80f4-4b4c-a592-f99911e14398"
},{
"docid":"33961aa6-fd96-4055-886f-b2afec7ff193",
"elementid":"eeb9a417-02a1-47e3-ab82-221abe8a1644",
"modelid":"0e5ccbaf-62af-42cc-98de-7c597d83747a"
},{
"docid":"33961aa6-fd96-4055-886f-b2afec7ff193",
"elementid":"c311f123-368f-43ba-88b6-4e3ecd9aed94",
"modelid":"57f18497-9598-4c70-a251-6072baf223ff"
}];
Bokeh.embed.embed_items(docs_json, render_items);
</script>
{
'Green': '\n<div id="e89297cf-a2dc-4edd-8993-e16f0ca6af04"></div>',
'Blue': '\n<div id="eeb9a417-02a1-47e3-ab82-221abe8a1644"></div>',
'Red': '\n<div id="c311f123-368f-43ba-88b6-4e3ecd9aed94"></div>'
}
然后,您可以将生成的脚本和元素插入到如下所示的样板中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bokeh Scatter Plots</title>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.2.0.min.js"></script>
<!-- COPY/PASTE SCRIPT HERE -->
</head>
<body>
<!-- INSERT DIVS HERE -->
</body>
</html>
请注意,这不包括用于"-widgets"的JavaScript和CSS文件,因为文档没有使用任何Bokeh小部件。
你可以通过执行以下操作查看多个图生成的示例:
python /bokeh/examples/embed/embed_multiple.py
自动加载脚本#
你也可以使用autoload_static()函数嵌入独立的文档。
这个函数提供了一个
此函数接受一个Bokeh模型,例如你想要显示的图表,一个Resources对象,以及一个加载脚本的路径。然后autoload_static()返回一个自包含的
以下是如何使用autoload_static()与一个简单图表的示例:
from bokeh.resources import CDN
from bokeh.plotting import figure
from bokeh.embed import autoload_static
plot = figure()
plot.circle([1,2], [3,4])
js, tag = autoload_static(plot, CDN, "some/path")
生成的
<script
src="some/path"
id="c5339dfd-a354-4e09-bba4-466f58a574f1"
async="true"
data-bokeh-modelid="7b226555-8e16-4c29-ba2a-df2d308588dc"
data-bokeh-loglevel="info"
></script>
在您希望图表显示在HTML页面上的任何位置包含此标签。
将JavaScript代码保存到服务器上的“some/path”文件中,以便包含图表的文档可以访问它。
注意
Bokeh 应用程序#
本节介绍如何嵌入整个Bokeh服务器应用程序。您可以嵌入Bokeh应用程序,以便每次页面加载时要么创建并显示一个新的会话和文档,要么输出一个特定的现有会话。
应用程序文档#
如果一个应用程序运行在Bokeh服务器上,并且可以通过某个URL访问,你通常会希望将整个应用程序嵌入到一个网页中。这样,每次加载页面时,都会创建一个新的会话并将其显示给用户。
你可以使用server_document()函数来实现这一点。这个函数接受一个Bokeh服务器应用程序的URL,并返回一个脚本,该脚本每次执行时都会从该服务器嵌入一个新的会话。
以下是使用 server_document() 函数的示例:
from bokeh.embed import server_document
script = server_document("https://demo.bokeh.org/sliders")
这将返回一个看起来像这样的
<script
src="https://demo.bokeh.org/sliders/autoload.js?bokeh-autoload-element=1000&bokeh-app-path=/sliders&bokeh-absolute-url=https://demo.bokeh.org/sliders"
id="1000">
</script>
您可以将此标签添加到HTML页面中,以在该点包含Bokeh应用程序。
应用会话#
有时,您可能希望加载一个特定的会话,而不是加载一个新的会话。
以一个Flask应用为例,它为经过身份验证的用户渲染一个页面。你可能希望它拉取一个新的会话,为该特定用户进行一些自定义,并提供这个自定义的Bokeh服务器会话。
你可以使用server_session()函数来实现这一点。这个函数接受一个特定的模型来嵌入(或者None表示整个会话文档),会话ID,以及Bokeh应用程序的URL。
以下是如何在Flask中使用server_session()的示例:
from flask import Flask, render_template
from bokeh.client import pull_session
from bokeh.embed import server_session
app = Flask(__name__)
@app.route('/', methods=['GET'])
def bkapp_page():
# pull a new session from a running Bokeh server
with pull_session(url="http://localhost:5006/sliders") as session:
# update or customize that session
session.document.roots[0].children[1].title.text = "Special sliders for a specific user!"
# generate a script to load the customized session
script = server_session(session_id=session.id, url='http://localhost:5006/sliders')
# use the script in the rendered page
return render_template("embed.html", script=script, template="Flask")
if __name__ == '__main__':
app.run(port=8080)
标准模板#
Bokeh 还提供了一个标准的 Jinja 模板,通过扩展“基础”模板,帮助您快速灵活地嵌入不同的文档根。这在您需要将 Bokeh 应用程序的各个组件嵌入到非 Bokeh 布局(如 Bootstrap)中时特别有用。
这是一个最小示例,用于创建一个具有名称属性设置的两个根的应用:
p1 = figure(..., name="scatter")
p2 = figure(..., name="line")
curdoc().add_root(p1)
curdoc().add_root(p2)
然后,您可以通过名称引用这些根,并将它们传递给embed宏,以将它们放置在模板的任何部分:
{% extends base %}
<!-- goes in head -->
{% block preamble %}
<link href="app/static/css/custom.min.css" rel="stylesheet">
{% endblock %}
<!-- goes in body -->
{% block contents %}
<div> {{ embed(roots.scatter) }} </div>
<div> {{ embed(roots.line) }} </div>
{% endblock %}
这是一个完整的模板,包含您可以覆盖的所有部分:
<!DOCTYPE html>
<html lang="en">
{% block head %}
<head>
{% block inner_head %}
<meta charset="utf-8">
<title>{% block title %}{{ title | e if title else "Bokeh Plot" }}{% endblock %}</title>
{% block preamble -%}{%- endblock %}
{% block resources -%}
{% block css_resources -%}
{{- bokeh_css if bokeh_css }}
{%- endblock css_resources %}
{% block js_resources -%}
{{ bokeh_js if bokeh_js }}
{%- endblock js_resources %}
{% endblock resources %}
{% block postamble %}{% endblock %}
{% endblock inner_head %}
</head>
{% endblock head%}
{% block body %}
<body>
{% block inner_body %}
{% block contents %}
{% for doc in docs %}
{{ embed(doc) if doc.elementid }}
{%- for root in doc.roots %}
{% block root scoped %}
{{ embed(root) }}
{% endblock %}
{% endfor %}
{% endfor %}
{% endblock contents %}
{{ plot_script | indent(4) }}
{% endblock inner_body %}
</body>
{% endblock body%}
</html>