Какое правильное преобразование matplotlib для «виртуальной третьей оси» в моем графике водопада? - PullRequest
2 голосов
/ 29 апреля 2019

Работая над улучшением ответа на этот вопрос , я зашел в тупик.

Чего я хочу добиться , так это создать «искусственный» 3D-водопад в matplotlib, где отдельные линейные графики (или, возможно, любой другой тип графика) смещены в пиксельных координатах фигуры и нанесены позади друг с другом. Эта часть уже работает нормально, и, используя мой пример кода (см. Ниже), вы сможете построить десять эквивалентных линий, которые смещены на fig.dpi/10. в направлениях x и y и нанесены друг за другом через zorder.

Обратите внимание, что я также добавил fill_between(), чтобы сделать "глубинный сигнал" zorder более заметным.

enter image description here

Где я застрял - это то, что я хотел бы добавить «третью ось», то есть линию (позже, возможно, отформатированную с некоторыми галочками), которая правильно выравнивается с основанием (то есть [0 , 0] в единицах данных) каждой строки.

Эта проблема, возможно, еще более осложняется тем фактом, что это не одноразовая вещь (т. Е. Решения не должны работать только в статических пиксельных координатах), а должны корректно вести себя при масштабировании, особенно при интерактивной работе . Как видите, настройка, например, xlim позволяет изменять масштаб строк «как ожидалось» (лучше всего, если вы попробуете это интерактивно), но красная линия (будущая ось), которую я пытался вставить, не транспонируется так же, как основы каждого линейный участок.

То, что я не ищу - это решения, основанные на mpl_toolkits.mplot3d Axes3D, так как это приведет к множеству других проблем , касающихся zorder и zoom, это именно то, чего я пытаюсь избежать, придумав свой собственный «фальшивый 3D-сюжет».

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D,IdentityTransform

def offset(myFig,myAx,n=1,xOff=60,yOff=60):
    """
        this function will apply a shift of  n*dx, n*dy
        where e.g. n=2, xOff=10 would yield a 20px offset in x-direction
    """
    ## scale by fig.dpi to have offset in pixels!
    dx, dy = xOff/myFig.dpi , yOff/myFig.dpi 
    t_data = myAx.transData 
    t_off = mpl.transforms.ScaledTranslation( n*dx, n*dy, myFig.dpi_scale_trans)
    return t_data + t_off

fig,axes=plt.subplots(nrows=1, ncols=3,figsize=(10,5))

ys=np.arange(0,5,0.5)
print(len(ys))

## just to have the lines colored in some uniform way
cmap = mpl.cm.get_cmap('viridis')
norm=mpl.colors.Normalize(vmin=ys.min(),vmax=ys.max())

## this defines the offset in pixels
xOff=10 
yOff=10

for ax in axes:
    ## plot the lines
    for yi,yv in enumerate(ys):
        zo=(len(ys)-yi)
        ax.plot([0,0.5,1],[0,1,0],color=cmap(norm(yv)),
                zorder=zo, ## to order them "behind" each other
        ## here we apply the offset to each plot:
                transform=offset(fig,ax,n=yi,xOff=xOff,yOff=yOff)
        )

        ### optional: add a fill_between to make layering more obvious
        ax.fill_between([0,0.5,1],[0,1,0],0,
                facecolor=cmap(norm(yv)),edgecolor="None",alpha=0.1,
                zorder=zo-1, ## to order them "behind" each other
        ## here we apply the offset to each plot:
                transform=offset(fig,ax,n=yi,xOff=xOff,yOff=yOff)
        )

    ##################################
    ####### this is the important bit:
    ax.plot([0,2],[0,2],color='r',zorder=100,clip_on=False,
        transform=ax.transData+mpl.transforms.ScaledTranslation(0.,0., fig.dpi_scale_trans)
    )

## make sure to set them "manually", as autoscaling will fail due to transformations
for ax in axes:
    ax.set_ylim(0,2)

axes[0].set_xlim(0,1)
axes[1].set_xlim(0,2)
axes[2].set_xlim(0,3)

### Note: the default fig.dpi is 100, hence an offset of of xOff=10px will become 30px when saving at 300dpi!
# plt.savefig("./test.png",dpi=300)

plt.show()

Обновление:

Теперь я включил анимацию ниже, которая показывает, как сложенные линии ведут себя при масштабировании / панорамировании, и как их «базовая линия» (синие кружки) перемещается вместе с графиком вместо статического решения OriginLineTrans (зеленая линия). ) или моя преобразованная линия (красная, пунктирная).

Точки присоединения наблюдают различные преобразования и могут быть вставлены с помощью:

ax.scatter([0],[0],edgecolors="b",zorder=200,facecolors="None",s=10**2,)
ax.scatter([0],[0],edgecolors="b",zorder=200,facecolors="None",s=10**2,transform=offset(fig,ax,n=len(ys)-1,xOff=xOff,yOff=yOff),label="attachment points")

Plot behaviour on zoom and rescale

1 Ответ

1 голос
/ 30 апреля 2019

Вопрос сводится к следующему:

Как создать линию, которая

  • начинается с начала координат (0,0) в координатах осей и
  • развивается под углом angle в физических координатах (в пиксельном пространстве)

с помощью преобразования matpotlib?

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

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

Это может выглядеть следующим образом

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtrans


class OriginLineTrans(mtrans.Transform):
    input_dims = 2
    output_dims = 2
    def __init__(self, origin, angle, axes):
        self.axes = axes
        self.origin = origin
        self.angle = angle # in radiants
        super().__init__()

    def get_affine(self):
        origin = ax.transAxes.transform_point(self.origin)
        trans = ax.transAxes + \
                mtrans.Affine2D().translate(*(-origin)) \
                .skew(self.angle, 0).translate(*origin)
        return trans.get_affine()



fig, ax = plt.subplots()
ax.plot([0,0], [0,1], transform=OriginLineTrans((0,0), np.arctan(1), ax))

plt.show()

Обратите внимание, что вв случае исходного вопроса угол будет np.arctan(dx/dy).

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