在Matplotlib,Python3的极坐标/径向条形图中的条形上方获取标签 [英] Getting Labels on top of Bar in Polar/Radial Bar Chart in Matplotlib, Python3

查看:85
本文介绍了在Matplotlib,Python3的极坐标/径向条形图中的条形上方获取标签的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个径向条形图.我有以下Python3代码:

I want to create a radial bar chart. I have the following Python3 code:

lObjectsALLcnts = [1, 1, 1, 2, 2, 3, 5, 14, 15, 20, 32, 33, 51, 1, 1, 2, 2, 3, 3, 3, 3, 3, 4, 6, 7, 7, 10, 10, 14, 14, 14, 17, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 6, 14, 14, 27, 27, 1, 1, 2, 3, 4, 4, 5]`

lObjectsALLlbls = ['DuctPipe', 'Column', 'Protrusion', 'Tree', 'Pole', 'Bar', 'Undefined', 'EarthingConductor', 'Grooves', 'UtilityPipe', 'Cables', 'RainPipe', 'Moulding', 'Intrusion', 'PowerPlug', 'UtilityBox', 'Balcony', 'Lighting', 'Lock', 'Doorbell', 'Alarm', 'LetterBox', 'Grate', 'Undefined', 'CableBox', 'Canopy', 'Vent', 'PowerBox', 'UtilityHole', 'Recess', 'Protrusion', 'Shutter', 'Handrail', 'Lock', 'Mirror', 'SecuritySpike', 'Bench', 'Intrusion', 'Picture', 'Showcase', 'Camera', 'Undefined', 'Stair', 'Protrusion', 'Alarm', 'Graffiti', 'Lighting', 'Ornaments', 'SecurityBar', 'Grate', 'Vent', 'Lighting', 'UtilityHole', 'Intrusion', 'Undefined', 'Protrusion']

iN = len(lObjectsALLcnts)
arrCnts = np.array(lObjectsALLcnts)

theta=np.arange(0,2*np.pi,2*np.pi/iN)
width = (2*np.pi)/iN *0.9

fig = plt.figure(figsize=(8, 8))
ax = fig.add_axes([0.1, 0.1, 0.75, 0.75], polar=True)
bars = ax.bar(theta, arrCnts, width=width, bottom=50)
ax.set_xticks(theta)
plt.axis('off')

它将创建以下图像:

radialbartchart_nolabels

radialbartchart_nolabels

创建此标签后,我想添加标签,但是在找到正确的坐标时遇到了一些麻烦.标签应沿条的方向旋转.

After creating this I would like to add labels, but I'm having a bit of troubles finding the right coordinates. The labels should be rotated along the directions of the bars.

我想出的最好办法是添加以下代码:

The best I've come up with is adding the following code:

rotations = [np.degrees(i) for i in theta]
for i in rotations: i = int(i)
for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls):
     height = bar.get_height() + 50
     ax.text(x + bar.get_width()/2, height, label, ha='center', va='bottom', rotation=rotation)

这将创建以下内容:

radialbarchart_wlabels

radialbarchart_wlabels

是否可以帮助我找到标签的正确坐标?我一直在寻找诸如在matplotlib条形图 并将其翻译成极坐标条形图.但是没有成功.

Can some help me with finding the right coordinates for the labels? I've been looking in to answers like Adding value labels on a matplotlib bar chart and translating it to the polar bar chart. But with no success.

预先感谢

关于StackOverflow的读者很久了,但是第一次我找不到答案.

A long time reader on StackOverflow, but for the first time I couldn't find an answer.

推荐答案

您遇到的问题是,文本边界框已展开以容纳完整的旋转文本,但是该框本身仍是在笛卡尔坐标中定义的.下图显示了两个文本,水平对齐为左",垂直对齐为底";问题是旋转后的文本的边界框边缘距离文本更远.

The problem you run into is that the text bounding box is expanded to host the complete rotated text, but that box itself is still defined in cartesian coordinates. The picture below shows two texts with horizontalalignment "left" and vertical alignment "bottom"; the problem is that the rotated text has its bounding box edge much further away from the text.

您想要的是让文本围绕其自身周围的边缘点旋转,如下所示.

What you want is rather to have the text rotate about an edge point of its own surrounding as below.

这可以通过使用matplotlib.text.Textrotation_mode="anchor"参数来实现,该参数完全可以控制上述功能.

This can be achieved using the rotation_mode="anchor" argument to matplotlib.text.Text, which steers exactly the above functionality.

ax.text(..., rotation_mode="anchor")

在此示例中:

from matplotlib import pyplot as plt
import numpy as np

lObjectsALLcnts = [1, 1, 1, 2, 2, 3, 5, 14, 15, 20, 32, 33, 51, 1, 1, 2, 2, 3, 3, 3, 3, 
                   3, 4, 6, 7, 7, 10, 10, 14, 14, 14, 17, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 
                   5, 5, 6, 14, 14, 27, 27, 1, 1, 2, 3, 4, 4, 5]

lObjectsALLlbls = ['DuctPipe', 'Column', 'Protrusion', 'Tree', 'Pole', 'Bar', 'Undefined', 
                   'EarthingConductor', 'Grooves', 'UtilityPipe', 'Cables', 'RainPipe', 'Moulding', 
                   'Intrusion', 'PowerPlug', 'UtilityBox', 'Balcony', 'Lighting', 'Lock', 'Doorbell', 
                   'Alarm', 'LetterBox', 'Grate', 'Undefined', 'CableBox', 'Canopy', 'Vent', 'PowerBox', 
                   'UtilityHole', 'Recess', 'Protrusion', 'Shutter', 'Handrail', 'Lock', 'Mirror', 
                   'SecuritySpike', 'Bench', 'Intrusion', 'Picture', 'Showcase', 'Camera', 
                   'Undefined', 'Stair', 'Protrusion', 'Alarm', 'Graffiti', 'Lighting', 'Ornaments', 
                   'SecurityBar', 
                   'Grate', 'Vent', 'Lighting', 'UtilityHole', 'Intrusion', 'Undefined', 'Protrusion']

iN = len(lObjectsALLcnts)
arrCnts = np.array(lObjectsALLcnts)

theta=np.arange(0,2*np.pi,2*np.pi/iN)
width = (2*np.pi)/iN *0.9
bottom = 50

fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1, 0.1, 0.75, 0.75], polar=True)
bars = ax.bar(theta, arrCnts, width=width, bottom=bottom)

plt.axis('off')

rotations = np.rad2deg(theta)
for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls):
    lab = ax.text(x,bottom+bar.get_height() , label, 
             ha='left', va='center', rotation=rotation, rotation_mode="anchor",)   
plt.show()

请注意,这使用了给定的50个底部间距单位.您可以稍微增加此数字,以使条形图和文本之间具有更大的间距.

Note that this uses the given 50 units of bottom spacing. You may increase this number a bit to have more spacing between bars and text.


此答案的以下初始版本有些过时.我将其保留在此处以供参考.

您遇到的问题是,文本边界框已展开以容纳完整的旋转文本,但是该框本身仍是在笛卡尔坐标中定义的.下图显示了两个文本,水平对齐为左",垂直对齐为底";问题是旋转后的文本的边界框边缘距离文本更远.

The problem you run into is that the text bounding box is expanded to host the complete rotated text, but that box itself is still defined in cartesian coordinates. The picture below shows two texts with horizontalalignment "left" and vertical alignment "bottom"; the problem is that the rotated text has its bounding box edge much further away from the text.

一个简单的解决方案可能是将水平和垂直对齐方式定义为中心",这样文本的枢轴就保持不变,而与旋转无关.

An easy solution may be to define the horizontal and vertical alignment as "center", such the pivot of the text stays the same independent of its rotation.

然后问题是要很好地估计文本的中心与条的顶部之间的距离.

The problem would then be to get a good estimate for the distance between the center of the text and the bar's top.

一个字母可以取文本中字母数量的一半,然后乘以某个系数.这需要通过反复试验才能找到.

One could take half the number of letters in the text and multiply it with some factor. This would need to be found by trial and error.

bottom = 50
rotations = np.rad2deg(theta)
y0,y1 = ax.get_ylim()

for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls):
     offset = (bottom+bar.get_height())/(y1-y0)
     h =offset + len(label)/2.*0.032
     lab = ax.text(x, h, label, transform=ax.get_xaxis_transform(), 
             ha='center', va='center')
     lab.set_rotation(rotation)

您还可以尝试找出渲染文本的实际大小,并使用此信息找出坐标,

You could also try to find out how large the rendered text really is and use this information to find out the coordinates,

bottom = 50
rotations = np.rad2deg(theta)
y0,y1 = ax.get_ylim()

for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls):
     offset = (bottom+bar.get_height())/(y1-y0)
     lab = ax.text(0, 0, label, transform=None, 
             ha='center', va='center')
     renderer = ax.figure.canvas.get_renderer()
     bbox = lab.get_window_extent(renderer=renderer)
     invb = ax.transData.inverted().transform([[0,0],[bbox.width,0] ])
     lab.set_position((x,offset+(invb[1][0]-invb[0][0])/2.*2.7 ) )
     lab.set_transform(ax.get_xaxis_transform())
     lab.set_rotation(rotation)

完整的复制代码:

import numpy as np
import matplotlib.pyplot as plt

lObjectsALLcnts = [1, 1, 1, 2, 2, 3, 5, 14, 15, 20, 32, 33, 51, 1, 1, 2, 2, 3, 3, 3, 3, 
               3, 4, 6, 7, 7, 10, 10, 14, 14, 14, 17, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 
               5, 5, 6, 14, 14, 27, 27, 1, 1, 2, 3, 4, 4, 5]

lObjectsALLlbls = ['DuctPipe', 'Column', 'Protrusion', 'Tree', 'Pole', 'Bar', 'Undefined', 
               'EarthingConductor', 'Grooves', 'UtilityPipe', 'Cables', 'RainPipe', 'Moulding', 
               'Intrusion', 'PowerPlug', 'UtilityBox', 'Balcony', 'Lighting', 'Lock', 'Doorbell', 
               'Alarm', 'LetterBox', 'Grate', 'Undefined', 'CableBox', 'Canopy', 'Vent', 'PowerBox', 
               'UtilityHole', 'Recess', 'Protrusion', 'Shutter', 'Handrail', 'Lock', 'Mirror', 
               'SecuritySpike', 'Bench', 'Intrusion', 'Picture', 'Showcase', 'Camera', 
               'Undefined', 'Stair', 'Protrusion', 'Alarm', 'Graffiti', 'Lighting', 'Ornaments', 
               'SecurityBar', 
               'Grate', 'Vent', 'Lighting', 'UtilityHole', 'Intrusion', 'Undefined', 'Protrusion']

iN = len(lObjectsALLcnts)
arrCnts = np.array(lObjectsALLcnts)

theta=np.arange(0,2*np.pi,2*np.pi/iN)
width = (2*np.pi)/iN *0.9
bottom = 50

fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1, 0.1, 0.75, 0.75], polar=True)
bars = ax.bar(theta, arrCnts, width=width, bottom=bottom)

plt.axis('off')

rotations = np.rad2deg(theta)
y0,y1 = ax.get_ylim()

for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls):
 offset = (bottom+bar.get_height())/(y1-y0)
 lab = ax.text(0, 0, label, transform=None, 
         ha='center', va='center')
 renderer = ax.figure.canvas.get_renderer()
 bbox = lab.get_window_extent(renderer=renderer)
 invb = ax.transData.inverted().transform([[0,0],[bbox.width,0] ])
 lab.set_position((x,offset+(invb[1][0]-invb[0][0])/2.*2.7 ) )
 lab.set_transform(ax.get_xaxis_transform())
 lab.set_rotation(rotation)

 
plt.show()

不幸的是,再次涉及到一些奇怪的因素.更不幸的是,在这种情况下,我完全不知道为什么它必须存在.但是结果可能仍然足够好.

Unfortunately there is again some strange factor 2.7 involved. Even more unfornate is that in this case I have absolutely no idea why it must be there. But the result may still be good enough to work with.

也可以使用以下问题的解决方案:

One could also use a solution from ths question: Align arbitrarily rotated text annotations relative to the text, not the bounding box

这篇关于在Matplotlib,Python3的极坐标/径向条形图中的条形上方获取标签的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆