plotly从入门到精通-实战篇
前言
不同于《基础篇》的通用性,《实战篇》更注重实际应用。在本篇文章中,将会从实际业务场景出发,向大家介绍更贴近工程实践的图像绘制方式。具体来说,《实战篇》分为以下三部分内容:
- 初级实战:动态添加
trace
- 中级实战:双轴图
- 高级实战:双轴多子图
在正文开始之前,先导入需要用到的模块。
import pandas as pd
import plotly
import plotly.graph_objs as go
from plotly.subplots import make_subplots
初级实战:动态添加 trace
先来看这样一份数据:
>>> df = pd.DataFrame({'type': ['wine', 'beer', 'milk', 'juice',
'wine', 'beer', 'milk', 'juice'],
'year': [2020, 2020, 2020, 2020,
2021, 2021, 2021, 2021],
'amt': [750, 820, 600, 453,
560, 720, 750, 650]})
>>> df
type year amt
0 wine 2020 750
1 beer 2020 820
2 milk 2020 600
3 juice 2020 453
4 wine 2021 560
5 beer 2021 720
6 milk 2021 750
7 juice 2021 650
如果说,我们需要比较不同类型的酒水、2020年和2021年销售金额的差异,根据《基础篇》讲解的内容,可以轻松绘制一份柱状图:
df_2020 = df.query('year==2020').copy()
trace_01 = go.Bar(x=df_2020['type'],
y=df_2020['amt'],
name=2020)
df_2021 = df.query('year==2021').copy()
trace_02 = go.Bar(x=df_2021['type'],
y=df_2021['amt'],
ame=2021)
traces = [trace_01, trace_02]
layout = {'title': "Comparison of sales amounts in 2020 and 2021",
'xaxis.title': 'type',
'yaxis.title': 'amt'}
fig = go.Figure(data=traces, layout=layout)
fig.show()
毫无疑问,我们实现了最初的需求。但这样就足够了吗?
思考这样一种场景:如果需要比较四年的数据呢?要手动生成四个trace
吗?如果有更多的年份呢?
显然还存在着更优解,那就是fig.add_trace
:
Signature: fig.add_trace(trace, row=None, col=None, secondary_y=None)
Docstring:
Add a trace to the figure
Parameters
----------
trace : BaseTraceType or dict
Either:
- An instances of a trace classe from the plotly.graph_objs
package (e.g plotly.graph_objs.Scatter, plotly.graph_objs.Bar)
- or a dicts where:
- The 'type' property specifies the trace type (e.g.
'scatter', 'bar', 'area', etc.). If the dict has no 'type'
property then 'scatter' is assumed.
- All remaining properties are passed to the constructor
of the specified trace type.
这里主要介绍的参数就是trace
,其余参数在高级实战中会见到。对于参数trace
,接收两种形式的参数:
- 类的实例。实际就是
go.Bar
或go.Scatter
等生成的实例,这在上面的代码中已经出现了; - 字典。看到帮助文档中的描述,不知各位读者是否还记得
fig.to_dict()['data']
。直接来看具体的例子:
fig = go.Figure()
fig.add_trace({'name': '2020',
'x': ['wine', 'beer', 'milk', 'juice'],
'y': [750, 820, 600, 453],
'type': 'bar'})
fig.add_trace({'name': '2021',
'x': ['wine', 'beer', 'milk', 'juice'],
'y': [560, 720, 750, 650],
'type': 'bar'})
传入fig.add_trace
的内容和fig.to_dict()['data']
返回的内容一模一样。
虽然使用字典传参也能将图像画出来,但有了pandas
的加持,很难想象会有人用这种方式画图。
接下来,使用fig.add_trace
,改写最初的代码,并添加更多的细节:
fig = go.Figure()
for year in [2020, 2021]:
df_temp = df.query('year==@year').copy()
trace = go.Bar(x=df_temp['type'],
y=df_temp['amt'],
name=year,
text=df_temp['amt'],
textposition='outside')
fig.add_trace(trace)
layout = {'title': {'text': 'Comparison of sales amounts in 2020 and 2021',
'x': 0.5,
'xanchor': 'center'},
'xaxis.title': 'type',
'yaxis.title': 'amt',
'font.size': 13}
fig.update_layout(layout)
fig.show()
对于动态添加trace
的内容就先介绍这么多,现在想扯一些“题外话”。对于有matplotlib
使用经验的读者来说,可能会敏锐地注意到一个问题。这里先卖个关子,不妨在揭晓谜底之前用matplotlib
尝试画一下上面的图像:
fig, ax = plt.subplots(figsize=(12, 7))
for year in [2020, 2021]:
df_temp = df.query('year==@year').copy()
ax.bar(x=df_temp['type'],
height=df_temp['amt'],
label=year,
width=0.6)
ax.set_title('Comparison of sales amounts in 2020 and 2021')
ax.set_xlabel('type')
ax.set_ylabel('amt')
ax.legend()
如各位所见,bar
发生了重叠。实际上,要解决这个问题并不难,只要计算好bar
的宽度以及每个bar
应该出现的位置,就能实现并列的效果(seaborn
通过参数hue
可以轻松实现这一点):
import matplotlib.pyplot as plt
import numpy as np
width, height = 10, 5
fig, ax = plt.subplots(figsize=(width, height))
# the bar width
bar_width = (width - 1) / df['type'].nunique() * 0.3
hue_width = bar_width / df['year'].nunique()
x_offset = (bar_width - hue_width) / 2
for index,year in enumerate([2020, 2021]):
df_temp = df.query('year==@year').copy()
ax.bar(x=np.array(range(len(df_temp['type']))) + index * hue_width - x_offset,
height=df_temp['amt'],
label=year,
width=hue_width)
ax.set_title('Comparison of sales amounts in 2020 and 2021')
ax.set_xlabel('type')
ax.set_ylabel('amt')
ax.set_xticks(np.array(range(len(df_temp['type']))))
ax.set_xticklabels([i for i in df_temp['type']])
ax.legend()
对于matplotlib
而言,bar
会发生重叠,所以需要进行额外的处理。但对于plotly
,重叠的现象并没有发生。原因就隐藏在layout
的属性之中。在go.Figure
的帮助文档中,我们可以找到barmode
这个参数:
barmode
Determines how bars at the same location
coordinate are displayed on the graph. With
"stack", the bars are stacked on top of one
another With "relative", the bars are stacked
on top of one another, with negative values
below the axis, positive values above With
"group", the bars are plotted next to one
another centered around the shared location.
With "overlay", the bars are plotted over one
another, you might need to an "opacity" to see
multiple bars.
barmode
参数默认的取值就是group
,所以才避免了重叠的情况。barmode
还有其他的取值,感兴趣的读者可以自行尝试。
中级实战:双轴图
先来介绍绘制双轴图使用的数据:
>>> df
year week cnt temperature
0 2018 1 25 9
1 2018 2 17 7
2 2018 3 19 13
3 2018 4 16 6
4 2018 5 16 7
.. ... ... ... ...
47 2018 48 23 18
48 2018 49 21 15
49 2018 50 21 11
50 2018 51 22 14
51 2018 52 26 9
这是2018年52个周的销售数据和气温数据。现在,想通过折线图,来同时观察销量和气温随时间推移的变化。这个需求很好理解,但关键的地方就在于,销量和气温有各自的数量级,实际上就是两个完全不同的指标,所以不能将他们简单绘制在同一个y
轴上。由此,绘制双轴图的需求就产生了。
在新建trace
的过程中,可以指定trace
要参考的坐标轴,以下是从go.Scatter
中摘录出来的内容:
xaxis
Sets a reference between this trace's x coordinates and
a 2D cartesian x axis. If "x" (the default value), the
x coordinates refer to `layout.xaxis`. If "x2", the x
coordinates refer to `layout.xaxis2`, and so on.
yaxis
Sets a reference between this trace's y coordinates and
a 2D cartesian y axis. If "y" (the default value), the
y coordinates refer to `layout.yaxis`. If "y2", the y
coordinates refer to `layout.yaxis2`, and so on.
依照帮助文档的指示,我们可以这样来组织代码:
trace_01 = go.Scatter(name='cnt',
x=df['week'],
y=df['cnt'])
trace_02 = go.Scatter(name='temperature',
x=df['week'],
y=df['temperature'],
yaxis='y2')
fig = go.Figure(data=[trace_01, trace_02])
fig.show()
但奇怪的事情发生了:指定的y2
轴确实起作用了,但y
和y2
堆叠到了一起,y2
甚至覆盖掉了y
。接下来,需要对此进行改进。我们目标有两个:
- 让两个
y
轴的数据都能显示出来; - 让
y
在左边、y2
在右边;
为了实现这两点,必须要对坐标轴进行更精细的调整。
刚刚摘录出来的帮助文档已经给出了提示:If "y2", the y coordinates refer to layout.yaxis2, and so on.
。在go.Figure
的帮助文档中,对于layout
介绍的部分,可以找到如下有关坐标轴的信息:
xaxis
:class:`plotly.graph_objects.layout.XAxis`
instance or dict with compatible properties
yaxis
:class:`plotly.graph_objects.layout.YAxis`
instance or dict with compatible properties
虽然layout
中没有提到yaxis2
,但当trace
的yaxis
设定为y2
时,yaxis2
就在layout
中诞生了。更重要的是,yaxis
和yaxis2
都是plotly.graph_objects.layout.YAxis
的实例,这意味着二者需要的参数是一致的。
接下来以yaxis
为例,在plotly.graph_objects.layout.YAxis
的帮助文档中,可以找到如下的内容:
overlaying
If set a `overlaying` id, this axis is overlaid on
top of the corresponding same-letter axis, with traces
and axes visible for both axes. If False, this axis
does not overlay any same-letter axes. In this case,
for axes with overlapping domains only the highest-
numbered axis will be visible.
side
Determines whether a x (y) axis is positioned at the
"bottom" ("left") or "top" ("right") of the plotting
area.
对于overlaying
,关键是要理解帮助文档中所说的same-letter axis
,翻译过来就是“同字母的轴”。以当前的例子进行说明,y
和y2
就是同字母的轴。从代码中我们还能得知,y2
的trace
覆盖在y
之上,这里暂时用[y2, y]
来表示这种顺序。
按照overlaying
的说明,如果yaxis={'overlaying': 'y2'}
,顺序就变成了[y, y2]
,也即y
的trace
会变成顶部的trace
,同时自身会变成透明,在其之下的y2
的trace
也会显示出来。同理,如果yaxis2={'overlaying': 'y'}
,顺序仍会是[y2, y]
,但y2
的trace
就会变成透明,y
的trace
就能显示出来了。
对于side
这个参数,就很容易理解了。如果设置yaxis
,可以选择left
或者right
;如果设置xaxis
,可以选择bottom
或者top
。当前,我们的需求是把y2
放到右边,只需要设置yaxis2={'side': 'right'}
就可以了。
下面是完整的代码:
trace_01 = go.Scatter(name='cnt',
x=df['week'],
y=df['cnt'])
trace_02 = go.Scatter(name='temperature',
x=df['week'],
y=df['temperature'],
yaxis='y2')
fig = go.Figure(data=[trace_01, trace_02])
layout = {'title': {'text': 'Comparison of cnt and temperature',
'x': 0.5,
'xanchor': 'center'},
'xaxis.title': 'week',
'yaxis.title': 'cnt',
'yaxis2': {'title': 'temperature',
'overlaying': 'y',
'side': 'right'},
'font.size': 13,
'legend': {'x': 1,
'y': 1.02,
'xanchor': 'right',
'yanchor': 'bottom',
'orientation': 'h'},
}
fig.update_layout(layout)
fig.show()
至此,绘制双轴图的需求就实现好了。总结下来,过程其实非常简单,总共分两步:
- 在
trace
中,将yaxis
设置成y2
, - 在
layout
中,将yaxis2
的overlaying
设置成y
、将side
设置成right
。
双轴图就这样画出来了。
但实际上,overlaying
这个参数却有很多值得探讨的内容。举一个简单例子:
trace_01 = go.Scatter(name='y', x=[1, 2, 3, 4, 5], y=[1, 1, 1, 1, 1])
trace_02 = go.Scatter(name='y2', x=[1, 2, 3, 4, 5], y=[2, 2, 2, 2, 2], yaxis='y2')
trace_03 = go.Scatter(name='y3', x=[1, 2, 3, 4, 5], y=[3, 3, 3, 3, 3], yaxis='y3')
fig = go.Figure(data=[trace_01, trace_02, trace_03])
# 图像省略,读者可自行绘制
毫无疑问,我们只能看到y3
,因为它的trace
在最顶端,y
和y2
都被y3
给遮挡住了,此时trace
的呈现顺序为[y3, y2, y]
。但在layout
中,对yaxis.overlaying
进行不同的设置,会出现一些有趣的结果:
- 如果设置
yaxis={'overlaying': 'y3'}
,此时顺序会变成[y, y3, y2]
,同时y
也会变成透明,底下的y3
也能显示出来。但是,我们看不到y2
,因为y3
还不是透明的,y2
依旧处于被遮挡的状态; - 如果设置
yaxis2={'overlaying': 'y3'}
,此时顺序会变成[y2, y3, y]
,但我们只能看到y2
和y3
,看不到y
,因为y3
不透明; - 如果设置
yaxis={'overlaying': 'y2'}
,注意,此时顺序会变成[y3, y, y2]
,y3
依旧在最顶端,只有y
和y2
的顺序发生了变化。最终,虽然y
会变透明,但y3
没有,所以我们还是只能看到y3
; - 如果设置
yaxis={'overlaying': 'y3'}, yaxis2={'overlaying': 'y3'}
,此时顺序会变成[y2, y, y3]
,同时,所有的trace
都能看见了。
说到这里,overlaying
的使用规律基本已经摸清了,所以对于它的探索也可以告一段落了。
像overlaying
这种表面人畜无害实则包藏祸心的参数在plotly
中不是少数,感兴趣的读者可以在实践过程中多尝试几次。探索的过程也未尝不充满乐趣。
高级实战:双轴多子图
在中级实战:双轴图中,我们使用了一年的数据去绘制图像,如果说,现在需要绘制四年数据的图像,甚至更进一步,将四年的图像以 2 ∗ 2 2*2 2∗2 的布局放在同一个画布上,这样的需求,要如何去实现呢?
先来看一下需要用到的数据:
>>> df
year week cnt temperature
0 2018 1 25 9
1 2018 2 17 7
2 2018 3 19 13
3 2018 4 16 6
4 2018 5 16 7
.. ... ... ... ...
203 2021 48 12 15
204 2021 49 11 13
205 2021 50 11 16
206 2021 51 11 12
207 2021 52 11 12
[208 rows x 4 columns]
这是2018年到2021年共计四年208个周的销售数据和气温数据,通过下面的代码,就可以将它们呈现在 2 ∗ 2 2*2 2∗2 的多子图中:
from plotly.subplots import make_subplots
fig = make_subplots(rows=2, cols=2,
vertical_spacing=0.12,
horizontal_spacing=0.09,
subplot_titles=['2018', '2019', '2020', '2021'],
specs=[
[{'secondary_y': True}, {'secondary_y': True}],
[{'secondary_y': True}, {'secondary_y': True}],
],
)
# 统一 y 轴的范围
min_y1 = df['cnt'].min()
max_y1 = df['cnt'].max()
adjust_y1 = (max_y1 - min_y1) * 0.05
min_y2 = df['temperature'].min()
max_y2 = df['temperature'].max()
adjust_y2 = (max_y2 - min_y2) * 0.05
# 使用循环绘制 4 张子图
for year,(row,col),showlegend in [[2018, (1,1), True], [2019, (1,2), False],
[2020, (2,1), False], [2021, (2,2), False]]:
df_temp = df.query('year==@year').copy()
trace_01 = go.Scatter(name='cnt',
x=df_temp['week'],
y=df_temp['cnt'],
marker=dict(color="#636EFA"),
legendgroup='indicator',
showlegend=showlegend,
)
fig.add_trace(trace_01,
row=row,
col=col,
secondary_y=False, # 主轴
)
trace_02 = go.Scatter(name='temperature',
x=df_temp['week'],
y=df_temp['temperature'],
marker=dict(color="black"),
legendgroup='factor',
showlegend=showlegend,
)
fig.add_trace(trace_02,
row=row,
col=col,
secondary_y=True, # 副轴
)
# 调整图像的格式
fig.update_layout(height=800,
width=1400,
title=dict(text='Comparison of cnt and temperature',
x=0.5,
xanchor='center'),
legend=dict(orientation="h",
x=0.95,
y=1.03,
xanchor='right',
yanchor='bottom'
),
)
fig.update_xaxes(title='week')
fig.update_yaxes(title='cnt',
range=[min_y1 - adjust_y1, max_y1 + adjust_y1],
secondary_y=False)
fig.update_yaxes(title='temperature',
range=[min_y2 - adjust_y2, max_y2 + adjust_y2],
secondary_y=True)
fig.show()
下面对代码进行详细地说明:
-
make_subplots
Signature: make_subplots(rows=1, cols=1, shared_xaxes=False, shared_yaxes=False, start_cell='top-left', print_grid=False, horizontal_spacing=None, vertical_spacing=None, subplot_titles=None, column_widths=None, row_heights=None, specs=None, insets=None, column_titles=None, row_titles=None, x_title=None, y_title=None, **kwargs) Docstring: Return an instance of plotly.graph_objs.Figure with predefined subplots configured in 'layout'.
从
Docstring
的描述中,我们就能知道,make_subplots
和go.Figure
返回的是同一个对象,只不过make_subplots
会提前对layout
进行一些配置。下面是在代码中是使用的参数,其余参数感兴趣的读者可以自行尝试:-
rows
&cols
:整个画布中,子图的行数和列数。也即每行或每列子图的数量 -
horizontal_spacing
&vertical_spacing
:子图间的水平间距和垂直间距 -
subplot_titles
:子图的标题,按照从左往右、从上到下的顺序依次和子图对应起来 -
specs
:用来对每一个子图进行精细设置。它接收一个二维列表,外层索引对应子图的行序号,内层索引对应列序号。在列表的内部,接收一个字典,字典的键值对就是属性及其取值,下面来看从帮助文档中摘录出来的内容:specs: list of lists of dict or None (default None) Per subplot specifications of subplot type, row/column spanning, and spacing. ex1: specs=[[{}, {}], [{'colspan': 2}, None]] ex2: specs=[[{'rowspan': 2}, {}], [None, {}]] - Each item in 'specs' is a dictionary. The available keys are: ...... * secondary_y (bool, default False): If True, create a secondary y-axis positioned on the right side of the subplot. Only valid if type='xy'.
在上面的代码中,我们对子图的
secondary_y
属性进行了设置,为所有子图开启了右侧的副轴。
-
-
go.Scatter
legendgroup Sets the legend group for this trace. Traces part of the same legend group hide/show at the same time when toggling legend items. showlegend Determines whether or not an item corresponding to this trace is shown in the legend.
先说一下使用这两个参数的原因。在代码中将这两个参数注释掉,得到的图像将会是这样:
![plotly_advanced_08][]
差别就在图例上。我们可以总结出以下两点:
- 每个子图,都有一组图例,并且在操作某一子图的图例时,不会影响到其他三个子图;
- 四个子图的图例都是重复的;
对于当前的需求而言,只需要保留一组图例。同时,还需要将所有子图的图例都统一起来,用一组图例来同时控制四个子图。
legendgroup
和showlegend
就用来实现这样的功能。为了将四个子图统一起来,我们就需要使用
legendgroup
,对子图的trace
进行编组。在代码中,我们分别对cnt
和temperature
进行了编组,这样就能保证子图间图例行为的一致性。那既然图例的行为统一了,接下来只要保留一组图例就可以了。showlegend
就帮助我们将其余三组图例隐藏起来。 -
fig.add_trace
现在,正式回收在初级实战中遗留下来的问题。
row : int or None (default None) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using `plotly.subplots.make_subplots` col : int or None (default None) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using `plotly.subplots.make_subplots` secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the following conditions are satisfied: * The figure was created using `plotly.subplots.make_subplots`. * The row and col arguments are not None * The subplot at the specified row and col has type xy (which is the default) and secondary_y True. These properties are specified in the specs argument to make_subplots. See the make_subplots docstring for more info. * The trace argument is a 2D cartesian trace (scatter, bar, etc.)
如大家所见,剩下的三个参数都跟子图相关。
row
&col
:将trace
定位到指定的子图上,比如row=2, col=1
指的就是第二行第一列的子图。secondary_y
:是否将trace
关联到副轴上。在代码中,我们将温度的数据全都关联到了副轴上。
在中级实战:双轴图里,我们通过设置
trace
和layout
的yaxis
参数来实现双轴的效果,而绘制子图时,主要依靠secondary_y
参数来实现双轴的效果。 -
fig.update_xaxes
在之前的讲解中,主要是通过
fig.update_layout
来对xaxis
进行设置。但到了多子图中,fig.update_layout
只能修改一个子图,无法满足我们当前的需求。所以,我们选择使用fig.update_xaxes
,以达到修改所有子图的目的:Signature: fig.update_xaxes(patch=None, selector=None, overwrite=False, row=None, col=None, **kwargs) Docstring: Perform a property update operation on all xaxis objects that satisfy the specified selection criteria Parameters ---------- patch: dict Dictionary of property updates to be applied to all xaxis objects that satisfy the selection criteria. selector: dict or None (default None) Dict to use as selection criteria. xaxis objects will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match the supplied values. If None (the default), all xaxis objects are selected.
从帮助文档的内容来看,是不是有种似曾相识的感觉。没错,和
fig.update_traces
十分相似,尤其是最核心的两个参数patch
和selector
,连使用规则都一样,只不过当前接收的参数,都来自plotly.graph_objects.layout.XAxis
。 -
fig.update_yaxes
使用这个方法的原因有两个,一是要同时修改所有子图,二是要分别修改主轴和副轴。先看帮助文档:
Signature: fig.update_yaxes(patch=None, selector=None, overwrite=False, row=None, col=None, secondary_y=None, **kwargs) Docstring: Perform a property update operation on all yaxis objects that satisfy the specified selection criteria
patch
&selector
:它们接收的参数,都来自plotly.graph_objects.layout.YAxis
overwrite
:如果是False
,更新属性;如果是True,覆盖属性。row
&col
:用来指定待修改的子图。如果没有指定,则默认所有子图。secondary_y
:如果是True
,将会对副轴进行属性的更新;如果是False
,则会对主轴进行属性的更新
代码已经介绍完了,根据需求的变化,还可以添加更多的trace
,比如有多个商品的情况:
>>> df
year week item01 item02 item03 temperature
0 2018 1 25 22 28 9
1 2018 2 17 13 23 7
2 2018 3 19 14 20 13
3 2018 4 16 16 22 6
4 2018 5 16 15 17 7
.. ... ... ... ... ... ...
203 2021 48 12 15 22 15
204 2021 49 11 11 19 13
205 2021 50 11 15 12 16
206 2021 51 11 13 21 12
207 2021 52 11 6 13 12
[208 rows x 6 columns]
item_list = ['item01', 'item02', 'item03']
colors_list = ['#636EFA', '#EF553B', '#00CC96', '#AB63FA', '#FFA15A',
'#19D3F3', '#FF6692', '#B6E880', '#FF97FF', '#FECB52']
fig = make_subplots(rows=2, cols=2,
vertical_spacing=0.12,
horizontal_spacing=0.09,
subplot_titles=['2018', '2019', '2020', '2021'],
specs=[
[{'secondary_y': True}, {'secondary_y': True}],
[{'secondary_y': True}, {'secondary_y': True}],
],
)
# 统一 y 轴的范围
min_y1 = df.loc[:, item_list].values.min()
max_y1 = df.loc[:, item_list].values.max()
adjust_y1 = (max_y1 - min_y1) * 0.05
min_y2 = df['temperature'].min()
max_y2 = df['temperature'].max()
adjust_y2 = (max_y2 - min_y2) * 0.05
# 使用循环绘制 4 张子图
for year,(row,col),showlegend in [[2018, (1,1), True], [2019, (1,2), False],
[2020, (2,1), False], [2021, (2,2), False]]:
df_temp = df.query('year==@year').copy()
for i,item in enumerate(item_list):
trace_01 = go.Scatter(name=item,
x=df_temp['week'],
y=df_temp[item],
marker={"color": colors_list[i%10]},
legendgroup='group' + str(i),
showlegend=showlegend,
)
fig.add_trace(trace_01,
row=row,
col=col,
secondary_y=False, # 主轴
)
trace_02 = go.Scatter(name='temperature',
x=df_temp['week'],
y=df_temp['temperature'],
marker=dict(color="black"),
legendgroup='factor',
showlegend=showlegend,
)
fig.add_trace(trace_02,
row=row,
col=col,
secondary_y=True, # 副轴
)
# 调整图像的格式
fig.update_layout(height=800,
width=1400,
title=dict(text='Comparison of cnt and temperature',
x=0.5,
xanchor='center'),
legend=dict(orientation="h",
x=0.95,
y=1.03,
xanchor='right',
yanchor='bottom'
),
)
fig.update_xaxes(title='week')
fig.update_yaxes(title='cnt',
range=[min_y1 - adjust_y1, max_y1 + adjust_y1],
secondary_y=False)
fig.update_yaxes(title='temperature', overwrite=True,
range=[min_y2 - adjust_y2, max_y2 + adjust_y2],
secondary_y=True)
fig.show()
![plotly_advanced_09][]
到这里,双轴多子图的讲解就结束了,读者可以在此基础上进行更多的尝试。
后记
plotly
为我们构筑了一个相当广阔的世界。企图在有限的文章中将所有重点向各位读者娓娓道来,只能是痴人说梦。所以作者本人在起草两篇文章的时候,内心满怀着“授人以渔”的美好憧憬,并寄希望于读者自发进行举一反三。这实际是一种“逆势而为”。打开任何一个搜索引擎,能找到的画图教程,无一不是开门见山地讲述着实际案例,字里行间都充斥着一种务实精神。对于这种表达方式,无意推崇更无意批评,只是这样教程,过于浅显,能解决的需求也是过于单一。一旦我们的需求和教程罗列的事例有些许偏差,我们就会陷于一种尴尬的境地。总结下来就是,“我们的问题好像解决了,又好像没有解决”。这对读者的精神打击是巨大的。“好不容易”、“千辛万苦”找到的“救命稻草”,不过是一厢情愿。成年人的崩溃往往就在这一瞬间。这哪是“救命稻草”,这是“压死骆驼的最后一根稻草”。
所以,在文章写作之初,为了避免这种“破大防”的情况出现,我决定使用一种颇为冷门的讲述方式来组织这份教程,也就是从基础概念讲起,不厌其烦地带领读者翻阅有着茫茫多参数的帮助文档。这正是因为,答案基本都藏在帮助文档之中。不过,这种方式并非我的原创,虽然它一直都存在,但鲜有人用。个人推测,是读者更务实了,而作者群体嗅到了这一丝躁动,并逐渐开始灵活地迎合群众的喜好。正如萧伯纳所言,“流行的就一定高尚吗”,所以,对于这份教程来说,虽然不是用读者广泛认可的形式进行讲解,但未必没有价值,起码在涤荡鱼龙混杂的互联网创作氛围上,做出了自己的努力。
但理想终究是丰满的。在文章写作的过程中,难免有力不从心的时候。所以,借此机会,对文章中出现的瑕疵进行澄清:这并非作者本人一贯的敷衍,而是因为作者能力有限,不足以支撑丰满的理想,而瑕疵的出现,实属无心之举。但作者也在这里做出保证,一定会对文章进行持续的更新,给各位读者一个交代。