Отображать максимальную поверхность в matplotlib?

Я рисую несколько поверхностей на одной фигуре с помощью matplotlib, и мне хотелось бы увидеть только самую верхнюю поверхность, как показывает Matlab.

Matlab 3D view: Matlab 3D view

Вид сверху Matlab: Matlab top view

Matplotlib 3D-просмотр: Matplotlib 3D view

Matplotlib вид сверху: Matplotlib top view

Как я могу заставить Matplotlib показать результат, похожий на Matlab, в котором любой верхний класс отображается вверху, а не один класс, имеющий приоритет над другим?

Ответ 1

Мне было интересно думать о некоторых грязных хаках, таких как упоминания mgab в их ответе, но затем решил просто пойти значительно проще:

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

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
from scipy.special import erf

fig = plt.figure()
ax = fig.gca(projection='3d')

X = np.arange(0, 6, 0.25)
Y = np.arange(0, 6, 0.25)
X, Y = np.meshgrid(X, Y)

Z1 = np.zeros_like(X)
Z2 = np.ones_like(X)

for i in range(len(X)):
  for j in range(len(X[0])):
    Z1[i,j] = 0.5*(erf((X[i,j]+Y[i,j]-4.5)*0.5)+1)
    Z2[i,j] = 0.5*(erf((-X[i,j]-Y[i,j]+4.5)*0.5)+1)


alpha = 0.25

surf1 = ax.plot_surface(X, Y, Z1, cstride=2, rstride=1, cmap=cm.Oranges, linewidth=0, antialiased=False, alpha=alpha)

surf2 = ax.plot_surface(X, Y, Z2, cstride=2, rstride=1, cmap=cm.Blues, linewidth=0, antialiased=False, alpha=alpha)

ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

fig.colorbar(surf1, shrink=0.5, aspect=5)
fig.colorbar(surf2, shrink=0.5, aspect=5)

plt.show()

enter image description here

enter image description here

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

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

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from scipy.special import erf

fig = plt.figure()
ax = fig.gca(projection='3d')

X = np.arange(0, 6, 0.25)
Y = np.arange(0, 6, 0.25)
X, Y = np.meshgrid(X, Y)

Z1 = np.empty_like(X)
Z2 = np.empty_like(X)
C1 = np.empty_like(X, dtype=object)
C2 = np.empty_like(X, dtype=object)

for i in range(len(X)):
  for j in range(len(X[0])):
    z1 = 0.5*(erf((X[i,j]+Y[i,j]-4.5)*0.5)+1)
    z2 = 0.5*(erf((-X[i,j]-Y[i,j]+4.5)*0.5)+1)
    Z1[i,j] = z1
    Z2[i,j] = z2

    # If you want to grab a colour from a matplotlib cmap function, 
    # you need to give it a number between 0 and 1. z1 and z2 are 
    # already in this range, so it just works.
    C1[i,j] = plt.get_cmap("Oranges")(z1)
    C2[i,j] = plt.get_cmap("Blues")(z2)


# Create a transparent bridge region
X_bridge = np.vstack([X[-1,:],X[-1,:]])
Y_bridge = np.vstack([Y[-1,:],Y[-1,:]])
Z_bridge = np.vstack([Z1[-1,:],Z2[-1,:]])
color_bridge = np.empty_like(Z_bridge, dtype=object)

color_bridge.fill((1,1,1,0)) # RGBA colour, onlt the last component matters.

# Join the two surfaces flipping one of them (using also the bridge)
X_full = np.vstack([X, X_bridge, np.flipud(X)])
Y_full = np.vstack([Y, Y_bridge, np.flipud(Y)])
Z_full = np.vstack([Z1, Z_bridge, np.flipud(Z2)])
color_full = np.vstack([C1, color_bridge, np.flipud(C2)])

surf_full = ax.plot_surface(X_full, Y_full, Z_full, rstride=1, cstride=1,
                            facecolors=color_full, linewidth=0,
                            antialiased=False)


plt.show()

enter image description here

enter image description here

Ответ 2

Ответ

Как указано в комментариях к вопросу, matplotlib не делает действительно 3D-графика, и его приближение может дать вам ограниченные результаты. Вопросы, с которыми вы сталкиваетесь, на самом деле подтверждены в mplot3d FAQ > .

Они также направляют вас на MayaVi, если вы хотите сделать серьезную трехмерную печать. Если вам действительно не нужна 3D-графика и только забота о верхнем представлении, я бы сделал 2D-график напрямую, как предложил Bensciens в комментариях...

Грязные обходные пути

Конечно, если вы готовы заплатить душами программистов, почти всегда есть решение, связанное с темной магией...: P

Вариант 1

Если вам действительно нужны только два вида, которые вы приводите в качестве примера, а поверхности - нечто похожее на них, вы можете сначала нарисовать часть, лежащую за поверхностью A, затем всю поверхность B, а затем часть, которая лежит сверху поверхности A... Позвольте мне объяснить:

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

from mpl_toolkits.mplot4d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)

R = (X+Y)
Z1 = R/R.max()
Z2 = -R/R.max()

surfA_bottom = ax.plot_surface(X, Y, np.where(Z1<=Z2,Z1, np.nan),
                               rstride=1, cstride=1, color='r', linewidth=0)

surfB = ax.plot_surface(X, Y, Z2,
                        rstride=1, cstride=1, color='b', linewidth=0)

surfA_top = ax.plot_surface(X, Y, np.where(Z1>=Z2,Z1, np.nan),
                            rstride=1, cstride=1, color='r', linewidth=0)

ax.set_zlim3d(-1, 1)
ax.set_ylim(-5,5)
ax.set_xlim(-5,5)

plt.show()

3d plot with masked array 1

3d plot with masked array 2

Вариант 2

(У него есть некоторые объяснения, пропустите последний фрагмент кода, если вы хотите только решение!)

Это решение немного сложнее, но более устойчиво и для более сложных поверхностей... Дело в том, что 3D-графики в matplotlib не справляются с глубиной для разных объектов... правильно? но он делает для одного объекта... Что касается построения обеих поверхностей как одиночной поверхности, тогда?

Для этого вам нужно объединить все точки в одну поверхность (у вас может быть несколько значений Z для повторных комбинаций X-Y). Чтобы дифференцировать две части нашей новой поверхности (наши прежние две поверхности), мы можем использовать facecolors kwarg. (Я добавил некоторое значение alpha, чтобы более четко видеть, что происходит)

from mpl_toolkits.mplot4d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)

Z1 = np.sin(np.sqrt(X**2+Y**2))
Z2 = np.ones_like(Z1)*0.6

C1 = np.empty_like(X, dtype=str)
C1.fill('b')
C2 = C1.copy()
C2.fill('r')

X3 = np.vstack([X,X])
Y3 = np.vstack([Y,Y])
Z3 = np.vstack([Z1,Z2])
C3 = np.vstack([C1,C2])


surf3 = ax.plot_surface(X3, Y3, Z3, rstride=1, cstride=1,
                       facecolors=C3, linewidth=0,
                       antialiased=False, alpha=0.5)

ax.set_zlim3d(-1, 2)
plt.show()

3d plot merging to one array 1

Как вы можете видеть, результаты довольно хорошие, но есть какой-то странный эффект, так как одна крайность одной поверхности связана с другой крайностью другой поверхности. Как избавиться от него? Прозрачные пленки не являются вариантом, поскольку, насколько мне известно, plot_surface() позволяет влиять только на значение alpha на всю поверхность. Я также пытался маскировать переходы с помощью строки значений NaN в X, Y и Z аналогично в обходном пути 1, но затем рендеринг разбивается. Вы можете попробовать, может быть, это зависит от моей установки.

EDIT: Я нашел менее элегантное и более проблематичное решение, но, как указывает @, вы можете установить прозрачность только в области моста, указав цвета с помощью rgba synthax. Я оставлю свою версию для истории обзора, так как ответ уже достаточно длинный...: P

(вы можете получить более мягкие ребра, увеличивающие количество точек)

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)

# Complex shape from examples in matplotlib gallery
Z1 = np.sin(np.sqrt(X**2+Y**2))
Z2 = np.ones_like(Z1)*0.6

# Define the color for each one of our surfaces
# (it doesn't need to be a gradient)
color1 = np.empty_like(X, dtype=str)
color1.fill('b')
color2 = np.empty_like(X, dtype=str)
color2.fill('r')

# Create a white bridge region
X_bridge = np.vstack([X[-1,:],X[0,:]])
Y_bridge = np.vstack([Y[-1,:],Y[0,:]])
Z_bridge = np.vstack([Z1[-1,:],Z2[0,:]])
color_bridge = np.empty_like(Z_bridge, dtype=object)
color_bridge.fill((1,1,1,0))

# Join the two surfaces (using also the bridge)
X_full = np.vstack([X, X_bridge, X])
Y_full = np.vstack([Y, Y_bridge, Y])
Z_full = np.vstack([Z1, Z_bridge, Z2])
color_full = np.vstack([color1, color_bridge, color2])

surf_full = ax.plot_surface(X_full, Y_full, Z_full, rstride=1, cstride=1,
                                    facecolors=color_full, linewidth=0,
                                                                antialiased=False)

ax.set_zlim3d(-1, 2)
ax.set_ylim(-5,5)
ax.set_xlim(-5,5)

plt.show()

enter image description here

enter image description here