0

I'm working on a 3D plot displayed by a wireframe, where 2D plots are projected on the x, y, and z surface, respectively. Below you can find a minimum example.

I have 2 questions:

  1. With contourf, the 2D plots for every x=10, x=20,... or y=10, y=20,... are displayed on the plot walls. Is there a possibility to define for which x or y, respectively, the contour plots are displayed? For example, in case I only want to have the xz contour plot for y = 0.5 mirrored on the wall?

ADDITION: To display what I mean with "2D plots", I changed "contourf" in the code to "contour" and added the resulting plot to this question. Here you can see now the xz lines for different y values, all offset to y=90. What if I do not want to have all the lines, but only two of them for defined y values?

3D_plot_with_2D_contours

  1. As you can see in the minimum example, the 2D contour plot optically covers the wireframe 3D plot. With increasing the transparency with alpha=0.5 I can increase the transparency of the 2D contours to at least see the wireframe, but it is still optically wrong. Is it possible to sort the objects correctly?
import matplotlib.pyplot as plt,numpy as np
import pylab as pl

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np

plt.clf()

fig = plt.figure(1,figsize=(35,17),dpi=600,facecolor='w',edgecolor='k')
fig.set_size_inches(10.5,8)
ax  = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)

Xnew = X + 50
Ynew = Y + 50

cset = ax.contourf(Xnew, Ynew, Z, zdir='z', offset=-100, cmap=plt.cm.coolwarm, alpha=0.5)
cset = ax.contourf(Xnew, Ynew, Z, zdir='x', offset=10, cmap=plt.cm.coolwarm, alpha=0.5) 
cset = ax.contourf(Xnew, Ynew, Z, zdir='y', offset=90, cmap=plt.cm.coolwarm, alpha = 0.5) 

ax.plot_wireframe(Xnew, Ynew, Z, rstride=5, cstride=5, color='black')

Z=Z-Z.min()
Z=Z/Z.max()

from scipy.ndimage.interpolation import zoom

Xall=zoom(Xnew,5)
Yall=zoom(Ynew,5)
Z=zoom(Z,5)

ax.set_xlim(10, 90)
ax.set_ylim(10, 90)
ax.set_zlim(-100, 100)

ax.tick_params(axis='z', which='major', pad=10)

ax.set_xlabel('X',labelpad=10)
ax.set_ylabel('Y',labelpad=10)
ax.set_zlabel('Z',labelpad=17)


ax.view_init(elev=35., azim=-70)

fig.tight_layout()

plt.show()

ADDITION 2: Here is the actual code I'm working with. However, the original data are hidden in the csv files which are too big to be included in the minimal example. That's why was initially replacing them by the test data. However, maybe the actual code helps nevertheless.

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
import pylab as pl
from matplotlib.markers import MarkerStyle

import csv
with open("X.csv", 'r') as f:
  X = list(csv.reader(f, delimiter=";"))
import numpy as np
X = np.array(X[1:], dtype=np.float)

import csv
with open("Z.csv", 'r') as f:
  Z = list(csv.reader(f, delimiter=";"))
import numpy as np
Z = np.array(Z[1:], dtype=np.float)

Y = [[7,7.1,7.2,7.3,7.4,7.5,7.6,7.7,7.8,7.9,8,8.1,8.2,8.3,8.4,8.5,8.6,8.7,8.8,8.9,9]]

Xall = np.repeat(X[:],21,axis=1)
Yall = np.repeat(Y[:],30,axis=0)

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np


plt.clf()

fig = plt.figure(1,figsize=(35,17),dpi=600,facecolor='w',edgecolor='k')
fig.set_size_inches(10.5,8) 
ax  = fig.gca(projection='3d')

cset = ax.contourf(Xall, Yall, Z, 2, zdir='x', offset=0,  cmap=plt.cm.coolwarm, shade = False, edgecolor='none', alpha=0.5)
cset = ax.contourf(Xall, Yall, Z, 2, zdir='y', offset=9, cmap=plt.cm.coolwarm, shade = False, edgecolor='none', alpha=0.5)

ax.plot_wireframe(Xall, Yall, Z, rstride=1, cstride=1, color='black')

Z=Z-Z.min()
Z=Z/Z.max()

from scipy.ndimage.interpolation import zoom

Xall=zoom(Xall,5)
Yall=zoom(Yall,5)
Z=zoom(Z,5)

cset = ax.plot_surface(Xall, Yall, np.zeros_like(Z)-0,facecolors=plt.cm.coolwarm(Z),shade=False,alpha=0.5,linewidth=False)

ax.set_xlim(-0.5, 31)
ax.set_ylim(6.9, 9.1)
ax.set_zlim(0, 500)

labelsx = [item.get_text() for item in ax.get_xticklabels()]
empty_string_labelsx = ['']*len(labelsx)
ax.set_xticklabels(empty_string_labelsx)

labelsy = [item.get_text() for item in ax.get_yticklabels()]
empty_string_labelsy = ['']*len(labelsy)
ax.set_yticklabels(empty_string_labelsy)

labelsz = [item.get_text() for item in ax.get_zticklabels()]
empty_string_labelsz = ['']*len(labelsz)
ax.set_zticklabels(empty_string_labelsz)

import matplotlib.ticker as ticker
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(0.25))
ax.zaxis.set_major_locator(ticker.MultipleLocator(100))
ax.zaxis.set_minor_locator(ticker.MultipleLocator(50))

ax.tick_params(axis='z', which='major', pad=10)

ax.set_xlabel('X',labelpad=5,fontsize=15)
ax.set_ylabel('Y',labelpad=5,fontsize=15)
ax.set_zlabel('Z',labelpad=5,fontsize=15)


ax.view_init(elev=35., azim=-70)

fig.tight_layout()

plt.show()

2 Answers2

0

(Answer to question 1) To plot the intersections between the surface and the specified planes (y=-20, and y=20), one need to find what Y[?]=-20 and 20. By inspection, I found that Y[100]=20, Y[20]=-20.

The relevant code to plot the lines of intersection:

# By inspection, Y[100]=20, Y[20]=-20
ax.plot3D(X[100], Y[100], Z[100], color='red', lw=6)  # line-1 at y=20
ax.plot3D(X[20], Y[20], Z[20], color='green', lw=6)   # line-2 at y=-20

# Project them on Z=-100 plane
ax.plot3D(X[100], Y[100], -100, color='red', lw=3)  # projection of Line-1
ax.plot3D(X[20], Y[20], -100, color='green', lw=3)  # projection of Line-2

The output plot:

y20-20

(Answer to question 2) To get better plot with the wireframe standout from the surface plot. The surface plot must be partially transparent, which is achieved by setting option alpha=0.6. The relevant code follows.

Z1 = Z-Z.min()
Z1 = Z1/Z.max()
Xall = zoom(X,3)
Yall = zoom(Y,3)
Zz = zoom(Z1, 3)

surf = ax.plot_surface(Xall, Yall, Zz, rstride=10, cstride=10, 
                       facecolors = cm.jet(Zz/np.amax(Zz)),
                       linewidth=0, antialiased=True,
                       alpha= 0.6)

# Wireframe
ax.plot_wireframe(X, Y, Z, rstride=5, cstride=5, color='black', alpha=1, lw=0.8)

The plot is:

combined

swatchai
  • 17,400
  • 3
  • 39
  • 58
  • Thanks, swatchi, for the comment. Unfortunatly, it is now exactly the answer to my questions. I edited my question slightly to specify it a bit better. – user14360796 Oct 04 '20 at 06:44
  • @user14360796 (According to newly edited question) Can you show me a sketch of ` the xz contour plot for y = 0.5 mirrored on the wall`? – swatchai Oct 04 '20 at 07:32
  • In principle what you can already see in the plot you added in the response. The contourf term results in the 2D plot displaying the xz lines for different y values, all offset to one surface at y=40, the "plot wall". Likewise for yz plot at x=-40. So theoretically I'm fine with it, but I would prefer fewer steps in contours. I added a plot to my question above which might explain the issue a bit better. – user14360796 Oct 04 '20 at 11:35
  • @user14360796 Can you provide the code that produces your real 3D wireframe you are working on. I like to work on it rather than the test data. – swatchai Oct 04 '20 at 13:38
  • @swatschai I added now the original code to the question; however, I'm reading in csv files for the data, which I most probably cannot upload here (or can I?) and which are too big to be included in the code itself. Does that help anyway? – user14360796 Oct 04 '20 at 14:14
  • In the meantime I found out that in "cset = ax.contourf(Xall, Yall, Z, 2, zdir='x', offset=0, cmap=plt.cm.coolwarm, shade = False, edgecolor='none', alpha=0.5)", the stride can be set (which is 2 here). That at least reduces the steps of the contours, although they are not set to a specific value, which is what I initially wanted. – user14360796 Oct 04 '20 at 14:15
0

Alternate possible answer.

This code demonstrates

  1. A plot of a surface and its correponding wireframe
  2. The creation of data and its plot of 3d lines (draped on the surface in 1) at specified values of x and y
  3. Projections of the 3d lines (in 2) on to the frame walls
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from scipy import interpolate
import numpy as np

# use the test data for plotting
fig = plt.figure(1, figsize=(6,6), facecolor='w', edgecolor='gray')
ax  = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.1)  #get 3d data at appropriate density

# create an interpolating function
# can take a long time if data is too large
f1 = interpolate.interp2d(X, Y, Z, kind='linear')

# in general, one can use a set of other X,Y,Z that cover a surface
# preferably, (X,Y) are in grid arrangement

# make up a new set of 3d data to plot
# ranges of x1, and y1 will be inside (X,Y) of the data obtained above
# related grid, x1g,y1g,z1g will be obtained from meshgrid and the interpolated function
x1 = np.linspace(-15,15,10)
y1 = np.linspace(-15,15,10)
x1g, y1g = np.meshgrid(x1, y1)
z1g = f1(x1, y1)  #dont use (x1g, y1g)

# prep data for 3d line on the surface (X,Y,Z) at x=7.5
n = 12
x_pf = 7.5
x5 = x_pf*np.ones(n)
y5 = np.linspace(-15, 15, n)
z5 = f1(x_pf, y5)
# x5,y5,z5 can be used to plot 3d line on the surface (X,Y,Z)

# prep data for 3d line on the surface (X,Y,Z) at y=6
y_pf = 6
x6 = np.linspace(-15, 15, n)
y6 = x_pf*np.ones(n)
z6 = f1(x6, y_pf)
# x6,y6,z6 can be used to plot 3d line on the surface (X,Y,Z)

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

ax.plot_surface(x1g, y1g, z1g, alpha=0.25)
ax.plot_wireframe(x1g, y1g, z1g, rstride=2, cstride=2, color='black', zorder=10, alpha=1, lw=0.8)

# 3D lines that follow the surface
ax.plot(x5,y5,z5.flatten(), color='red', lw=4)
ax.plot(x6,y6,z6.flatten(), color='green', lw=4)

# projections of 3d curves
# project red and green lines to the walls
ax.plot(-15*np.ones(len(y5)), y5, z5.flatten(), color='red', lw=4, linestyle=':', alpha=0.6)
ax.plot(x6, 15*np.ones(len(x6)), z6.flatten(), color='green', lw=4, linestyle=':', alpha=0.6)

# projections on other sides (become vertical lines)
# change to if True, to plot these
if False:
    ax.plot(x5, 15*np.ones(len(x5)), z5.flatten(), color='red', lw=4, alpha=0.3)
    ax.plot(-15*np.ones(len(x6)), y6, z6.flatten(), color='green', lw=4, alpha=0.3)

ax.set_title("Projections of 3D lines")

# set limits
ax.set_xlim(-15, 15.5)
ax.set_ylim(-15.5, 15)

plt.show();

enter image description here

swatchai
  • 17,400
  • 3
  • 39
  • 58
  • Many thanks, this was exactly what I was looking for! I just applied it to my data, and it works perfectly! Just one small edit: in the preparation of the data at y=6, it has to be y6 = y_pf*np.ones(n) instead of ...x_pf... . By the way, this also solved my question 2, which was about the optical arrangement of the objects within the plot - by not using contourf, the wireframe is always at the front and not covered by any surface. Very smart work-around! – user14360796 Oct 05 '20 at 11:37