使用 Bokeh、Flask 和 Python 3 的响应式条形图

Bokeh 是一个功能强大的开源 Python 库,它允许开发人员为其 Web 应用程序生成 JavaScript 数据可视化,无需编写任何 JavaScript。虽然学习基于 JavaScript 的数据可视化库(如 d3.js)可能很有用,但敲几行 Python 代码来完成工作通常要容易得多。

使用 Bokeh,我们可以创建非常详细的交互式可视化,或者只是像下面的条形图这样的传统可视化。

具有 64 个条的响应式散景条形图。

让我们使用 Flask 网络框架和 Bokeh 在 Python 网络应用程序中创建自定义条形图。

我们的工具

本教程适用于 Python 2 或 3,但强烈建议新应用程序使用 Python 3。我在写这篇文章时使用了 Python 3.6.1。除了整个教程中的 Python 之外,我们还将使用以下应用程序依赖项:

  • Flask web框架,版本0.12.2
  • Bokeh数据可视化库,版本0.12.5
  • pandas数据结构与分析库,版本0.20.1
  • pip 和 virtualenv,它们与 Python 3 一起打包,用于安装 Flask、Bokeh 和 pandas 库并将其与您可能正在处理的任何其他 Python 项目隔离

如果您在运行此代码之前需要配置开发环境的帮助,请查看此指南以在 Ubuntu 16.04 LTS 上设置 Python 3 和 Flask

这篇博文中的所有代码都可以在 GitHub 上的 blog-code-examples 存储库的 bar-charts-bokeh-flask-python-3 目录下的 MIT 许可证下获得开源。随意使用和滥用源代码您自己的应用程序。

安装 Bokeh 和 Flask

在终端中使用以下命令为该项目创建一个新的虚拟环境以隔离我们的依赖项。我通常在一个单独的 venvs 目录中运行此命令,我的所有 virtualenvs 都存储在该目录中。

python3 -m venv barchart

激活虚拟环境。

source barchart/bin/activate

激活 virtualenv 后命令提示符将发生变化:

在命令行上激活我们的 Python 虚拟环境。

请记住,您需要在每个要使用 virtualenv 运行项目的新终端窗口中激活 virtualenv。

Bokeh 和 Flask 可以安装到现在激活的 virtualenvusing pip 中。运行此命令以获得适当的 Bokeh 和 Flask 版本。

pip install bokeh==0.12.5 flask==0.12.2 pandas==0.20.1

经过短暂的下载和安装后,我们所需的依赖项应该安装在我们的虚拟环境中。查找输出以确认一切正常。

Installing collected packages: six, requests, PyYAML, python-dateutil, MarkupSafe, Jinja2, numpy, tornado, bokeh, Werkzeug, itsdangerous, click, flask, pytz, pandas
  Running setup.py install for PyYAML ... done
  Running setup.py install for MarkupSafe ... done
  Running setup.py install for tornado ... done
  Running setup.py install for bokeh ... done
  Running setup.py install for itsdangerous ... done
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 PyYAML-3.12 Werkzeug-0.12.2 bokeh-0.12.5 click-6.7 flask-0.12.2 itsdangerous-0.24 numpy-1.12.1 pandas-0.20.1 python-dateutil-2.6.0 pytz-2017.2 requests-2.14.2 six-1.10.0 tornado-4.5.1

现在我们可以开始构建我们的网络应用程序了。

启动我们的 Flask 应用

我们将首先编写一个基本的 Flask 应用程序,然后将我们的条形图添加到呈现的页面。

为您的项目创建一个文件夹,然后在其中创建一个名为app.py 的文件,其中包含以下初始内容:

from flask import Flask, render_template


app = Flask(__name__)


@app.route("/<int:bars_count>/")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1
    return render_template("chart.html", bars_count=bars_count)


if __name__ == "__main__":
    app.run(debug=True)

上面的代码是一个简短的单路由 Flask 应用程序,它定义了 chart 函数。 chart 接受一个任意整数作为输入,稍后将用于定义我们在条形图中需要多少数据。 render_template 中的chart 函数将使用来自 Flask 的默认模板引擎 Jinja2 的模板来输出 HTML。

最后两行允许我们在调试模式下从命令行在端口 5000 上运行 Flask 应用程序。永远不要在生产环境中使用调试模式,这就是构建像 Gunicorn 这样的 WSGI 服务器的目的。

在您的项目文件夹中创建一个名为 templates 的子目录。在templates 中创建一个文件名chart.htmlchart.html 在我们的 chart 文件的 app.py 函数中被引用,所以我们需要在我们的应用程序正常运行之前创建它。使用以下 Jinja2 标记填充 chart.html

<!DOCTYPE html>
<html>
  <head>
    <title>Bar charts with Bokeh!</title>
  </head>
  <body>
    <h1>Bugs found over the past {{ bars_count }} days</h1>
  </body>
</html>

chart.html 的样板显示通过 URL 传递到 chart 函数的柱数。

<h1> 标记关于发现的错误数量的消息与我们的示例应用程序的主题一致。我们将假装绘制每天运行的自动化测试发现的错误数量的图表。

我们现在可以测试我们的应用程序了。

确保您的 virtualenv 仍然处于激活状态,并且您位于 app.py 所在的项目的基本目录中。使用 app.py 命令运行 python

$(barchart) python app.py

在您的 Web 浏览器中转到 localhost:5000/16/。您应该会看到一条大消息,该消息会在您修改 URL 时发生变化。

没有条形图的简单 Flask 应用程序

我们简单的 Flask 路线已经到位,但这并不是很令人兴奋。是时候添加我们的条形图了。

生成条形图

我们可以在刚刚编写的基本 Flask 应用程序基础上构建一些使用 Bokeh 的新 Python 代码。

打开 app.py 备份并更改文件的顶部以包含以下导入。

import random
from bokeh.models import (HoverTool, FactorRange, Plot, LinearAxis, Grid,
                          Range1d)
from bokeh.models.glyphs import VBar
from bokeh.plotting import figure
from bokeh.charts import Bar
from bokeh.embed import components
from bokeh.models.sources import ColumnDataSource
from flask import Flask, render_template

在文件的其余部分,我们将需要这些 Bokeh 导入以及 random 模块来生成数据和我们的条形图。

我们的条形图将使用“发现的软件错误”作为主题。每次刷新页面时都会随机生成数据。在实际应用程序中,您将拥有更稳定和有用的数据源!

继续修改 app.py,使导入后的部分看起来像下面的代码。

app = Flask(__name__)


@app.route("/<int:bars_count>/")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1

    data = {"days": [], "bugs": [], "costs": []}
    for i in range(1, bars_count + 1):
        data['days'].append(i)
        data['bugs'].append(random.randint(1,100))
        data['costs'].append(random.uniform(1.00, 1000.00))

    hover = create_hover_tool()
    plot = create_bar_chart(data, "Bugs found per day", "days",
                            "bugs", hover)
    script, div = components(plot)

    return render_template("chart.html", bars_count=bars_count,
                           the_div=div, the_script=script)

chart 函数获得了三个由 Python 3 超级方便的随机模块随机生成的新列表。

chart 调用了两个函数,create_hover_toolcreate_bar_chart。我们还没有编写这些函数,所以继续添加下面的代码 chart:

def create_hover_tool():
    # we'll code this function in a moment
    return None


def create_bar_chart(data, title, x_name, y_name, hover_tool=None,
                     width=1200, height=300):
    """Creates a bar chart plot with the exact styling for the centcom
       dashboard. Pass in data as a dictionary, desired plot title,
       name of x axis, y axis and the hover tool HTML.
    """
    source = ColumnDataSource(data)
    xdr = FactorRange(factors=data[x_name])
    ydr = Range1d(start=0,end=max(data[y_name])*1.5)

    tools = []
    if hover_tool:
        tools = [hover_tool,]

    plot = figure(title=title, x_range=xdr, y_range=ydr, plot_width=width,
                  plot_height=height, h_symmetry=False, v_symmetry=False,
                  min_border=0, toolbar_location="above", tools=tools,
                  responsive=True, outline_line_color="#666666")

    glyph = VBar(x=x_name, top=y_name, bottom=0, width=.8,
                 fill_color="#e12127")
    plot.add_glyph(source, glyph)

    xaxis = LinearAxis()
    yaxis = LinearAxis()

    plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
    plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
    plot.toolbar.logo = None
    plot.min_border_top = 0
    plot.xgrid.grid_line_color = None
    plot.ygrid.grid_line_color = "#999999"
    plot.yaxis.axis_label = "Bugs found"
    plot.ygrid.grid_line_alpha = 0.1
    plot.xaxis.axis_label = "Days after app deployment"
    plot.xaxis.major_label_orientation = 1
    return plot

上面有很多新代码,所以让我们分解一下。 create_hover_tool 函数还没有做任何事情,它只是返回 None,如果我们不需要悬停工具,我们可以使用它。悬停工具是一个叠加层,当我们将鼠标光标移到其中一个栏上或触摸触摸屏上的栏时,它会出现,以便我们可以看到有关该栏的更多数据。

create_bar_chart 函数中,我们接收生成的数据源并将其转换为 ColumnDataSource 对象,这是一种我们可以传递给 Bokeh 函数的输入对象.我们为图表的 x 轴和 y 轴指定了两个范围。

由于我们还没有悬停工具,tools 列表将保持为空。我们使用 ## 创建 plot 的行# 函数是许多魔法发生的地方。我们指定了我们希望我们的图形具有的所有参数,例如大小、工具栏、边框以及图形是否应响应更改 Web 浏览器大小。

我们使用 VBar 对象创建垂直条,并使用 add_glyph 函数将它们添加到绘图中,该函数将我们的源数据与 ## #规范。

函数的最后几行修改了图形的外观。例如,我通过指定 Bokeh 去掉了 plot.toolbar.logo = None 标志,并在两个轴上添加了标签。我建议保持 bokeh.plottin 文档处于打开状态,以了解自定义可视化的选项。

我们只需要对 templates/chart.html 文件进行一些更新即可显示可视化效果。打开文件并将这 6 行添加到文件中。其中两行用于所需的 CSS,两行是 JavaScript Bokehfiles,其余两行是生成的图表。

<!DOCTYPE html>
<html>
  <head>
    <title>Bar charts with Bokeh!</title>
    <link href="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.css" rel="stylesheet">
    <link href="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.0.min.css" rel="stylesheet">
  </head>
  <body>
    <h1>Bugs found over the past {{ bars_count }} days</h1>
    {{ the_div|safe }}
    <script src="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.js"></script>
    <script src="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.5.min.js"></script>
    {{ the_script|safe }}
  </body>
</html>

好的,让我们用 4 个柱状图的简单图表来尝试我们的应用程序。当您使用新代码保存 app.py 时,Flask 应用程序应该会自动重新加载,但如果您关闭开发服务器,请使用 python app.py 命令将其重新启动。

打开浏览器到 localhost:5000/4/。

具有 4 个条形的响应式散景条形图。

那个看起来有点稀疏,所以我们可以通过转到 localhost:5000/16/ 将其调高 4 倍至 16 格。

具有 16 个条的响应式散景条形图。

现在使用 localhost:5000/128/… 又增加了 4 倍到 128 根柱线

具有 128 个条的响应式散景条形图。

到目前为止看起来不错。但是那个悬停工具如何深入每个栏以获取更多数据呢?我们可以在 create_hover_tool 函数中通过几行代码添加悬停。

添加悬停工具

app.py 中修改 create_hover_tool 以匹配以下代码。

def create_hover_tool():
    """Generates the HTML for the Bokeh's hover data tool on our graph."""
    hover_html = """
      <div>
        <span >$x</span>
      </div>
      <div>
        <span >@bugs bugs</span>
      </div>
      <div>
        <span >[email protected]{0.00}</span>
      </div>
    """
    return HoverTool(tooltips=hover_html)

将 HTML 嵌入到您的 Python 应用程序中可能看起来很奇怪,但这就是我们指定悬停工具应显示的内容的方式。我们使用 $x 来显示条形图的 x 轴,使用 @bugs 来显示来自我们数据源的“bugs”字段,使用 [email protected]{0.00} 显示格式为美元金额的“费用”字段,精确到小数点后两位。

确保将 return None 更改为 return HoverTool(tooltips=hover_html),以便我们可以在图中看到新函数的结果。

返回浏览器并重新加载 localhost:5000/128/ 页面。

具有 128 个条形并显示悬停工具的响应式散景条形图。

干得好!尝试调整 URL 中的条数和窗口大小,以查看图形在不同条件下的外观。

图表上挤满了 100 多个柱状图,但您可以尝试使用任意数量的柱状图。这是不切实际的 50,000 条柱线看起来的样子:

具有 50000 个条的响应式散景条形图。

是的,我们可能需要做一些额外的工作才能一次显示超过几百个柱。

下一步是什么?

您刚刚在 Bokeh 中创建了一个漂亮的可配置条形图。接下来您可以修改配色方案、更改输入数据源、尝试创建其他类型的图表或解决如何显示大量条形图的问题。

Bokeh 的功能远不止这些,因此请务必查看官方项目文档、GitHub 存储库、Full Stack Python Bokeh 页面或查看有关 Full Stack Python 的其他主题。

有问题吗?在 Twitter@fullstackpython 或@mattmakai 上通过 Full Stack Python 存储库上的 GitHub 问题单让我知道。

看到这篇博文有什么问题了吗?在 GitHub 上创建此页面的源代码并提交拉取请求。

赞(0) 打赏

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏