楔子
前面我们介绍了 plotly 的基本用法,学习了如何绘制各种各样的图表,以及调整轨迹和画布的属性。但还有一些遗漏的部分没有说,就是绘制子图以及图表的保存,下面就来看一下这些内容。
绘制子图
所谓的绘制子图就是将一个画布分为多个区域,每个区域展示各自的图表。
# 创建子图使用make_subplots from plotly.subplots import make_subplots trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]) trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6]) trace2 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]) trace3 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6]) fig = make_subplots(rows=2, # 将画布分为两行 cols=2, # 将画布分为两列 # 子图的标题 subplot_titles=["trace0的标题", "trace1的标题", "trace3的标题", "trace4的标题"], x_title="x轴标题", y_title="y轴标题") # 添加轨迹 # 将trace0添加到第一行第一列的位置 fig.append_trace(trace0, 1, 1) # 将trace1添加到第一行第二列的位置 fig.append_trace(trace1, 1, 2) # 将trace2添加到第二行第一列的位置 fig.append_trace(trace2, 2, 1) # 将trace3添加到第二行第二列的位置 fig.append_trace(trace3, 2, 2) fig
看一下绘制出来的结果如何:
此时我们就成功绘制出来了子图,但是有一点不完美的地方是,它的这个坐标轴的标题是针对整体的。可不可以对不同的坐标轴施加不同的描述呢?显然是可以的。
但是这里需要再补充一下,我们之前创建画布的时候是通过 go.Figure,然后将画布属性通过字典的方式先写好,然后传给 layout 参数。但事实上我们也可以先创建画布,后续再对属性进行修改,比如:
fig["layout"]["xaxis"].update({"title": "x坐标轴"})
这样做也是可以的。fig["data"] 拿到的就是所有的轨迹,我们也可以往里面加入新的轨迹;fig["layout"] 则是拿到的是画布的属性,一个字典,我们同样可以往里面添加、删除属性。
之所以说这些,就是因为 make_subplots 里面没办法直接传递 layout,只能先创建出画布,然后手动获取里面的 layout,再添加属性。
# 创建子图使用make_subplots from plotly.subplots import make_subplots trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]) trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6]) trace2 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]) trace3 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6]) fig = make_subplots(rows=2, # 将画布分为两行 cols=2, # 将画布分为两列 # 子图的标题 subplot_titles=["trace0的标题", "trace1的标题", "trace3的标题", "trace4的标题"]) fig.append_trace(trace0, 1, 1) fig.append_trace(trace1, 1, 2) fig.append_trace(trace2, 2, 1) fig.append_trace(trace3, 2, 2) # 创建了四个子图,自动就会有四个坐标轴。 # 每个轨迹占一个,这些都是自动分配好的 # 比如第 N 个轨迹的坐标轴就是 xaxisN 和 yaxisN fig["layout"]["xaxis"].update({"title": "trace0的x轴", "titlefont": {"color": "red"}}) fig["layout"]["yaxis"].update({"title": "trace0的y轴", "titlefont": {"color": "red"}}) fig["layout"]["xaxis2"].update({"title": "trace1的x轴", "titlefont": {"color": "green"}}) fig["layout"]["yaxis2"].update({"title": "trace1的y轴", "titlefont": {"color": "green"}}) fig["layout"]["xaxis3"].update({"title": "trace2的x轴", "titlefont": {"color": "pink"}}) fig["layout"]["yaxis3"].update({"title": "trace2的y轴", "titlefont": {"color": "pink"}}) fig["layout"]["xaxis4"].update({"title": "trace3的x轴", "titlefont": {"color": "yellow"}}) fig["layout"]["yaxis4"].update({"title": "trace3的y轴", "titlefont": {"color": "yellow"}}) fig["layout"]["template"] = "plotly_dark" fig
此时我们就成功绘制好了子图,并调整了标题。既然标题可以调整,那么其它属性,比如刻度之类的,也是可以调整的。
绘制子图(方式二)
绘制子图还有另一种方式:
trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]) trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6], # 表示该轨迹将应用于第二坐标轴 # make_subplots 是自动分配的 # 但这里必须手动指定 # 要是第三坐标轴就指定为 "x3" 和 "y3" xaxis="x2", yaxis="y2") fig = go.Figure( data=[trace0, trace1], layout={ # 指定第一幅图的范围是0到百分之60 "xaxis": { "domain": [0, 0.6] }, # 第二幅图的范围是百分之70到百分之百 "xaxis2": { "domain": [0.7, 1] }, # 第二个 y 轴的坐标位置,应该在第二幅图的左边 # 不指定的话,那么两幅子图的 y 轴就重合了 "yaxis2": { "anchor": "x2" }, "template": "plotly_dark" }) fig
这种绘制子图的方式可能不是容易让人理解,主要就在于里面的 domain 参数。如果不指定 domain 的话,可以认为是两张子图(都占据全部位置)重合了,现在通过 domain 将两幅子图分开了。因此相比 make_subplots,这种绘制子图的方式有点让人费解,而且我们还要计算好位置。
但是这种绘制子图的方式的好处就在于我们可以自定义子图的位置,如果使用 make_subplots 的话,多个子图的大小是一样的,并且间隔什么的是等分的,但是 domain 这种方式就不同了。
trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]) trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6], xaxis="x2", yaxis="y2") fig = go.Figure(data=[trace0, trace1], layout={# xaxis不指定,则默认是占据全部 # 我们只指定xaxis2和yaxis2 "xaxis2": {"domain": [0.6, 0.95], "anchor": "y2"}, "yaxis2": {"domain": [0.1, 0.4], "anchor": "x2"}, "template": "plotly_dark"}) fig
我们看到一张图出现在了另一张图的里面,这是因为第一坐标轴、也就是默认的轴,它的范围是百分之百的。而我们指定的第二坐标轴的 x 方向和 y 方向只占据了一部分,那么使用该坐标轴的轨迹就会显示在局部,而不是像之前一样占满全部范围。
而绘制出类似于 make_subplots 的方式,则是让每个轴的 domain 不重合即可,然后计算好位置。所以这种方式应该说更强大,只不过对于绘制标准的、一个子图占一个坑、规规整整排列的多个子图的话,还是推荐使用 make_subplots,而 domain 这种方式则是让我们自定义的。
而最后再说说那个轴里面的 anchor,这个老铁主要是控制坐标轴显示在什么地方的。对于绘制子图而言,我们只需将第 n 坐标轴的 x 轴的 "anchor" 指定为 yn、第 n 坐标轴的 y 轴的 "anchor" 指定为 xn 即可。
绘制双坐标轴
多坐标轴是最复杂的地方,那为什么会有多个坐标轴呢。首先一张画布默认只有一个坐标轴,但如果绘制的多个轨迹的数值相差比较大,那么 plotly 为了显示所有的轨迹,就会把坐标轴往大的方向调整,从而导致数值小的轨迹不明显。我们举个例子看一下就很直观了:
x0 = np.linspace(1, 100, 5) x1 = np.linspace(0, 1000, 5) y0 = x0 ** 2 + x0 + 1 y1 = x1 ** 2 + x1 + 1 trace0 = go.Scatter(x=x0, y=y0) trace1 = go.Scatter(x=x1, y=y1) fig = go.Figure(data=[trace0, trace1], layout={"template": "plotly_dark"}) fig
plotly 为了展示所有的轨迹,坐标轴上刻度会按照数值大的一方来显示,因此这就造成了数值小的轨迹的局部细节展示不出来。所以在轨迹的数值相差比较大的时候,我们需要绘制第二个坐标轴。
x0 = np.linspace(1, 100, 5) x1 = np.linspace(0, 1000, 5) y0 = x0**2 + x0 + 1 y1 = x1**2 + x1 + 1 trace0 = go.Scatter(x=x0, y=y0) trace1 = go.Scatter(x=x1, y=y1, yaxis="y2", xaxis="x2") fig = go.Figure( data=[trace0, trace1], layout={ # 由于在同一个画布上,所以网格会重叠 # 我们将其中的一个轴的网格给隐藏掉,还有 zeroline # 当然这些参数都是关于视觉上的,对于表达数据则无影响 # 具体如何影响图表的模样,可以自己测试一下 "xaxis": { "title": "trace0的x轴", "showgrid": False, "zeroline": False }, "yaxis": { "title": "trace0的y轴", "showgrid": False }, # 这里就是第二坐标轴的x轴了 "xaxis2": { "title": "trace1的x轴", # 这个很重要,要设置方向,显然设置为上方 "side": "top", # 必须要写,否则图像无法显示 # 同理下面的yaxis也是类似 "overlaying": "x" }, "yaxis2": { "title": "trace1的y轴", # 第二y轴要设置在右边 "side": "right", "overlaying": "y" }, "template": "plotly_dark" }) fig
此时就绘制出来了,因为现在两个轨迹的坐标不是同一个坐标了。但是它们之间的规律是一样的,再加上又显示在同一个画布上,所以轨迹是差不多重合的。
图表的保存
最后我们来看一下如何保存图表,准确的说是将整个画布保存起来。首先保存的格式主要有三种:html、json 和图片。
# 直接调用画布的 fig.write_xxx 方法即可 fig.write_html("xxx.html") fig.write_json("xxx.json") fig.write_image("xxx.png") # 还可以导入一个模块 import plotly.io as pio pio.write_html(fig, "xxx.html") pio.write_json(fig, "xxx.json") pio.write_image(fig, "xxx.png")
注意保存为 html 的时候,里面显示的是 js 生成的动态可交互数据,所以保存的 html 会比较大,大概有几 M。
如果是保存为 json,那么里面都是图表的核心数据。
最后是保存为图片,这应该是最常用的,因为很多时候我们绘制完图表之后需要展示在网页上或者做其它的什么事情,总是就是需要一个图片。
但保存为图片稍微有点复杂,如果是保存为 html 和 json 的话,直接保存即可,但是保存为图片不行,需要一个东西,叫 orca。估计很多小伙伴在保存为图片的时候都遇到这个问题:
原因就在于你没有安装 orca,所以我们需要安装一下。而安装方式也很简单,直接去 GitHub 上下载即可。
Windows 和 Mac 的安装过程就不说了,直接像安装普通软件一样安装就行,然后安装完毕之后再将 orca 可执行文件的路径配置到环境变量。
而 Linux 安装过程如下:
# 安装下面这些,不管有没有,最好都执行一下 yum install fuse-libs-2.9.2-11.el7.x86_64 yum install gtk2-2.24.31-1.el7.x86_64 yum install desktop-file-utils yum install Xvfb yum install xdg-utils-1.1.0-0.17.20120809git.el7.noarch # 执行以下命令,这里的 xxx.AppImage 就是相应的安装文件 xvfb-run -a xxx.AppImage "$@" # 然后改个名 mv xxx.AppImage orca # 赋予执行权限 chmod 755 orca # 添加到环境变量,输入 orca --help 查看 # 正常输出,则安装成功
安装完 orca 之后,再生成图片就没有问题了。
另外除了 write_image,还有一个 to_image,这个就不是保存成图片了,而是直接返回图片的字节流,相当于使用 rb 模式对图片进行读取。比起图片本身,我们可能更常用字节流,直接渲染到页面上,或者生成 base64 字符串。
同理还有 to_html、to_json,可以自己尝试一下。另外 to_image 同样需要 orca,而 to_html 和 to_json 则不需要。
小结
到此,关于 plotly 的内容我们就说完了。plotly 的使用难度还是有的,主要是里面的参数非常多,并且我们也只介绍了一部分,因为有很多参数主要是设置交互场景的,比如鼠标悬浮时显示指定内容等等。
你也可以自己尝试绘制几张图表,慢慢熟悉 plotly。