绘图中的注释自动放置

kab*_*nus 4 python text matplotlib plot-annotations

我有代码可以轻松地自动处理着色和绘制多个图(对我来说)。我想让注释变得更容易:

目标:如果注释 xy 与前一个注释冲突,请向上移动 - 直到与其他注释不发生冲突。

  1. 如果有一个功能已经能够做到这一点那将是一个梦想,但我找不到。

  2. 否则 - 列出注释并在坐标系中获取其边界框的最佳方法是什么?

我有一个自动着色代码,如下所示:

if chain:
    children = []
    for child in Iplot.axes.get_children():
        if (type(child) is not matplotlib.collections.PathCollection and
            type(child) is not matplotlib.lines.Line2D):
            continue
        children.append(child)
    col_step = 1.0/(len(children)+len(args))
    for child in children:
        child.set_color([Iplot.col,0,1-Iplot.col])
        Iplot.col += col_step
Run Code Online (Sandbox Code Playgroud)

我可以对注释做类似的事情(更改 if 语句和第二个循环的主体),但是 1)我不喜欢这段代码 2)我希望存在更优雅的东西。

kab*_*nus 5

这是我的解决方案,也是我试图避免的解决方案。我在问题评论中链接的旧问题中看到有人提到这是一个 np 完全问题,但我想指出这并不重要。我测试了多达 26 个注释,需要几秒钟,但不会再多了。任何实际情节都不会有 1000 个注释。

注意事项:

  1. 如前所述,这并不是超快。具体来说,我希望我可以避免绘制()。现在可以了,只画两次。
  2. 此代码允许仅以特定的正交(左/右/上/下)方向添加任何新注释,但这可以扩展。
  3. 箭头的位置取决于窗口。这意味着确保窗口大小或轴在注释(使用箭头)后不会改变。如果调整大小,请重新注释。
  4. 不是动态的,参见第 3 点。

背景:

  1. Iplot是一个辅助类,我必须处理多图图形、处理着色、调整大小和现在注释。
  2. pltmatplotlib.pyplot
  3. 此方法处理多个注释(或单个注释),现在可以解决冲突。
  4. 正如你可能已经猜到的那样,Iplot.axes拿着我的轴图。

编辑 我删除了我的类代码以使其更易于复制粘贴。应该为函数提供轴,并且 kwargs 接受现有的 box 关键字以考虑到之前的注释,这些注释是就地编辑的。注意我使用一个类来封装它。该函数还必须返回要使用的框,以防这是第一次调用。

编辑2

一段时间后,速度加快了 - 不需要绘制那么多,最好循环两次,然后在中间更新渲染器。

代码:

def annotate(axes,boxes,labels,data,**kwargs):
    #slide should be relevant edge of bbox - e.g. (0,0) for left, (0,1) for bottom ...
    try: slide = kwargs.pop("slide")
    except KeyError: slide = None
    try: 
        xytexts = kwargs.pop("xytexts")
        xytext  = xytexts
    except KeyError: 
        xytext = (0,2)
        xytexts = None
    try: boxes = kwargs.pop("boxes")
    except KeyError: boxes = list()
    pixel_diff = 1
                                                                                  newlabs = []              
    for i in range(len(labels)):
        try: 
            len(xytexts[i])
            xytext = xytexts[i]
        except TypeError: pass

        a = axes.annotate(labels[i],xy=data[i],textcoords='offset pixels',
                                    xytext=xytext,**kwargs)
        newlabs.append(a)
    plt.draw()
    for i in range(len(labels)):
        cbox = a.get_window_extent()
        if slide is not None:
            direct  = int((slide[0] - 0.5)*2)
            current = -direct*float("inf")
            arrow = False
            while True:
                overlaps = False
                count = 0
                for box in boxes:
                    if cbox.overlaps(box):
                        if direct*box.get_points()[slide] > direct*current:
                            overlaps = True
                            current =  box.get_points()[slide] 
                            shift   = direct*(current - cbox.get_points()[1-slide[0],slide[1]])
                if not overlaps: break
                arrow = True
                position = array(a.get_position())
                position[slide[1]] += shift * direct * pixel_diff
                a.set_position(position)
                plt.draw()
                cbox = a.get_window_extent()
                x,y =  axes.transData.inverted().transform(cbox)[0]
            if arrow:
                axes.arrow(x,y,data[i][0]-x,data[i][1]-y,head_length=0,head_width=0)
        boxes.append(cbox)
    plt.draw()
    return boxes
Run Code Online (Sandbox Code Playgroud)

任何改进建议都将受到热烈欢迎。非常感谢!