Matplotlib裁剪辅助轴标签

时间:2017-10-29 19:44:07

标签: python matplotlib

设置

我正在尝试使用以下“属性”创建Matplotlib图:

  • 特定尺寸(宽x高),图周围没有填充,这意味着紧密的边界框应该完全图形尺寸
  • 两个相关轴,左侧是主要轴,右侧是次要轴
  • 主轴上的数据应显示在辅助轴上的数据上方。一旦两个轴都被定义,我通过将主轴的zorder更改为比次轴更高的数字来实现这一目标
  • 水平和垂直网格都应显示在两个轴上的数据下方。我通过打开辅助轴的网格实现了这一点,辅助轴的大小与主轴的大小完全相同,并且两个轴上的相关刻度标记使得两个轴的网格线对齐

问题

下面的Python代码创建的图形符合我的所有要求,除了次要(右)轴标签被裁剪,我找不到解决方法。有人知道如何修改我的代码或建议一种方法让辅助标签完全出现在图中,同时仍然保持“紧”的边界框?

import numpy as np
import matplotlib.pyplot as plt

def main():
    # Data definition (toy example)
    xticks = [0, 0.5, 1, 1.5, 2, 2.0, 2.5, 3]
    xlim = [0, 3]
    pyticks = [2.8, 3.5, 4.2, 5.0, 5.7, 6.4, 7.2, 7.9, 8.6, 9.4, 10.1, 10.8]
    syticks = [5.6, 6.0, 6.4, 6.9, 7.3, 7.8, 8.2, 8.7, 9.1, 9.6, 10, 10.4]
    prim_indep = np.array([0, 1, 2, 3])
    prim_dep = np.array([3.5, 5.7, 10.1, 8.7])
    sec_indep = np.array([0.5, 1, 1.5])
    sec_dep = np.array([10, 9.0, 6.0])
    # Figure creation
    fig = plt.figure(figsize=(6.4, 4.8))
    axis_prim = plt.subplot(111)
    setup_axis(axis_prim, xticks, pyticks, xlim)
    plt.tight_layout(pad=0, w_pad=0, h_pad=2)
    axis_sec = fig.add_axes(axis_prim.get_position(), frameon=False)
    setup_axis(axis_sec, xticks, syticks, xlim)
    axis_sec.yaxis.set_label_position('right')
    axis_sec.yaxis.set_ticks_position('right')
    axis_sec.tick_params(axis='x', which='both', length=0, labelbottom='off')
    axis_sec.xaxis.grid(True, which='both', zorder=2)
    axis_sec.yaxis.grid(True, which='both', zorder=2)
    axis_prim.set_zorder(axis_sec.get_zorder()+1)
    axis_prim.patch.set_visible(False)
    # Plot data
    axis_prim.plot(prim_indep, prim_dep, color='k', linewidth=3)
    plot_marker(axis_prim, prim_indep, prim_dep, 'k')
    axis_sec.plot(sec_indep, sec_dep, color='b', linestyle='--', linewidth=3)
    plot_marker(axis_sec, sec_indep, sec_dep, 'b')
    #
    fig.savefig('test.png', pad_inches=0)
    plt.close('all')

def setup_axis(axis, xticks, yticks, xlim):
    """Specify axis features"""
    axis.tick_params(axis='x', which='major', labelsize=14, zorder=4)
    axis.tick_params(axis='y', which='major', labelsize=14, zorder=4)
    axis.xaxis.set_ticks(xticks)
    axis.yaxis.set_ticks(yticks)
    axis.set_xlim(xlim)
    axis.set_axisbelow(True)

def plot_marker(axis, indep, dep, color):
    """Plot marker with "standard" sizing"""
    axis.plot(
        indep, dep,
        color=color, linestyle='', linewidth=0, marker='o',
        markeredgecolor=color, markersize=9,
        markeredgewidth=3, markerfacecolor='w',
    )

if __name__ == '__main__':
    main()

上面的代码生成了下图:

Image file produces by code immediately above

[编辑]我的回答

我找到的解决方案是使用 tight_layout 函数的 rect 参数。图中绘制了两次:

  • 在第一次绘图过程中没有 rect 值传递给 tight_layout ,此绘图过程的目的是计算辅助从属轴右边缘之间的距离刻度标签和最后一个独立轴刻度标签的右边缘。后一个坐标是图的结合框结束(裁剪辅助轴标签)
  • 第二个绘图过程是通过 rect 参数实际调整 tight_layout 边界框右边缘在第一个绘图过程中获得的数量(缩放到数据单位) 。该图通过
  • 后保存

可能有办法避免两张抽签。产生具有我想要的要求的图形的代码如下所示,“正确”的图像低于该图像。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg

def get_tightbbox_adjustment(fig, axis_prim, axis_sec):
    """
    Calculates distance between rightmsot edge of secondary dependent axis
    tick labels and rightmost edge of rightmost tick label of independent axis
    """
    right_edge = get_edge(fig, axis_sec.yaxis)
    xaxis_edge = get_edge(fig, axis_prim.xaxis)
    return right_edge-xaxis_edge

def bbox(fig, obj):
    """Returns bounding box of an object"""
    renderer = fig.canvas.get_renderer()
    return obj.get_window_extent(renderer=renderer).transformed(
        fig.dpi_scale_trans.inverted()
    )

def get_edge(fig, axis):
    """Find rightmost edge of an axis tick labels"""
    tlabels = [label for label in axis.get_ticklabels()]
    return max([bbox(fig, label).xmax for label in tlabels])

def draw(fsize, save=False, fbbox=None):
    # Data definition (toy example)
    xticks = [0, 0.5, 1, 1.5, 2, 2.0, 2.5, 3]
    xlim = [0, 3]
    pyticks = [2.8, 3.5, 4.2, 5.0, 5.7, 6.4, 7.2, 7.9, 8.6, 9.4, 10.1, 10.8]
    syticks = [5.6, 6.0, 6.4, 6.9, 7.3, 7.8, 8.2, 8.7, 9.1, 9.6, 10, 10.4]
    prim_indep = np.array([0, 1, 2, 3])
    prim_dep = np.array([3.5, 5.7, 10.1, 8.7])
    sec_indep = np.array([0.5, 1, 1.5])
    sec_dep = np.array([10, 9.0, 6.0])
    # Figure creation
    fig = plt.figure(figsize=fsize)
    #
    axis_prim = plt.subplot(1, 1, 1)
    setup_axis(axis_prim, xticks, pyticks, xlim)
    plt.tight_layout(rect=fbbox, pad=0, h_pad=2)
    #
    axis_sec = plt.axes(axis_prim.get_position(), frameon=False)
    setup_axis(axis_sec, xticks, syticks, xlim)
    axis_sec.yaxis.set_label_position('right')
    axis_sec.yaxis.set_ticks_position('right')
    axis_sec.tick_params(axis='x', which='both', length=0, labelbottom='off')
    axis_sec.xaxis.grid(True, which='both', zorder=2)
    axis_sec.yaxis.grid(True, which='both', zorder=2)
    #
    axis_prim.set_zorder(axis_sec.get_zorder()+1)
    axis_prim.patch.set_visible(False)
    # Plot data
    axis_prim.plot(prim_indep, prim_dep, color='k', linewidth=3)
    plot_marker(axis_prim, prim_indep, prim_dep, 'k')
    axis_sec.plot(sec_indep, sec_dep, color='b', linestyle='--', linewidth=3)
    plot_marker(axis_sec, sec_indep, sec_dep, 'b')
    #
    if save:
        fig.savefig('test.png', box='tight', pad_inches=0)
    else:
        FigureCanvasAgg(fig).draw()
    plt.close('all')
    return fig, axis_prim, axis_sec


def main():
    fsize = (6.4, 4.8)
    fig, axis_prim, axis_sec = draw(fsize)
    fbbox = axis_prim.get_position()
    delta = get_tightbbox_adjustment(fig, axis_prim, axis_sec)/fsize[0]
    fbbox = [0, 0, 1-delta, 1]
    draw(fsize, save=True, fbbox=fbbox)

def plot_marker(axis, indep, dep, color):
    """Plot marker with "standard" sizing"""
    axis.plot(
        indep, dep,
        color=color, linestyle='', linewidth=0, marker='o',
        markeredgecolor=color, markersize=9,
        markeredgewidth=3, markerfacecolor='w',
    )

def setup_axis(axis, xticks, yticks, xlim):
    """Specify axis features"""
    axis.tick_params(axis='x', which='major', labelsize=14, zorder=4)
    axis.tick_params(axis='y', which='major', labelsize=14, zorder=4)
    axis.xaxis.set_ticks(xticks)
    axis.yaxis.set_ticks(yticks)
    axis.set_xlim(xlim)
    axis.set_axisbelow(True)

if __name__ == '__main__':
    main()

Image produced by code immediately above

0 个答案:

没有答案