I am trying to create a 3D surface plot similar to the one shown below. view of 3D surface plot with defined z-bounds and added contour lines I have been able to have some luck with the basic pyplot plot_surface function, but I have not found a way to cleanly clip the top of the surface, despite trying several different approaches suggested elsewhere online. The code to generate each of the plots I have tried is below, as well as a picture of the corresponding output. If it is important, I am also running all code below on a Jupyter notebook within PyCharm.
My first approach was to just use the basic plot_surface function. The specific surface z = f(x, y) that I am plotting rises very sharply (see the z-axis scale), but I am mainly trying to plot the region of the surface near the local minima, which is difficult to see in the graph.
import matplotlib
import matplotlib.pyplot as plt
import jax.numpy as jnp
def f(x, y, a, b):
return x**4 + y**4 + y**3 - (4 * x**2 * y) + y**2 - (a * x) + (b * y)
X, Y = jnp.meshgrid(jnp.arange(-3, 3, 0.01), jnp.arange(-3, 3, 0.01))
Z = f(X, Y, 0, 0) #Generate the z-values at each (x, y) for a single choice of parameters a and b
matplotlib.use("Qt5Agg")
fig, ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(16, 12))
surf = ax.plot_surface(X, Y, Z, linewidth=0, antialiased=False, cmap="viridis")
ax.view_init(elev=10, azim=15, roll=0)
plt.tight_layout()
plt.show()
Output 1: (https://i.sstatic.net/6HWe3RaB.png)
My second approach was then to define the minimum and maximum z values that I wanted to visualize and pass the argument axlim_clip=True into the plotting function. This seems to have zoomed in on the region of interest, but apparently kept the previous color scaling (i.e. this region has virtually all the same color), and produced a jagged top edge where entire surface patches were removed.
import matplotlib
import matplotlib.pyplot as plt
import jax.numpy as jnp
def f(x, y, a, b):
return x**4 + y**4 + y**3 - (4 * x**2 * y) + y**2 - (a * x) + (b * y)
X, Y = jnp.meshgrid(jnp.arange(-3, 3, 0.01), jnp.arange(-3, 3, 0.01))
Z = f(X, Y, 0, 0) #Generate the z-values at each (x, y) for a single choice of parameters a and b
Zmin, Zmax = jnp.min(Z), jnp.percentile(Z, 15.)
matplotlib.use("Qt5Agg")
fig, ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(16, 12))
surf = ax.plot_surface(X, Y, Z, linewidth=0, antialiased=False, cmap="viridis", axlim_clip=True)
ax.view_init(elev=10, azim=15, roll=0)
ax.set_zlim(Zmin, Zmax)
plt.tight_layout()
plt.show()
Output 2: (https://i.sstatic.net/rULdNyak.png)
My third approach was then to try a mask where I set all z-values above a defined threshold to NaN values. This seems to have fixed the color scaling issue, but it still has the jagged edge on the top of the surface instead of a clean clip.
import matplotlib
import matplotlib.pyplot as plt
import jax.numpy as jnp
def f(x, y, a, b):
return x**4 + y**4 + y**3 - (4 * x**2 * y) + y**2 - (a * x) + (b * y)
X, Y = jnp.meshgrid(jnp.arange(-3, 3, 0.01), jnp.arange(-3, 3, 0.01))
Z = f(X, Y, 0, 0) #Generate the z-values at each (x, y) for a single choice of parameters a and b
Zmax = jnp.percentile(Z, 15.)
Zplateau = jnp.minimum(Z, Zmax)
Zmasked = jnp.where(Z > Zmax, jnp.nan, Zplateau)
matplotlib.use("Qt5Agg")
fig, ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(16, 12))
surf = ax.plot_surface(X, Y, Zmasked, linewidth=0, antialiased=False, cmap="viridis")
ax.view_init(elev=10, azim=15, roll=0)
ax.set_zlim(jnp.min(Z), Zmax)
plt.tight_layout()
plt.show()
Output 3: (https://i.sstatic.net/LR6MZFFd.png)
The last approach I have tried is similar to #3, but I set the z-values above the threshold equal to the threshold value instead of a NaN value. This makes the top of the graph clean, but creates an unwanted plateau at the top of the surface.
import matplotlib
import matplotlib.pyplot as plt
import jax.numpy as jnp
def f(x, y, a, b):
return x ** 4 + y ** 4 + y ** 3 - (4 * x ** 2 * y) + y ** 2 - (a * x) + (b * y)
X, Y = jnp.meshgrid(jnp.arange(-3, 3, 0.01), jnp.arange(-3, 3, 0.01))
Z = f(X, Y, 0, 0) #Generate the z-values at each (x, y) for a single choice of parameters a and b
Zmax = jnp.percentile(Z, 15.)
Zclipped = jnp.where(Z > Zmax, Zmax, Z)
matplotlib.use("Qt5Agg")
fig, ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(16, 12))
surf = ax.plot_surface(X, Y, Zclipped, linewidth=0, antialiased=False, cmap="viridis")
contours = ax.contour(X, Y, Z, levels=[Zmax], zdir="z", offset=Zmax, colors="k", linewidths=2)
ax.view_init(elev=10, azim=15, roll=0)
ax.set_zlim(jnp.min(Z), Zmax)
plt.tight_layout()
plt.show()
Output 4: (https://i.sstatic.net/KxE9yvGy.png)
As some final notes, I have tried (for each approach) using a finer grid of xy-points to evaluate the surface over, as well as changing the rstride and cstride values with the plot_surface call. Both result in the surface of the graph below the top edge being smoother, but the top edge itself is still jagged. This is my first stackoverflow post, so thank you in advance for your help, and please let me know if there is anything else I can provide! :)
