左对齐每个情节子情节的标题

问题描述 投票:0回答:1

我有一组刻面包装的 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))
但结果是:

python plotly plotly-python facet-grid
1个回答
0
投票

这里有几个主要挑战:

  • 注释不知道它们在 facet plot 中的列(这使得确定 x 值变得困难)
  • 标注的创建顺序是从左到右,从下到上(因此,图底部的标注先创建)
  • 如果子图的数量没有平均划分列数,那么第一个 {remainder} 数量的注释在底行进行,然后后续注释从下一行开始,其中
    {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
,结果也符合预期:

© www.soinside.com 2019 - 2024. All rights reserved.