Поворот текста на строку в логарифмическом масштабе в Matplotlib - PullRequest
0 голосов
/ 22 октября 2018

Проблема

Я работаю с данными в логарифмическом масштабе и хотел бы повернуть их так, чтобы они соответствовали линии.Я знаю модель, но не совсем уверен, какой угол я должен подключить к transform_angles, чтобы восстановить правильное вращение.После небольшого количества проб и ошибок я знаю, что ответ составляет около 10 градусов для требуемых мне осей.

MWE

import matplotlib.pylab as plt
import numpy as np

plt.clf()
plt.yscale('log')
plt.ylim((1e-11, 1e-1))  # Other data is usually plotted and these are the ranges I need. 
plt.xlim((-0.2, 7.2))
x_fit = np.linspace(0.8, 3.2, 1000)
y_ols = (lambda x: np.exp(np.log(2)*(-20.8 + -1.23 * x)))(x_fit)  # I get these numbers from OLS fitting. 
plt.plot(x_fit, y_ols, 'b-', dashes='', label='__nolegend__')
plt.gca().text(np.min(x_fit), 1.2*y_ols[0], r'$O(2^{{ {:.3}x }})$'.format(-1.23), rotation=-10).set_bbox(dict(facecolor='w', alpha=0.7, edgecolor='k', linewidth=0))  # There are several others lines which have been omitted. 

enter image description here

Подобные вопросы ( держит текст повернутым в системе координат данных после изменения размера? ) использовать только линейные оси, как и демонстрации matplotlib .

Замечания на графике для ответа на комментарии


  • В своем полном графике я использую двойную ось (обе в логарифмическом масштабе) с функцией twinx().Все данные нанесены на ax1, который использует шкалу log-10 (как показано).(Я мог бы быть более явным и написать yscale('log', basey=10) ...).В конечном итоге я хочу ось Base-10.
  • Модель, использованная при создании y_ols, основана на регрессионном сопоставлении с некоторыми исходными данными и требует base-2.В логарифмическом масштабе достаточно легко восстановить градиент в любой необходимой базе.

Использование градиентов

Достаточно легко восстановить градиент в логарифмическом масштабе, используя сочетание np.gradient и угла (в радианах), используя np.arctan, но я не могу восстановить число, близкое к 10 градусам (0,17 радиан).

transData.transform_angles(np.array((np.mean(np.gradient(np.log10(y_ols), np.mean(np.diff(x_fit)))),)), np.array([np.min(x_fit), 1.2*y_ols[0]]).reshape((1, 2)), radians=True)[0]

дает -1.6 радиан (приблизительно -90 градусов), тогда как мне требуется число, близкое к 0.17 радиан.Возможно, мне следует использовать другую базу, или я все делаю неправильно (отсюда и пост).

Дополнения - вертикальное смещение

Как видно из кода, я добавилвертикальное смещение для точки привязки при использовании 1.2*y_ols[0].Если решение должно принять это во внимание, тогда все будет лучше.

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

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


В дальнейшем я, следовательно, приведу только сравнение между линейным масштабом и масштабом журнала, чтобы помочьпонять, что на самом деле нет никакой разницы между использованием журнала или линейной шкалы с подходом из связанного matplotlib "text_rotation_relative_to_line" примера .

Сначала вы рассчитываете угол в координатах данных.Это легко сделать с помощью numpy.arctan2 и разницы первых двух данных (или любой другой пары близлежащих данных) в качестве аргументов.
Затем вы используете ax.transData.transform_angles для преобразования угла, заданного в координатах данных, в угол в экранных координатах.

Ниже приведен пример (взятие данных из другого ответа) для того же случая в линейном и логарифмическом масштабе.

import matplotlib.pyplot as plt
import numpy as np

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 4), sharex=True)

ax2.set_yscale('log')
ax2.set(ylim=(1e-11, 1e-1), xlim=(-0.2, 7.2))

x = np.linspace(0.8, 6.2, 100)
y = (lambda x: 10**(( -2 - x)))(x)  

# angle in data coordinates
angle_data = np.rad2deg(np.arctan2(y[1]-y[0], x[1]-x[0]))

# Apply the exact same code to linear and log axes
for ax in (ax1, ax2):

    ax.plot(x, y, 'b-')

    # angle in screen coordinates
    angle_screen = ax.transData.transform_angles(np.array((angle_data,)), 
                                              np.array([x[0], y[0]]).reshape((1, 2)))[0]

    # using `annotate` allows to specify an offset in units of points
    ax.annotate("Text", xy=(x[0],y[0]), xytext=(2,2), textcoords="offset points", 
                rotation_mode='anchor', rotation=angle_screen)

plt.show()

enter image description here

0 голосов
/ 02 ноября 2018

Для textbox не имеет значения, какую ось вы используете.Вам нужно всего лишь настроить его угол на figure свойства.Чтобы лучше это продемонстрировать, я немного поменяю ваш MWE.Я буду использовать функцию y(x)=10^(2-x), и в логарифмическом масштабе она должна обеспечивать линейную функцию с наклоном -45 градусов.И если вы изучите значения сетки, это так (функция отбрасывается на одно десятилетие для каждой единицы).Но, поскольку соотношение сторон фигуры искажено, угол обзора различен (только один квадрат для двух единиц), и вам необходимо настроить его.Таким образом, для данного показателя правильное значение наклона составляет -26,259 градусов.См. Значение adjusted_slope в коде.

Что касается вертикального смещения textbox, вы можете выбрать значение, обеспечивающее наилучшие визуальные результаты.

# you can set whatever size you need
plt.figure(figsize=(6, 4))

# these are the original settings
plt.yscale('log')
plt.ylim((1e-11, 1e-1))  
plt.xlim((-0.2, 7.2))

x_fit = np.linspace(0.8, 6.2, 100)
slope = -1.0
# a slight change in the function
y_ols = (lambda x: 10**(( -2 + slope * x)))(x_fit)  

plt.plot(x_fit, y_ols, 'b-', dashes='', label='__nolegend__')

# estimate the "right" slope 
calc_slope = np.mean(np.gradient(np.log10(y_ols), np.mean(np.diff(x_fit))))

# get current figure properties
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
x_sz, y_sz = plt.gcf().get_size_inches()
x_factor = x_sz / (x_max - x_min)
y_factor = y_sz / (np.log10(y_max) - np.log10(y_min))  # adjust to logarithmic values 

# calculate adjustment
adjusted_slope = (calc_slope * y_factor / x_factor)  # in radians

plt.gca().text(np.min(x_fit), 1.2*y_ols[0], r'$O(10^{{ {:.3}x }})$'.format(slope),
            rotation_mode='anchor', rotation=np.arctan(adjusted_slope)*180/np.pi).set_bbox(
                dict(facecolor='w', alpha=0.7, edgecolor='k', linewidth=0)) 

enter image description here

...