我有一组刻面包装的 plotly express barplots ,每个都有一个标题。如何将每个子图的标题与其绘图窗口的左侧对齐?
import lorem
import plotly.express as px
import numpy as np
import random
items = np.repeat([lorem.sentence() for i in range(10)], 5)
response = list(range(1,6)) * 10
n = [random.randint(0, 10) for i in range(50)]
(
px.bar(x=response, y=n, facet_col=items, facet_col_wrap=4, height=1300)
.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
.for_each_xaxis(lambda xaxis: xaxis.update(showticklabels=True))
.for_each_yaxis(lambda yaxis: yaxis.update(showticklabels=True))
.show()
)
我尝试添加
.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1], x=0))
但结果是:
这里有几个主要挑战:
{remainder} = {number of annotations} % {number of columns}
帮助形象化:
x x x x
x x x x ⭠ followed by these ones left to right, ...
x x ⭠ these annotations are created first left to right
这意味着我们想要遍历
(annotation1, x_location_of_col1), (annotation2, x_location_of_col2), (annotation3, x_location_of_col1)...
因为标题的位置是基于知道列何时重新启动。
在您的构面图中,有 10 个注释、总共 12 个子图和 4 列。因此,我们想要跟踪我们可以从布局中提取的每个子图的 x 值:
fig.layout['xaxis'], fig.layout['xaxis2']...
中的信息包含纸张坐标中每个子图的起始 x 值,我们可以使用最多 'xaxis4 的信息'(因为我们有 4 列),并将此信息存储在 x_axis_start_positions
中,在我们的例子中是 [0.0, 0.255, 0.51, 0.7649999999999999]
(这是在我的代码中派生的,我们绝对不想对此进行硬编码)
然后利用有4列,10个注释,12个绘图的事实,我们可以计算出有
10 // 4 = 2 full rows of plots
,并且第一行在创建的第一行注释中有10 % 4 = 2 plots
我们可以将此信息提取到我们迭代的 x 个起始位置以放置每个标题:
x_axis_start_positions_iterator = x_axis_start_positions[:remainder] + x_axis_start_positions*number_of_full_rows
# [0.0, 0.255, 0.0, 0.255, 0.51, 0.7649999999999999, 0.0, 0.255, 0.51, 0.7649999999999999]
然后我们遍历所有 10 个注释和标题的 10 个起始位置,覆盖自动生成的注释的位置。
import lorem
import plotly.express as px
import numpy as np
import random
items = np.repeat([lorem.sentence() for i in range(10)], 5)
response = list(range(1,6)) * 10
n = [random.randint(0, 10) for i in range(50)]
## we should keep this variable
facet_col_wrap = 3
fig = (
px.bar(x=response, y=n, facet_col=items, facet_col_wrap=facet_col_wrap, height=1300)
.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
.for_each_xaxis(lambda xaxis: xaxis.update(showticklabels=True))
.for_each_yaxis(lambda yaxis: yaxis.update(showticklabels=True))
)
# x x x x
# x x x x <-- then these annotations
# x x <-- these annotations are created first
## we need to know the remainder
## because when we iterate through annotations
## they need to know what column they are in
## (and annotations natively contain no such information)
remainder = len(fig.data) % facet_col_wrap
number_of_full_rows = len(fig.data) // facet_col_wrap
annotations = fig.layout.annotations
xaxis_col_strings = list(range(1, facet_col_wrap+1))
xaxis_col_strings[0] = ''
x_axis_start_positions = [fig.layout[f'xaxis{i}']['domain'][0] for i in xaxis_col_strings]
if remainder == 0:
x_axis_start_positions_iterator = x_axis_start_positions*number_of_full_rows
else:
x_axis_start_positions_iterator = x_axis_start_positions[:remainder] + x_axis_start_positions*number_of_full_rows
for a, x in zip(annotations, x_axis_start_positions_iterator):
a['x'] = x
a['xanchor'] = 'left'
fig.layout.annotations = annotations
fig.show()
而如果我们改变
facet_col_wrap = 3
,结果也符合预期: