【问题标题】:Align matplotlib scatter marker left and or right左右对齐matplotlib散点标记
【发布时间】:2014-12-28 11:38:10
【问题描述】:

我正在使用matplotlib scatterplot 函数在垂直线上创建手柄外观,以描绘图形的某些部分。但是,为了使它们看起来正确,我需要能够将散点图标记对齐到左侧(对于左线/轮廓线)和/或右侧(对于右侧线/轮廓线)。

这是一个例子:

#create the figure
fig = plt.figure(facecolor = '#f3f3f3', figsize = (11.5, 6))
ax = plt. ax = plt.subplot2grid((1, 1), (0,0))

#make some random data
index = pandas.DatetimeIndex(start = '01/01/2000', freq  = 'b', periods = 100)
rand_levels = pandas.DataFrame( numpy.random.randn(100, 4)/252., index = index, columns = ['a', 'b', 'c', 'd'])
rand_levels = 100*numpy.exp(rand_levels.cumsum(axis = 0))
ax.stackplot(rand_levels.index, rand_levels.transpose())

#create the place holder for the vertical lines
d1, d2 = index[25], index[50]

#draw the lines
ymin, ymax = ax.get_ylim()
ax.vlines([index[25], index[50]], ymin = ymin, ymax = ymax, color = '#353535', lw = 2)

#draw the markers
ax.scatter(d1, ymax, clip_on = False, color = '#353535', marker = '>', s = 200, zorder = 3)
ax.scatter(d2, ymax, clip_on = False, color = '#353535', marker = '<', s = 200, zorder = 3)

#reset the limits
ax.set_ylim(ymin, ymax)
ax.set_xlim(rand_levels.index[0], rand_levels.index[-1])
plt.show()

上面的代码几乎为我提供了我正在寻找的图表,如下所示:

但是,我希望最左边的标记 (">") 是“左对齐”(即稍微向右移动),以便线条继续到标记的后面同样,我想要最右边的标记(“

关于如何以灵活的方式完成此任务的任何指导或建议?

注意:实际上,我的DataFrame 索引是pandas.Datetime,而不是我为这个简单示例提供的整数。

【问题讨论】:

    标签: python matplotlib scatter-plot


    【解决方案1】:

    我喜欢这个问题,但对我的第一个答案不满意。特别是,创建图形特定对象 (mark_align_*) 以对齐标记似乎不必要地麻烦。我最终发现的是通过 verts 指定标记的功能(2 元素浮点数列表或 Nx2 数组,它指定相对于目标绘图点的标记顶点(0, 0) )。为了实现这个目的,我编写了这个函数,

    from matplotlib import markers
    from matplotlib.path import Path
    
    def align_marker(marker, halign='center', valign='middle',):
        """
        create markers with specified alignment.
    
        Parameters
        ----------
    
        marker : a valid marker specification.
          See mpl.markers
    
        halign : string, float {'left', 'center', 'right'}
          Specifies the horizontal alignment of the marker. *float* values
          specify the alignment in units of the markersize/2 (0 is 'center',
          -1 is 'right', 1 is 'left').
    
        valign : string, float {'top', 'middle', 'bottom'}
          Specifies the vertical alignment of the marker. *float* values
          specify the alignment in units of the markersize/2 (0 is 'middle',
          -1 is 'top', 1 is 'bottom').
    
        Returns
        -------
    
        marker_array : numpy.ndarray
          A Nx2 array that specifies the marker path relative to the
          plot target point at (0, 0).
    
        Notes
        -----
        The mark_array can be passed directly to ax.plot and ax.scatter, e.g.::
    
            ax.plot(1, 1, marker=align_marker('>', 'left'))
    
        """
    
        if isinstance(halign, (str, unicode)):
            halign = {'right': -1.,
                      'middle': 0.,
                      'center': 0.,
                      'left': 1.,
                      }[halign]
    
        if isinstance(valign, (str, unicode)):
            valign = {'top': -1.,
                      'middle': 0.,
                      'center': 0.,
                      'bottom': 1.,
                      }[valign]
    
        # Define the base marker
        bm = markers.MarkerStyle(marker)
    
        # Get the marker path and apply the marker transform to get the
        # actual marker vertices (they should all be in a unit-square
        # centered at (0, 0))
        m_arr = bm.get_path().transformed(bm.get_transform()).vertices
    
        # Shift the marker vertices for the specified alignment.
        m_arr[:, 0] += halign / 2
        m_arr[:, 1] += valign / 2
    
        return Path(m_arr, bm.get_path().codes)
    

    使用此功能,可以将所需的标记绘制为,

    ax.plot(d1, 1, marker=align_marker('>', halign='left'), ms=20,
            clip_on=False, color='k', transform=ax.get_xaxis_transform())
    ax.plot(d2, 1, marker=align_marker('<', halign='right'), ms=20,
            clip_on=False, color='k', transform=ax.get_xaxis_transform())
    

    或使用ax.scatter

    ax.scatter(d1, 1, 200, marker=align_marker('>', halign='left'),
               clip_on=False, color='k', transform=ax.get_xaxis_transform())
    ax.scatter(d2, 1, 200, marker=align_marker('<', halign='right'),
               clip_on=False, color='k', transform=ax.get_xaxis_transform())
    

    在这两个示例中,我都指定了transform=ax.get_xaxis_transform(),因此标记的垂直位置位于坐标轴坐标中(1 是坐标轴的顶部),这与标记 对齐无关.

    与我之前的解决方案相比,此解决方案的明显优势在于它不需要了解 markersize绘图功能ax.plot vs. ax.scatter ) 或 axes (用于变换)。相反,只需指定一个标记及其对齐方式!

    干杯!

    【讨论】:

    • 在寻找替代解决方案的过程中,我尝试为markerax.plotax.scatter 输入选项提供自定义的matplotlib.markers.MarkerStyle 实例,但此功能尚未出现得到支持。
    • 哇@farenorth...哇。首先,看起来通过使用markers.MarkerStyle 类,您无需将figure 传递给函数即可解决问题(这对我来说是一大优势)。我需要实现你所做的以确保我正确使用/理解它,但这是一个非常优雅的抽象/解决方案!
    • 只需将一些测试器功能放在一起,就可以很好地工作而无需通过数字(以及更清晰和更具表现力的语言)。很棒的解决方案!
    • 非常感谢您为这个分析器付出了这么多努力!这实际上应该作为标记的参数包含在 matplotlib 中。
    • @Oren,好问题。这似乎是创建标记方式的错误。我创建了一个issueproposed a solution。现在,我已经用解决方法更新了我的函数。
    【解决方案2】:

    我找到了解决这个问题的简单方法。 Matplotlib 具有不同对齐方式的内置标记: lines_bars_and_markers example code: marker_reference.py

    只需将'&gt;' 标记更改为9,将'&lt;' 标记更改为8

    #draw the markers
    ax.scatter(d1, ymax, clip_on=False, color='#353535', marker=9, s=200, zorder=3)
    ax.scatter(d2, ymax, clip_on=False, color='#353535', marker=8, s=200, zorder=3)
    

    【讨论】:

      【解决方案3】:

      一种解决方案是使用mpl.transforms,并将transform 输入参数用于ax.scatterax.plot。具体来说,我将首先添加,

      from matplotlib import transforms as tf
      

      在这种方法中,我使用tf.offset_copy 创建偏移一半大小的标记。但是标记的大小是多少?事实证明,ax.scatterax.plot 指定的标记大小不同。请参阅this question 了解更多信息。

      1. ax.scatters= 输入参数以点^2 指定标记大小(即,这是标记适合的正方形区域)。

      2. ax.plotmarkersize 输入参数以磅为单位指定标记的宽度和高度(即标记适合的正方形的宽度和高度)。

      使用ax.scatter

      所以,如果你想用ax.scatter 绘制你的标记,你可以这样做,

      ms_scatter = 200  # define markersize
      mark_align_left_scatter = tf.offset_copy(ax.get_xaxis_transform(), fig,
                                               ms_scatter ** 0.5 / 2,
                                               units='points')
      mark_align_right_scatter = tf.offset_copy(ax.get_xaxis_transform(), fig,
                                                -ms_scatter ** 0.5 / 2,
                                                units='points')
      

      这里我使用了ax.get_xaxis_transform,这是一种将点放置在沿 x 轴的数据坐标中,但在 axes(0 到 1)坐标中的 y 轴上的变换.这样,我可以使用1 将点放在图的顶部,而不是使用ymax。此外,如果我平移或缩放图形,标记仍将位于顶部!一旦我定义了新的转换,当我调用ax.scatter 时,我将它们分配给transform 属性,

      ax.scatter(d1, 1, s=ms_scatter, marker='>', transform=mark_align_left_scatter,
                 clip_on=False, color='k')
      ax.scatter(d2, 1, s=ms_scatter, marker='<', transform=mark_align_right_scatter,
                 clip_on=False, color='k')
      

      使用ax.plot

      因为它比较简单,我可能会使用ax.plot。在那种情况下,我会这样做,

      ms = 20
      
      mark_align_left = tf.offset_copy(ax.get_xaxis_transform(), fig,
                                       ms / 2, units='points')
      mark_align_right = tf.offset_copy(ax.get_xaxis_transform(), fig,
                                        -ms / 2, units='points')
      
      ax.plot(d1, 1, marker='>', ms=ms, transform=mark_align_left,
              clip_on=False, color='k')
      ax.plot(d2, 1, marker='<', ms=ms, transform=mark_align_right,
              clip_on=False, color='k')
      

      最后的评论

      您可能希望创建一个包装器以更轻松地创建 mark_align_* 转换,但如果您愿意,我会留给您实施。

      无论您使用ax.scatter 还是ax.plot,您的输出图看起来都像,

      【讨论】:

      • 您可能还想查看ax.axvline 代替ax.vlinesax.axvline 不需要 ymin 和 ymax 值。
      • 这是一个非常彻底的答案,谢谢。
      【解决方案4】:

      不是最优雅的解决方案,但如果我正确理解您的问题,分别从d1d2 减去和添加一个应该可以做到:

      ax.scatter(d1-1, ymax, clip_on = False, color = '#353535', marker = '>', s = 200, zorder = 3)
      ax.scatter(d2+1, ymax, clip_on = False, color = '#353535', marker = '<', s = 200, zorder = 3)
      

      【讨论】:

      • 看看我的注释,我声明在我的实际问题中,DataFrame 索引是日期,这需要是一个灵活的解决方案,而不是仅适用于示例的解决方案。不过,非常感谢您的想法。
      • 哦,我错过了那部分。您能否提供DataFramepandas.Datetime 的示例代码?
      • 刚刚添加了代码来制作Datetime类型的Index而不是int
      • 看起来如果您将 freq = 'b' 更改为 freq = 'h'freq = 'd' 发布的解决方案有效
      • 这只是我生成的示例数据。它将有真实的日期(最像“工作日”),所以不幸的是,我不能将'b' 更改为'h'
      猜你喜欢
      • 1970-01-01
      • 2012-10-13
      • 1970-01-01
      • 1970-01-01
      • 2013-10-17
      • 1970-01-01
      • 2021-10-10
      • 2012-01-15
      • 2017-12-13
      相关资源
      最近更新 更多