Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib surface plot hides scatter points which should be in front

Yet another question about matplotlib 3d surfaces... I have code which adds a scatter point to a matplotlib surface graph.

from above The problem that I have is that the point always appears behind the surface, regardless of which angle you view it from. from below

If I cobble an (admittedly ugly) version using 3 short lines to mark the same point, it is visible. enter image description here

I have turned off the depthshade function, so it isn't this. Can anybody explain what is going on and how I can correct it? Here is a simplified version of the code:

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

df = pd.DataFrame({10: {10: 1,15: 1,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   15: {10: 4,15: 1,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   20: {10: 6,15: 3,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   25: {10: 7,15: 5,20: 3,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   30: {10: 9,15: 6,20: 4,25: 3,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   35: {10: 10,15: 7,20: 5,25: 4,30: 2,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   40: {10: 11,15: 8,20: 6,25: 4,30: 3,35: 2,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   45: {10: 12,15: 9,20: 7,25: 5,30: 4,35: 3,40: 2,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   50: {10: 13,15: 9,20: 7,25: 6,30: 5,35: 4,40: 3,45: 2,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   55: {10: 14,15: 10,20: 8,25: 7,30: 5,35: 4,40: 3,45: 3,50: 2,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   60: {10: 15,15: 11,20: 9,25: 7,30: 6,35: 5,40: 4,45: 3,50: 3,55: 2,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   65: {10: 16,15: 12,20: 9,25: 8,30: 6,35: 5,40: 5,45: 4,50: 3,55: 2,60: 2,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   70: {10: 17,15: 12,20: 10,25: 8,30: 7,35: 6,40: 5,45: 4,50: 4,55: 3,60: 2,65: 2,70: 1,75: 1,80: 1,85: 1,90: 1},
                   75: {10: 18,15: 13,20: 10,25: 9,30: 7,35: 6,40: 5,45: 5,50: 4,55: 3,60: 3,65: 2,70: 2,75: 1,80: 1,85: 1,90: 1},
                   80: {10: 19,15: 14,20: 11,25: 9,30: 8,35: 7,40: 6,45: 5,50: 4,55: 4,60: 3,65: 3,70: 2,75: 2,80: 1,85: 1,90: 1},
                   85: {10: 21,15: 14,20: 11,25: 10,30: 8,35: 7,40: 6,45: 6,50: 5,55: 4,60: 4,65: 3,70: 3,75: 2,80: 2,85: 1,90: 1},
                   90: {10: 23,15: 15,20: 12,25: 10,30: 9,35: 8,40: 7,45: 6,50: 5,55: 5,60: 4,65: 3,70: 3,75: 3,80: 2,85: 2,90: 1}})




xv, yv = np.meshgrid(df.index, df.columns)
ma = np.nanmax(df.values)
norm = matplotlib.colors.Normalize(vmin = 0, vmax = ma, clip = True)

fig = plt.figure(1)
ax = Axes3D(fig)
surf = ax.plot_surface(yv,xv,df, cmap='viridis_r', linewidth=0.3,
                       alpha = 0.8, edgecolor = 'k', norm=norm)
ax.scatter(25,35,4, c='k', depthshade=False, alpha = 1, s=100)

fig = plt.figure(2)
ax = Axes3D(fig)
surf = ax.plot_surface(yv,xv,df, cmap='viridis_r', linewidth=0.3,
                       alpha = 0.8, edgecolor = 'k', norm=norm)
line1_x = [25,25]
line1_y = [35,35]
line1_z = [3,5]

line2_x = [25,25]
line2_y = [33,37]
line2_z = [4,4]

line3_x = [23,27]
line3_y = [35,35]
line3_z = [4,4]

ax.plot(line1_x, line1_y, line1_z, alpha = 1, linewidth = 1, color='k')
ax.plot(line2_x, line2_y, line2_z, alpha = 1, linewidth = 1, color='k')
ax.plot(line3_x, line3_y, line3_z, alpha = 1, linewidth = 1, color='k')
plt.show()
like image 301
Will Avatar asked Jul 09 '18 08:07

Will


People also ask

How do I set boundaries in Matplotlib?

Matplotlib automatically arrives at the minimum and maximum values of variables to be displayed along x, y (and z axis in case of 3D plot) axes of a plot. However, it is possible to set the limits explicitly by using set_xlim() and set_ylim() functions.

What does %Matplotlib mean in Python?

What Does Matplotlib Mean? Matplotlib is a plotting library available for the Python programming language as a component of NumPy, a big data numerical handling resource. Matplotlib uses an object oriented API to embed plots in Python applications.

How do I make Matplotlib curve smooth?

Smooth Spline Curve with PyPlot:interpolate. make_interp_spline(). We use the given data points to estimate the coefficients for the spline curve, and then we use the coefficients to determine the y-values for very closely spaced x-values to make the curve appear smooth.


2 Answers

A useful workaround is to use the option computed_zorder=False (added in Feb 2021, see doc), and to plot the different elements in the desired order. The only caveat is that it requires knowing which points are below the surface, and which points are above.

Et Voilà !

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

df = pd.DataFrame({10: {10: 1,15: 1,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   15: {10: 4,15: 1,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   20: {10: 6,15: 3,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   25: {10: 7,15: 5,20: 3,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   30: {10: 9,15: 6,20: 4,25: 3,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   35: {10: 10,15: 7,20: 5,25: 4,30: 2,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   40: {10: 11,15: 8,20: 6,25: 4,30: 3,35: 2,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   45: {10: 12,15: 9,20: 7,25: 5,30: 4,35: 3,40: 2,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   50: {10: 13,15: 9,20: 7,25: 6,30: 5,35: 4,40: 3,45: 2,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   55: {10: 14,15: 10,20: 8,25: 7,30: 5,35: 4,40: 3,45: 3,50: 2,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   60: {10: 15,15: 11,20: 9,25: 7,30: 6,35: 5,40: 4,45: 3,50: 3,55: 2,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   65: {10: 16,15: 12,20: 9,25: 8,30: 6,35: 5,40: 5,45: 4,50: 3,55: 2,60: 2,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   70: {10: 17,15: 12,20: 10,25: 8,30: 7,35: 6,40: 5,45: 4,50: 4,55: 3,60: 2,65: 2,70: 1,75: 1,80: 1,85: 1,90: 1},
                   75: {10: 18,15: 13,20: 10,25: 9,30: 7,35: 6,40: 5,45: 5,50: 4,55: 3,60: 3,65: 2,70: 2,75: 1,80: 1,85: 1,90: 1},
                   80: {10: 19,15: 14,20: 11,25: 9,30: 8,35: 7,40: 6,45: 5,50: 4,55: 4,60: 3,65: 3,70: 2,75: 2,80: 1,85: 1,90: 1},
                   85: {10: 21,15: 14,20: 11,25: 10,30: 8,35: 7,40: 6,45: 6,50: 5,55: 4,60: 4,65: 3,70: 3,75: 2,80: 2,85: 1,90: 1},
                   90: {10: 23,15: 15,20: 12,25: 10,30: 9,35: 8,40: 7,45: 6,50: 5,55: 5,60: 4,65: 3,70: 3,75: 3,80: 2,85: 2,90: 1}})




xv, yv = np.meshgrid(df.index, df.columns)
ma = np.nanmax(df.values)
norm = matplotlib.colors.Normalize(vmin = 0, vmax = ma, clip = True)

fig = plt.figure(1)
ax = Axes3D(fig, computed_zorder=False)

ax.scatter(10,70,4, c='k', depthshade=False, alpha = 1, s=100)
surf = ax.plot_surface(yv,xv,df, cmap='viridis_r', linewidth=0.3,
                       alpha = 0.8, edgecolor = 'k', norm=norm)
ax.scatter(25,35,4, c='k', depthshade=False, alpha = 1, s=100)

plt.show()
like image 154
TomDLT Avatar answered Nov 13 '22 06:11

TomDLT


Run into this problem in 2020 and do not want to switch to another package. This solution is a modification of Will's answer above. Basically draw the circle in three axis to make it more like a dot. Also use ellipse to adjust for axis ratios. Works better if you set the radius smaller and choose a face color:

enter image description here

   def add_point(ax, x, y, z, fc = None, ec = None, radius = 0.005):
       xy_len, z_len = ax.get_figure().get_size_inches()
       axis_length = [x[1] - x[0] for x in [ax.get_xbound(), ax.get_ybound(), ax.get_zbound()]]
       axis_rotation =  {'z': ((x, y, z), axis_length[1]/axis_length[0]),
                         'y': ((x, z, y), axis_length[2]/axis_length[0]*xy_len/z_len),
                         'x': ((y, z, x), axis_length[2]/axis_length[1]*xy_len/z_len)}
       for a, ((x0, y0, z0), ratio) in axis_rotation.items():
           p = Ellipse((x0, y0), width = radius, height = radius*ratio, fc=fc, ec=ec)
           ax.add_patch(p)
           art3d.pathpatch_2d_to_3d(p, z=z0, zdir=a)

where radius is the radius of the "circle", fc is the face color, ec is the edge color.

The modified code:

import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D, art3d
from matplotlib.patches import Circle, Ellipse
import numpy as np

df = pd.DataFrame({10: {10: 1,15: 1,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   15: {10: 4,15: 1,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   20: {10: 6,15: 3,20: 1,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   25: {10: 7,15: 5,20: 3,25: 1,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   30: {10: 9,15: 6,20: 4,25: 3,30: 1,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   35: {10: 10,15: 7,20: 5,25: 4,30: 2,35: 1,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   40: {10: 11,15: 8,20: 6,25: 4,30: 3,35: 2,40: 1,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   45: {10: 12,15: 9,20: 7,25: 5,30: 4,35: 3,40: 2,45: 1,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   50: {10: 13,15: 9,20: 7,25: 6,30: 5,35: 4,40: 3,45: 2,50: 1,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   55: {10: 14,15: 10,20: 8,25: 7,30: 5,35: 4,40: 3,45: 3,50: 2,55: 1,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   60: {10: 15,15: 11,20: 9,25: 7,30: 6,35: 5,40: 4,45: 3,50: 3,55: 2,60: 1,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   65: {10: 16,15: 12,20: 9,25: 8,30: 6,35: 5,40: 5,45: 4,50: 3,55: 2,60: 2,65: 1,70: 1,75: 1,80: 1,85: 1,90: 1},
                   70: {10: 17,15: 12,20: 10,25: 8,30: 7,35: 6,40: 5,45: 4,50: 4,55: 3,60: 2,65: 2,70: 1,75: 1,80: 1,85: 1,90: 1},
                   75: {10: 18,15: 13,20: 10,25: 9,30: 7,35: 6,40: 5,45: 5,50: 4,55: 3,60: 3,65: 2,70: 2,75: 1,80: 1,85: 1,90: 1},
                   80: {10: 19,15: 14,20: 11,25: 9,30: 8,35: 7,40: 6,45: 5,50: 4,55: 4,60: 3,65: 3,70: 2,75: 2,80: 1,85: 1,90: 1},
                   85: {10: 21,15: 14,20: 11,25: 10,30: 8,35: 7,40: 6,45: 6,50: 5,55: 4,60: 4,65: 3,70: 3,75: 2,80: 2,85: 1,90: 1},
                   90: {10: 23,15: 15,20: 12,25: 10,30: 9,35: 8,40: 7,45: 6,50: 5,55: 5,60: 4,65: 3,70: 3,75: 3,80: 2,85: 2,90: 1}})




xv, yv = np.meshgrid(df.index, df.columns)
ma = np.nanmax(df.values)
norm = matplotlib.colors.Normalize(vmin = 0, vmax = ma, clip = True)

fig = plt.figure(1)
ax = Axes3D(fig)
surf = ax.plot_surface(yv,xv,df, cmap='viridis_r', linewidth=0.3,
                       alpha = 0.8, edgecolor = 'k', norm=norm)

add_point(ax, 25, 35, 0, radius=1)
add_point(ax, 25, 35, 2, radius=2)
add_point(ax, 25, 35, 4, radius=3)

plt.show()
like image 32
Z Li Avatar answered Nov 13 '22 05:11

Z Li