嵌入Bokeh内容

Bokeh提供了多种将绘图和数据嵌入HTML文档的方法。首先,提醒您注意独立文档和应用程序之间的区别:

独立文档

这些是没有Bokeh服务器支持的Bokeh文档。他们可能有许多工具和交互(例如 CustomJS 回调),但是自包含的HTML、JavaScript和CSS。它们可以作为一个大文档或一组单独模板的子组件嵌入到其他HTML页面中。

Bokeh应用程序

这些是由Bokeh服务器支持的Bokeh文档。除了独立文档的所有特性之外,还可以将事件和工具连接到Bokeh服务器中执行的实际Python回调。看到了吗 运行Bokeh服务器 有关创建和运行Bokeh应用程序的更多信息。

独立文档

本节介绍如何将独立文档(即 not 链接到Bokeh服务器)可以以多种方式发布或嵌入。

一项

Bokeh可以使用 file_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")

返回的HTML文本可以使用标准的Python文件操作保存到文件中。您还可以提供自己的模板并传入自定义或其他模板变量。见 file_html() 有关详细信息的文档。

这是生成HTML文件的一种相当低级的显式方法,对于web应用程序(例如Flask应用程序)可能很有用。当使用|博克。绘图|接口在脚本或Jupyter笔记本中,用户通常会调用函数 output_file()show()save() 相反。

JSON项目

Bokeh还可以提供一个JSON块,BokehJS可以很容易地使用它来呈现指定div中的独立Bokeh内容 json_item() 函数接受一个Bokeh模型(例如一个Plot)和一个目标ID,它可以标识要呈现到的div:

p = figure()
p.circle(x, y)

item_text = json.dumps(json_item(p, "myplot"))

此输出可由 Bokeh.embed.embed_item 网页功能:

item = JSON.parse(item_text);
Bokeh.embed.embed_item(item);

在这种情况下,Bokeh图将自己呈现为一个带有id的div “我的阴谋” .

调用时也可以省略目标id json_item()

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 终结点:

@app.route('/plot')
def plot():
    p = make_plot('petal_width', 'petal_length')
    return json.dumps(json_item(p, "myplot"))

那么页面上相应的代码可能如下所示:

<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/embed/json_item.py .

组件

也可以使用 components() 功能。此函数返回 <script> 包含绘图的数据,以及随附的 <div> 打印视图加载到的标记。这些标记可以在HTML文档中使用,但是您喜欢:

from bokeh.plotting import figure
from bokeh.embed import components

plot = figure()
plot.circle([1,2], [3,4])

script, div = components(plot)

归还的人 <script> 看起来像:

<script type="text/javascript">
    (function() {
  var fn = function() {
    Bokeh.safely(function() {
      var docs_json = { DOCUMENT DATA HERE };
      var 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> 看起来像:

<div class="bk-root" id="9574d123-9332-4b5f-96cc-6323bef37f40"></div>

这两个元素可以插入或模板化到HTML文本中,脚本在执行时将用plot替换div。

使用这些组件假定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>

这个 "-widgets" 只有当文档包含Bokeh小部件时,文件才是必需的。同样 "-tables" 如果您需要在文档中使用文档中的数据,请使用文档。

例如,使用版本 1.4.0 ,包括小部件和表支持:

<script src="https://cdn.bokeh.org/bokeh/release/bokeh-1.4.0.min.js"
        crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.4.0.min.js"
        crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-1.4.0.min.js"
        crossorigin="anonymous"></script>

注解

你必须提供结束语 </script> 标签。这是所有浏览器都需要的,没有它,页面通常不会呈现。您还应该始终包括 crossorigin="anonymous" 脚本标记上的属性。

如果你想包括 Subresource Integrity 通过设置 integrity 属性,则可以通过调用 get_sri_hashes_for_version() 例如

In [1]: import bokeh.resources

In [2]: bokeh.resources.get_sri_hashes_for_version("2.0.0")
Out[2]:
{'bokeh-2.0.0.js': 'TQAjsk2/lDn1NHjYoe8HIascd3/Cw4EWdk6GNtYXVVyAiUkbEZiuP7fEgbSwM37Y',

...

'bokeh-widgets-2.0.0.min.js': '2ltAd1cQhavmLeBEZXGgnna8fjbw+FjvDq9m2dig4+8KVS8JcYFUQaALvLT//qHE'}

这些是光散列,它们必须以前缀 sha384- 使用。例如:

<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.0.0.min.js"
        integrity="sha384-5Y+xuMRAbgBj/2WKUiL8yzV4fBFic1HJPo2hT3pq2IsEzbsJjj8kT2i0b1lZ7C2N"
        crossorigin="anonymous"></script>

SRI散列只为完整版本生成(即不为dev构建或发布候选版本)。

除了一个单一的Bokeh模型(例如一个地块),该模型 components() 函数还接受模型的列表或元组,或键和模型的字典。每个都返回一个元组,其中包含一个脚本和一个与div对应的数据结构。

以下说明了不同的输入类型与输出之间的关联:

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 we want
TOOLS="pan,wheel_zoom,box_zoom,reset,save"

# the red and blue graphs will share this data range
xr1 = Range1d(start=0, end=30)
yr1 = Range1d(start=0, end=30)

# only the green will use this data range
xr2 = Range1d(start=0, end=30)
yr2 = Range1d(start=0, end=30)

# build our figures
p1 = figure(x_range=xr1, y_range=yr1, tools=TOOLS, plot_width=300, plot_height=300)
p1.scatter(x1, y1, size=12, color="red", alpha=0.5)

p2 = figure(x_range=xr1, y_range=yr1, tools=TOOLS, plot_width=300, plot_height=300)
p2.scatter(x2, y2, size=12, color="blue", alpha=0.5)

p3 = figure(x_range=xr2, y_range=yr2, tools=TOOLS, plot_width=300, plot_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">
    var docs_json = { DOCUMENT DATA HERE }
    var 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 class="bk-root" id="e89297cf-a2dc-4edd-8993-e16f0ca6af04"></div>',
        'Blue': '\n<div class="bk-root" id="eeb9a417-02a1-47e3-ab82-221abe8a1644"></div>',
        'Red': '\n<div class="bk-root" id="c311f123-368f-43ba-88b6-4e3ecd9aed94"></div>'
    }

然后将script和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-1.1.0.min.js"></script>

        <!-- COPY/PASTE SCRIPT HERE -->

    </head>
    <body>
        <!-- INSERT DIVS HERE -->
    </body>
</html>

请注意,上面我们没有包括 "-widgets" JS和CSS文件,因为文档不使用Bokeh小部件。如果需要,CDN资源也可以作为HTTPS url使用。

可以通过运行以下命令查看示例:

python /bokeh/examples/embed/embed_multiple.py

自动加载脚本

嵌入独立文档的最后一种方法是 autoload_static() 功能。此函数提供 <script> 标记,它将用Bokeh图替换自身,无论标记位于何处。脚本还将检查BokehJS并在必要时加载它。使用此函数,可以通过在文档中单独放置此脚本标记来嵌入绘图。

此函数采用您要显示的Bokeh模型(例如,打印),a Resources 对象,以及从中加载脚本的路径。那么 autoload_static() 将返回一个自包含的 <script> 标记和一段JavaScript代码。JavaScript代码应该保存到您提供的路径中。这个 <script> 标记包含在页面中时,将加载并运行保存的JavaScript,以便在浏览器中实现打印。

以下是您可能使用的方法 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> 标签看起来像:

<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" ,来自嵌入绘图的文档。

注解

这个 <script> 标记加载 <div> 所以必须把它放在 <head> .

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。

下面是一个如何使用 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模板,可以通过扩展“base”模板灵活地快速嵌入不同的文档根。这对于将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 | indent(8) if bokeh_css }}
        {% endblock %}
        {% block js_resources %}
        {{ bokeh_js | indent(8) if bokeh_js }}
        {% endblock %}
    {% endblock %}
    {% block postamble %}{% endblock %}
    {% endblock %}
</head>
{% endblock %}
{% block body %}
<body>
    {% block inner_body %}
    {% block contents %}
        {% for doc in docs %}
        {{ embed(doc) if doc.elementid }}
        {% for root in doc.roots %}
            {{ embed(root) | indent(10) }}
        {% endfor %}
        {% endfor %}
    {% endblock %}
    {{ plot_script | indent(8) }}
    {% endblock %}
</body>
{% endblock %}
</html>