Matplotlib: обратное аффинное преобразование, чтобы получить равный аспект с различными пределами x и y - PullRequest
0 голосов
/ 16 января 2020

У меня есть 2D координаты геометрии c формы в виде массивов x и y. Используя комбинацию перемещения и поворота, я могу повернуть фигуру вокруг ее геометрии c центра на заданный угол alpha (см. Ниже минимальный пример).

Как показано в приведенном ниже коде, этого можно достичь, сначала сместив центр геометрии c формы в начало координат, затем применив вращение (умножение на матрицу двумерного вращения), а затем переведя вернем его в исходное положение.

В этом примере давайте предположим, что фигура представляет собой прямоугольник:

import numpy as np
from numpy import cos, sin, linspace, concatenate
import matplotlib.pyplot as plt


def rotate(x, y, alpha):
    """
    Rotate the shape by an angle alpha (given in degrees)
    """
    # Get the center of the shape
    x_center = (x.max() + x.min()) / 2.0
    y_center = (y.max() + y.min()) / 2.0

    # Shifting the center of the shape to the origin of coordinates
    x0 = x - x_center
    y0 = y - y_center
    angle_rad = np.deg2rad(alpha)
    rot_mat = np.array([
    [cos(angle_rad), -sin(angle_rad)],
    [sin(angle_rad), cos(angle_rad)]
    ])
    xy = np.vstack((x0, y0))
    xnew, ynew = rot_mat @ xy

    # translate it back to its original location
    xnew += x_center
    ynew += y_center

    return xnew, ynew

z0, z1, z2, z3 = 4 + 0.6*1j, 4 + 0.8*1j, 8 + 0.8*1j, 8 + 0.6*1j
xy = concatenate((
                linspace(z0, z1, 10, endpoint=False),
                linspace(z1, z2, 10, endpoint=False),
                linspace(z2, z3, 10, endpoint=False),
                linspace(z3, z0, 10, endpoint=True)
          ))

x = xy.real
y = xy.imag

xrot, yrot = rotate(x, y, alpha=-45.0)

# The x and y limits
xlow, xup = 0, 10
ylow, yup = -1.5, 3.0


plt.plot(x, y, label='original shape')
plt.plot(xrot, yrot, label='rotated shape')
plt.xlim((xlow, xup))
plt.ylim((ylow, yup))
plt.legend()
plt.show()  

Мы получаем следующий график:

enter image description here

Как вы можете видеть, фигура поворачивается, но также растягивается / наклоняется, потому что аспект не был установлен на equal. мы можем проверить это, установив:

plt.gca().set_aspect('equal')

И это покажет повернутую форму без перекоса:

enter image description here


Проблема в том, что я строю эту фигуру вместе с другими данными, у которых диапазон x намного больше, чем диапазон y. Таким образом, установка равного аспекта не является решением в этом случае.

Чтобы быть более точным, я хочу, чтобы повернутая фигура (оранжевый цвет) на первом рисунке отображалась правильно, как на втором рисунке. Мой подход заключается в том, чтобы найти матрицу обратной асимметрии на первом рисунке (в результате разницы между пределами x и y) и умножить ее на повернутую форму, чтобы получить ожидаемый результат.

К сожалению Используя метод проб и ошибок, я не смог получить правильную матрицу перекоса.

Любая помощь очень ценится.

РЕДАКТИРОВАТЬ С точки зрения линейной алгебры, как express эта деформация повернутой фигуры на первом рисунке с точки зрения преобразований перекоса и масштабирования?

1 Ответ

1 голос
/ 16 января 2020

При выполнении желаемого поворота вершины прямоугольника потеряют свое значение в координатах данных, а начальный прямоугольник станет трапецией. Видимо, это желательно. Таким образом, возникает вопрос, как выполнить поворот в экранных координатах относительно заданной точки center в координатах данных.

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

from matplotlib import pyplot as plt
from matplotlib.transforms import Affine2D 

x, y = (4, 0.6)
dx, dy = (4, 0.2)

fig, ax = plt.subplots()

# The x and y limits
xlow, xup = 0, 10
ylow, yup = -1.5, 3.0
ax.set(xlim=(xlow, xup), ylim=(ylow, yup))

rect1 = plt.Rectangle((x,y), width=dx, height=dy, facecolor="none", edgecolor="C0")
ax.add_patch(rect1)

rect2 = plt.Rectangle((x,y), width=dx, height=dy, facecolor="none", edgecolor="C1")
ax.add_patch(rect2) 

def lim_change(evt=None):
    center = (x+dx/2, y+dy/2)
    trans = ax.transData + Affine2D().rotate_deg_around(*ax.transData.transform_point(center), -45)
    rect2.set_transform(trans)
lim_change()   
cid = ax.callbacks.connect("xlim_changed", lim_change)
cid = ax.callbacks.connect("ylim_changed", lim_change)

plt.show()

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...