带有“浮动”径向轴的极坐标图
我正在制作一个图形,这个图形里有很多极坐标图,它们都在一个网格里,并且它们的径向轴使用的是相同的刻度。每个图都需要比较小,这样才能放进这个图形里,但当我把轴的尺寸缩小时,径向轴的刻度标签就显得拥挤,看不清楚,甚至遮住了我想要展示的数据。
比如说:
import numpy as np
from matplotlib import pyplot as plt
fig, axes = plt.subplots(1, 4, figsize=(9, 2), subplot_kw=dict(polar=True))
theta = np.r_[np.linspace(0, 2*np.pi, 12), 0]
for aa in axes.flat:
x = np.random.rand(12)
aa.plot(theta, np.r_[x, x[0]], '-sb')
aa.set_rlim(0, 1)
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.5)
我意识到,可以通过减小字体大小和减少径向刻度的数量来部分解决这个问题,但我更希望刻度标签不要和我的数据重叠。相反,我想要一个单独的“浮动”径向轴,放在图的外面,像这样:
在普通的笛卡尔坐标图中,我只需要用 ax.spine['left'].set_position(...)
就可以了,但在 PolarAxesSubplot
中只有一个 u'polar'
的脊柱,无法偏移。有没有什么“好”的方法可以为极坐标图创建一个浮动的径向轴,最好是它的刻度和范围能够随着极坐标图的径向轴的变化而更新?
3 个回答
也许我们可以在上面叠加另一个图:
fig, axes = plt.subplots(1, 4, figsize=(9, 2), subplot_kw=dict(polar=True))
for aa in axes.flat:
aa.plot(theta, r, '-sb')
aa.set_rlim(0, 1)
aa.set_yticklabels([])
box=axes[0].get_position()
axl=fig.add_axes([box.xmin/2, #put it half way between the edge of the 1st subplot and the left edge of the figure
0.5*(box.ymin+box.ymax), #put the origin at the same height of the origin of the polar plots
box.width/40, #Doesn't really matter, we will set everything invisible, except the y axis
box.height*0.4], #fig.subplots_adjust will not adjust this axis, so we will need to manually set the height to 0.4 (half of 0.9-0.1)
axisbg=None) #transparent background.
axl.spines['top'].set_visible(False)
axl.spines['right'].set_visible(False)
axl.spines['bottom'].set_visible(False)
axl.yaxis.set_ticks_position('both')
axl.xaxis.set_ticks_position('none')
axl.set_xticklabels([])
axl.set_ylim(0,1)
axl.set_ylabel('$R$\t', rotation=0)
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.5)
编辑
结果发现,subplots_adjust
也会影响叠加的坐标轴。如果我们查看 fig
里面的坐标轴列表,叠加的坐标轴就在那儿(如果你有疑问,可以查看 site-packages\matplotlib\figure.py):
In [27]:
fig.axes
Out[27]:
[<matplotlib.axes.PolarAxesSubplot at 0x9714650>,
<matplotlib.axes.PolarAxesSubplot at 0x9152730>,
<matplotlib.axes.PolarAxesSubplot at 0x9195b90>,
<matplotlib.axes.PolarAxesSubplot at 0x91878b0>,
<matplotlib.axes.Axes at 0x9705a90>]
真正的问题是,wspace=0.5
不仅影响极坐标图的宽度,还会影响高度(这样比例保持不变)。但是对于非极坐标的叠加坐标轴,它只影响宽度。因此,需要额外调整宽度,解决方案是:
fig, axes = plt.subplots(1, 4, figsize=(10, 2), subplot_kw=dict(polar=True))
for aa in axes.flat:
aa.plot(theta, r, '-sb')
aa.set_rlim(0, 1)
aa.set_yticklabels([])
#fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.5)
box=axes[0].get_position()
axl=fig.add_axes([box.xmin/2,
0.5*(box.ymin+box.ymax),
box.width/40,
box.height*0.5],
axisbg=None)
#fig.add_axes([box.xmin, box.ymin, box.width, box.height])
axl.spines['top'].set_visible(False)
axl.spines['right'].set_visible(False)
axl.spines['bottom'].set_visible(False)
axl.yaxis.set_ticks_position('both')
axl.xaxis.set_ticks_position('none')
axl.set_xticklabels([])
axl.set_ylim(0,1)
axl.set_ylabel('$R$\t', rotation=0)
w_pre_scl=box.width
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.5)
ratio=axes[0].get_position().width/w_pre_scl
axlb=axl.get_position()
axl.set_position([axlb.xmin, axlb.ymin, axlb.width, axlb.height*ratio])
如果没有 wspace=0.5
,最后几行代码就没有实际效果:
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9)
#ratio=axes[0].get_position().width/w_pre_scl
#axlb=axl.get_position()
#axl.set_position([axlb.xmin, axlb.ymin, axlb.width, axlb.height*ratio])
这可能不是你想要的结果,但它可以给你一些关于如何在极坐标轴上准确放置标签的提示:
import numpy as np
from matplotlib import pyplot as plt
fig, axes = plt.subplots(1, 4, figsize=(9, 2), subplot_kw=dict(polar=True))
theta = np.r_[np.linspace(0, 2*np.pi, 12), 0]
for aa in axes.flat:
x = np.random.rand(12)
aa.plot(theta, np.r_[x, x[0]], '-sb')
aa.set_rlim(0, 1)
plt.draw()
ax = axes[-1]
for r, t in zip(ax.yaxis.get_ticklocs(), ax.yaxis.get_ticklabels()):
ax.text(np.pi/2, r, '$\cdot$'*20 + t.get_text(), ha='left', va='center',
fontsize=10, color='0.25')
for ax in axes:
ax.yaxis.set_ticklabels([])
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.5)
fig.savefig('test.png', bbox_inches='tight')
在Saullo的回答基础上,这里有一个看起来更好一点的小技巧,主要是先在数据坐标中画出刻度线,然后在x轴上进行一个固定的平移:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import transforms
theta = np.linspace(0, 2 * np.pi, 13, endpoint=True)
fig, axes = plt.subplots(1, 4, figsize=(5, 2), subplot_kw=dict(polar=True))
for aa in axes.flat:
aa.hold(True)
r = np.random.rand(12)
r = np.r_[r, r[0]]
aa.plot(theta, r, '-sb')
aa.set_rlim(0, 1)
aa.set_yticklabels([])
factor = 1.1
d = axes[0].get_yticks()[-1] * factor
r_tick_labels = [0] + axes[0].get_yticks()
r_ticks = (np.array(r_tick_labels) ** 2 + d ** 2) ** 0.5
theta_ticks = np.arcsin(d / r_ticks) + np.pi / 2
r_axlabel = (np.mean(r_tick_labels) ** 2 + d ** 2) ** 0.5
theta_axlabel = np.arcsin(d / r_axlabel) + np.pi / 2
# fixed offsets in x
offset_spine = transforms.ScaledTranslation(-100, 0, axes[0].transScale)
offset_ticklabels = transforms.ScaledTranslation(-10, 0, axes[0].transScale)
offset_axlabel = transforms.ScaledTranslation(-40, 0, axes[0].transScale)
# apply these to the data coordinates of the line/ticks
trans_spine = axes[0].transData + offset_spine
trans_ticklabels = trans_spine + offset_ticklabels
trans_axlabel = trans_spine + offset_axlabel
# plot the 'spine'
axes[0].plot(theta_ticks, r_ticks, '-_k', transform=trans_spine,
clip_on=False)
# plot the 'tick labels'
for ii in xrange(len(r_ticks)):
axes[0].text(theta_ticks[ii], r_ticks[ii], "%.1f" % r_tick_labels[ii],
ha="right", va="center", clip_on=False,
transform=trans_ticklabels)
# plot the 'axis label'
axes[0].text(theta_axlabel, r_axlabel, '$r$', fontsize='xx-large',
ha='right', va='center', clip_on=False, transform=trans_axlabel)
fig.savefig('test.png', bbox_inches='tight')
这样做的好处是,当图形大小改变时,刻度线的y位置仍然能正确地相对于极坐标图的径向轴保持不变。不过(在@SaulloCastro的更新之前),由于x轴的偏移量是用点数来指定的,并且是固定的,所以当图形大小变化时,浮动的轴就可能无法正确定位,可能会和极坐标图重叠在一起: