Matplotlib: обратное аффинное преобразование для получения равного аспекта с разными пределами x и y

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

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

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

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()  

Получаем следующий сюжет:

введите здесь описание изображения

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

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

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

введите здесь описание изображения


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

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

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

Любая помощь приветствуется.

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


person s.ouchene    schedule 16.01.2020    source источник
comment
Ваш многоугольник определяется его 4 вершинами (имеется в виду их соответствующие координаты). Использование одинакового представления для обеих осей показывает, что ваш многоугольник является прямоугольником. Использование неравного представления изменит внутренние углы в многоугольнике, и поэтому он выглядит так, как будто это уже не прямоугольник, но так оно и есть. Если вы хотите остаться в этом неравном представлении и по-прежнему отображать прямоугольник, вам придется изменить координаты вершин, что полностью изменит ваш объект, а это означает, что он не будет отображаться обратно в прямоугольник в равном представлении. Вы можете использовать формат журнала-журнала для своих осей.   -  person Patol75    schedule 16.01.2020
comment
@Patol75: Да, я хочу изменить вершины, умножив повернутую форму на обратную матрицу перекоса (или любую другую матрицу деформации)   -  person s.ouchene    schedule 16.01.2020
comment
Хорошо, но прежде чем делать это, не могли бы вы попробовать представление логарифмического журнала?   -  person Patol75    schedule 16.01.2020
comment
@Patol75: Помните, я строю эту фигуру с другими данными, для которых не требуется логарифмическая шкала.   -  person s.ouchene    schedule 16.01.2020
comment
Немного сбивает с толку ваш вопрос: как только вы повернете прямоугольник в координатах экрана, он больше не будет прямоугольником (если вы не используете равные пропорции). Но ваш код предполагает, что вершины имеют значение в координатах данных. Это означает, что желаемый результат — это то, что потеряло смысл. Это действительно то, чего вы хотите?   -  person ImportanceOfBeingErnest    schedule 16.01.2020
comment
@ImportanceOfBeingErnest: Другими словами, на какую матрицу мы должны умножить повернутую форму, чтобы получить ее, как ожидалось (повернутую, но не деформированную) без изменения xlim и ylim.   -  person s.ouchene    schedule 16.01.2020
comment
Это математический вопрос или вопрос о том, как получить желаемый визуал?   -  person ImportanceOfBeingErnest    schedule 16.01.2020
comment
@ImportanceOfBeingErnest: я хочу получить желаемое изображение, не заботясь о математике (я изменю пост, если это необходимо).   -  person s.ouchene    schedule 16.01.2020
comment
На какую матрицу мы должны умножить... Основная проблема здесь в том, что матрица поворота не будет похожа на обычную 2D-матрица вращения, которая имеет 4 значения, зависящие только от полярного угла (theta в ссылке). Эта матрица будет иметь 4 значения, которые варьируются в зависимости от полярного угла и радиального расстояния до каждой точки, но в компоненте x больше, чем в компоненте y. Создать такую ​​матрицу на лету можно, но это будет.... сложно   -  person William Miller    schedule 16.01.2020


Ответы (1)


При выполнении нужного поворота вершины прямоугольника потеряют свое значение в данных координатах, а исходный прямоугольник станет трапецией. Видимо это желательно. Таким образом, возникает вопрос, как выполнить поворот в экранных координатах относительно заданной точки 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()

введите здесь описание изображения

person ImportanceOfBeingErnest    schedule 16.01.2020
comment
Спасибо. Но у меня есть данные в виде массивов x и y, а не в виде прямоугольных объектов, как я могу их использовать? - person s.ouchene; 16.01.2020
comment
Забудьте о своих данных ... в любом случае вывод неверен в координатах данных, как уже указывалось в комментариях. - person ImportanceOfBeingErnest; 16.01.2020
comment
Боюсь, вы меня неправильно поняли. В моем посте это был лишь минимальный пример. На самом деле мои формы - это аэродинамические поверхности (с координатами x и y). Как я могу применить ваш ответ, чтобы построить его. как я могу использовать массивы Numpy вместо matplotlib.rectangle? - person s.ouchene; 16.01.2020
comment
Если вы повернете свой аэродинамический профиль и покажете его в неравных осях, это будет неправильно. Я не буду способствовать дальнейшему получению неправильных результатов. - person ImportanceOfBeingErnest; 16.01.2020
comment
На мой взгляд, решение состоит в том, чтобы использовать другую ось с равным аспектом, просто чтобы нарисовать на ней повернутую форму, используя что-то вроде: ax2 = ax.twinx(); ax2.set_aspect('equal'); ax2.plot(xrot, yrot), сохраняя при этом исходную форму на первой оси. - person s.ouchene; 16.01.2020