Подтвердить что ты не робот

Как повернуть аннотацию matplotlib в соответствии с линией?

Есть сюжет с несколькими диагональными линиями с разными склонами. Я хотел бы аннотировать эти строки текстовой меткой, которая соответствует наклону строк.

Что-то вроде этого:

Annotated line

Есть ли надежный способ сделать это?

Я пробовал параметры вращения text и annotate, но они находятся в координатах экрана, а не координатах данных (т.е. всегда x градусов на экране, независимо от диапазонов xy). Мои диапазоны x и y различаются на порядки, и, очевидно, на кажущийся уклон влияет размер видового экрана среди других переменных, поэтому поворот с фиксированной степенью не делает этого трюка. Любые другие идеи?

4b9b3361

Ответ 1

Я придумал что-то, что работает для меня. Обратите внимание на серые пунктирные линии:

annotated lines

Вращение должно быть установлено вручную, но это должно быть сделано ПОСЛЕ draw() или макета. Поэтому мое решение состоит в том, чтобы связать строки с аннотациями, затем прокрутить их и сделать следующее:

  • получить преобразование данных линии (т.е. перейти от координат данных к отображению координат)
  • преобразует две точки вдоль линии для отображения координат
  • найти наклон отображаемой строки
  • установите поворот текста в соответствии с этим наклоном

Это не идеально, потому что обработка matplotlib повернутого текста неверна. Он выравнивается ограничивающей рамкой, а не базовой базой текста.

Некоторые основы шрифтов, если вас интересует рендеринг текста: http://docs.oracle.com/javase/tutorial/2d/text/fontconcepts.html

В этом примере показано, что делает matplotlib: http://matplotlib.org/examples/pylab_examples/text_rotation.html

Единственный способ, которым я нашел ярлык рядом с линией, - это выровнять по центру как по вертикали, так и по горизонтали. Затем я смещаю метку на 10 пунктов влево, чтобы она не перекрывалась. Достаточно хорошо для моего приложения.

Вот мой код. Я рисую линию, но я хочу, затем нарисую аннотацию, а затем привяжу ее к вспомогательной функции:

line, = fig.plot(xdata, ydata, '--', color=color)

# x,y appear on the midpoint of the line

t = fig.annotate("text", xy=(x, y), xytext=(-10, 0), textcoords='offset points', horizontalalignment='left', verticalalignment='bottom', color=color)
text_slope_match_line(t, x, y, line)

Затем вызовите другую вспомогательную функцию после макета, но до savefig (для интерактивных изображений, я думаю, вам придется регистрироваться на события рисования и вызывать update_text_slopes в обработчике)

plt.tight_layout()
update_text_slopes()

Помощники:

rotated_labels = []
def text_slope_match_line(text, x, y, line):
    global rotated_labels

    # find the slope
    xdata, ydata = line.get_data()

    x1 = xdata[0]
    x2 = xdata[-1]
    y1 = ydata[0]
    y2 = ydata[-1]

    rotated_labels.append({"text":text, "line":line, "p1":numpy.array((x1, y1)), "p2":numpy.array((x2, y2))})

def update_text_slopes():
    global rotated_labels

    for label in rotated_labels:
        # slope_degrees is in data coordinates, the text() and annotate() functions need it in screen coordinates
        text, line = label["text"], label["line"]
        p1, p2 = label["p1"], label["p2"]

        # get the line data transform
        ax = line.get_axes()

        sp1 = ax.transData.transform_point(p1)
        sp2 = ax.transData.transform_point(p2)

        rise = (sp2[1] - sp1[1])
        run = (sp2[0] - sp1[0])

        slope_degrees = math.degrees(math.atan(rise/run))

        text.set_rotation(slope_degrees)

Ответ 2

Это тот же самый процесс и базовый код, который дал @Adam - он просто перестроен, чтобы быть (надеюсь) немного более удобным.

def label_line(line, label, x, y, color='0.5', size=12):
    """Add a label to a line, at the proper angle.

    Arguments
    ---------
    line : matplotlib.lines.Line2D object,
    label : str
    x : float
        x-position to place center of text (in data coordinated
    y : float
        y-position to place center of text (in data coordinates)
    color : str
    size : float
    """
    xdata, ydata = line.get_data()
    x1 = xdata[0]
    x2 = xdata[-1]
    y1 = ydata[0]
    y2 = ydata[-1]

    ax = line.get_axes()
    text = ax.annotate(label, xy=(x, y), xytext=(-10, 0),
                       textcoords='offset points',
                       size=size, color=color,
                       horizontalalignment='left',
                       verticalalignment='bottom')

    sp1 = ax.transData.transform_point((x1, y1))
    sp2 = ax.transData.transform_point((x2, y2))

    rise = (sp2[1] - sp1[1])
    run = (sp2[0] - sp1[0])

    slope_degrees = np.degrees(np.arctan2(rise, run))
    text.set_rotation(slope_degrees)
    return text

Используется как:

import numpy as np
import matplotlib.pyplot as plt

...
fig, axes = plt.subplots()
color = 'blue'
line, = axes.plot(xdata, ydata, '--', color=color)
...
label_line(line, "Some Label", x, y, color=color)

Изменить: обратите внимание, что этот метод еще нужно вызвать после завершения макета чертежа, иначе все будет изменено.

Смотрите: https://gist.github.com/lzkelley/0de9e8bf2a4fe96d2018f1b1bd5a0d3c