【问题标题】:matplotlib axis arrow tipmatplotlib 轴箭头提示
【发布时间】:2016-02-17 16:34:21
【问题描述】:

我正在尝试在 matplotlib 中的轴末端设置一个箭头。 我不想移除刺并用纯箭头替换它们,因为我需要它们的功能......

我的实现is as slight modification of joferkington implementation

import matplotlib.pyplot as plt
import numpy as np

def arrowed_spines(ax=None, arrowLength=30, labels=('X', 'Y'), arrowStyle='<|-'):
    xlabel, ylabel = labels

    for i, spine in enumerate(['left', 'bottom']):
        # Set up the annotation parameters
        t = ax.spines[spine].get_transform()
        xy, xycoords = [1, 0], ('axes fraction', t)
        xytext, textcoords = [arrowLength, 0], ('offset points', t)

        # create arrowprops
        arrowprops = dict( arrowstyle=arrowStyle,
                           facecolor=ax.spines[spine].get_facecolor(), 
                           linewidth=ax.spines[spine].get_linewidth(),
                           alpha = ax.spines[spine].get_alpha(),
                           zorder=ax.spines[spine].get_zorder(),
                           linestyle = ax.spines[spine].get_linestyle() )

        if spine is 'bottom':
            ha, va = 'left', 'center'
            xarrow = ax.annotate(xlabel, xy, xycoords=xycoords, xytext=xytext, 
                        textcoords=textcoords, ha=ha, va='center',
                        arrowprops=arrowprops)
        else:
            ha, va = 'center', 'bottom'
            yarrow = ax.annotate(ylabel, xy[::-1], xycoords=xycoords[::-1], 
                        xytext=xytext[::-1], textcoords=textcoords[::-1], 
                        ha='center', va=va, arrowprops=arrowprops)
    return xarrow, yarrow

# plot
x = np.arange(-2., 10.0, 0.01)
plt.plot(x, x**2)
plt.gcf().set_facecolor('white')    
ax = plt.gca()
ax.set_xticks([]) 
ax.set_yticks([]) 

ax.spines['left'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('center')
ax.spines['top'].set_color('none')

arrowed_spines(ax)

plt.show()

绘图结果显示箭头移位如下

似乎一两点的偏移在起始位置和箭头与脊椎的对齐方面是一致的。我不知道如何解决这个问题。任何帮助将不胜感激。

谢谢

【问题讨论】:

  • 应该包括对源要点的引用:gist.github.com/joferkington/3845684
  • 我推荐ax.spines['left'].set_position('zero') 而不是ax.spines['left'].set_position('center'),这样轴会在原点相交。
  • 这是一个快速破解解决方法:ax.spines['bottom'].set_position(('data', 0.001))

标签: python python-2.7 matplotlib


【解决方案1】:

我在matplotlib documentation 中找到了最直接的解决方案。下面是一个例子:

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
# Move the left and bottom spines to x = 0 and y = 0, respectively.
ax.spines["left"].set_position(("data", 0))
ax.spines["bottom"].set_position(("data", 0))
# Hide the top and right spines.
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)

# Draw arrows (as black triangles: ">k"/"^k") at the end of the axes.  In each
# case, one of the coordinates (0) is a data coordinate (i.e., y = 0 or x = 0,
# respectively) and the other one (1) is an axes coordinate (i.e., at the very
# right/top of the axes).  Also, disable clipping (clip_on=False) as the marker
# actually spills out of the axes.
ax.plot(1, 0, ">k", transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, "^k", transform=ax.get_xaxis_transform(), clip_on=False)

# Some sample data.
x = np.linspace(-0.5, 1., 100)
ax.plot(x, np.sin(x*np.pi))

plt.show()

【讨论】:

    【解决方案2】:

    有一个示例展示了如何使用mpl_toolkits.axisartist 工具包将箭头作为轴装饰器in the matplotlib documentation

    from mpl_toolkits.axisartist.axislines import SubplotZero
    import matplotlib.pyplot as plt
    import numpy as np
    
    
    fig = plt.figure()
    ax = SubplotZero(fig, 111)
    fig.add_subplot(ax)
    
    for direction in ["xzero", "yzero"]:
        # adds arrows at the ends of each axis
        ax.axis[direction].set_axisline_style("-|>")
    
        # adds X and Y-axis from the origin
        ax.axis[direction].set_visible(True)
    
    for direction in ["left", "right", "bottom", "top"]:
        # hides borders
        ax.axis[direction].set_visible(False)
    
    x = np.linspace(-0.5, 1., 100)
    ax.plot(x, np.sin(x*np.pi))
    
    plt.show()
    


    在许多情况下,不希望使用mpl_toolkits.axisartist.axislines 模块。在这种情况下,您还可以通过在刺的顶部使用三角形作为标记来轻松获得箭头:

    import numpy as np
    import matplotlib.pyplot as plt
    
    x = np.linspace(-np.pi, np.pi, 100)
    y = 2 * np.sin(x)
    
    rc = {"xtick.direction" : "inout", "ytick.direction" : "inout",
          "xtick.major.size" : 5, "ytick.major.size" : 5,}
    with plt.rc_context(rc):
        fig, ax = plt.subplots()
        ax.plot(x, y)
    
        ax.spines['left'].set_position('zero')
        ax.spines['right'].set_visible(False)
        ax.spines['bottom'].set_position('zero')
        ax.spines['top'].set_visible(False)
        ax.xaxis.set_ticks_position('bottom')
        ax.yaxis.set_ticks_position('left')
    
        # make arrows
        ax.plot((1), (0), ls="", marker=">", ms=10, color="k",
                transform=ax.get_yaxis_transform(), clip_on=False)
        ax.plot((0), (1), ls="", marker="^", ms=10, color="k",
                transform=ax.get_xaxis_transform(), clip_on=False)
    
        plt.show()
    

    【讨论】:

    • 除非你想以错误的方式做事并因重新调整你的情节而受苦,否则这就是要走的路
    【解决方案3】:

    在这里,我结合了 Julien 和 s3b4s 的现有答案,并使函数更通用,以便您可以指定要修改的轴和箭头的方向。

    from matplotlib import pyplot as plt
    import numpy as np
    
    def arrowed_spines(
            ax,
            x_width_fraction=0.05,
            x_height_fraction=0.05,
            lw=None,
            ohg=0.3,
            locations=('bottom right', 'left up'),
            **arrow_kwargs
    ):
        """
        Add arrows to the requested spines
        Code originally sourced here: https://3diagramsperpage.wordpress.com/2014/05/25/arrowheads-for-axis-in-matplotlib/
        And interpreted here by @Julien Spronck: https://stackoverflow.com/a/33738359/1474448
        Then corrected and adapted by me for more general applications.
        :param ax: The axis being modified
        :param x_{height,width}_fraction: The fraction of the **x** axis range used for the arrow height and width
        :param lw: Linewidth. If not supplied, default behaviour is to use the value on the current left spine.
        :param ohg: Overhang fraction for the arrow.
        :param locations: Iterable of strings, each of which has the format "<spine> <direction>". These must be orthogonal
        (e.g. "left left" will result in an error). Can specify as many valid strings as required.
        :param arrow_kwargs: Passed to ax.arrow()
        :return: Dictionary of FancyArrow objects, keyed by the location strings.
        """
        # set/override some default plotting parameters if required
        arrow_kwargs.setdefault('overhang', ohg)
        arrow_kwargs.setdefault('clip_on', False)
        arrow_kwargs.update({'length_includes_head': True})
    
        # axis line width
        if lw is None:
            # FIXME: does this still work if the left spine has been deleted?
            lw = ax.spines['left'].get_linewidth()
    
        annots = {}
    
        xmin, xmax = ax.get_xlim()
        ymin, ymax = ax.get_ylim()
    
        # get width and height of axes object to compute
        # matching arrowhead length and width
        fig = ax.get_figure()
        dps = fig.dpi_scale_trans.inverted()
        bbox = ax.get_window_extent().transformed(dps)
        width, height = bbox.width, bbox.height
    
        # manual arrowhead width and length
        hw = x_width_fraction * (ymax-ymin)
        hl = x_height_fraction * (xmax-xmin)
    
        # compute matching arrowhead length and width
        yhw = hw/(ymax-ymin)*(xmax-xmin)* height/width
        yhl = hl/(xmax-xmin)*(ymax-ymin)* width/height
    
        # draw x and y axis
        for loc_str in locations:
            side, direction = loc_str.split(' ')
            assert side in {'top', 'bottom', 'left', 'right'}, "Unsupported side"
            assert direction in {'up', 'down', 'left', 'right'}, "Unsupported direction"
    
            if side in {'bottom', 'top'}:
                if direction in {'up', 'down'}:
                    raise ValueError("Only left/right arrows supported on the bottom and top")
    
                dy = 0
                head_width = hw
                head_length = hl
    
                y = ymin if side == 'bottom' else ymax
    
                if direction == 'right':
                    x = xmin
                    dx = xmax - xmin
                else:
                    x = xmax
                    dx = xmin - xmax
    
            else:
                if direction in {'left', 'right'}:
                    raise ValueError("Only up/downarrows supported on the left and right")
                dx = 0
                head_width = yhw
                head_length = yhl
    
                x = xmin if side == 'left' else xmax
    
                if direction == 'up':
                    y = ymin
                    dy = ymax - ymin
                else:
                    y = ymax
                    dy = ymin - ymax
    
    
            annots[loc_str] = ax.arrow(x, y, dx, dy, fc='k', ec='k', lw = lw,
                     head_width=head_width, head_length=head_length, **arrow_kwargs)
    
        return annots
    
    
    fig = plt.figure()
    ax = fig.add_subplot(111)
    x = np.arange(-2., 10.0, 0.01)
    ax.plot(x, x**2)
    fig.set_facecolor('white')
    
    annots = arrowed_spines(ax, locations=('bottom right', 'bottom left', 'left up', 'right down'))
    
    plt.show()
    

    结果

    突出问题:我试图匹配现有脊椎的线宽,但由于某种原因,箭头似乎有更粗的线。对此进行实验表明,0.8 的脊线线宽与大约 0.3 的箭头线宽相匹配。不知道为什么会这样 - 目前您必须将 lw=&lt;value&gt; 设置为手动修复。

    【讨论】:

      【解决方案4】:

      为了得到你想要的,从arrowed_spines函数中删除以下部分后,Julien的回答就足够了:

      # removing the default axis on all sides:
      for side in ['bottom','right','top','left']:
          ax.spines[side].set_visible(False)
      
      # removing the axis ticks
      plt.xticks([]) # labels 
      plt.yticks([])
      ax.xaxis.set_ticks_position('none') # tick markers
      ax.yaxis.set_ticks_position('none')
      

      在包含箭头后仍然可以修改脊椎,如您在此处看到的:

      【讨论】:

      • 您可能希望保持顶部和右侧的脊椎不存在。
      【解决方案5】:

      您可以删除所有的刺并扩大箭头以覆盖数据范围(找到此代码here):

      import matplotlib.pyplot as plt
      import numpy as np
      
      def arrowed_spines(fig, ax):
      
          xmin, xmax = ax.get_xlim() 
          ymin, ymax = ax.get_ylim()
      
          # removing the default axis on all sides:
          for side in ['bottom','right','top','left']:
              ax.spines[side].set_visible(False)
      
          # removing the axis ticks
          plt.xticks([]) # labels 
          plt.yticks([])
          ax.xaxis.set_ticks_position('none') # tick markers
          ax.yaxis.set_ticks_position('none')
      
          # get width and height of axes object to compute 
          # matching arrowhead length and width
          dps = fig.dpi_scale_trans.inverted()
          bbox = ax.get_window_extent().transformed(dps)
          width, height = bbox.width, bbox.height
      
          # manual arrowhead width and length
          hw = 1./20.*(ymax-ymin) 
          hl = 1./20.*(xmax-xmin)
          lw = 1. # axis line width
          ohg = 0.3 # arrow overhang
      
          # compute matching arrowhead length and width
          yhw = hw/(ymax-ymin)*(xmax-xmin)* height/width 
          yhl = hl/(xmax-xmin)*(ymax-ymin)* width/height
      
          # draw x and y axis
          ax.arrow(xmin, 0, xmax-xmin, 0., fc='k', ec='k', lw = lw, 
                   head_width=hw, head_length=hl, overhang = ohg, 
                   length_includes_head= True, clip_on = False) 
      
          ax.arrow(0, ymin, 0., ymax-ymin, fc='k', ec='k', lw = lw, 
                   head_width=yhw, head_length=yhl, overhang = ohg, 
                   length_includes_head= True, clip_on = False)
      
      
      # plot
      x = np.arange(-2., 10.0, 0.01)
      plt.plot(x, x**2)
      fig = plt.gcf()
      fig.set_facecolor('white') 
      ax = plt.gca()
      
      arrowed_spines(fig, ax)
      
      plt.show()
      

      【讨论】:

      • 我不想移除脊椎,因为我仍然需要网格、刻度和脊椎的所有其他功能。我的代码是我需要做的事情的简化示例
      • 好的。然后,我建议您在问题中告诉我们您实际需要做什么。
      • 我需要能够在脊椎上添加箭头,同时保留脊椎及其所有功能
      猜你喜欢
      • 2022-01-02
      • 1970-01-01
      • 1970-01-01
      • 2021-04-24
      • 1970-01-01
      • 2017-03-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多