1

I am plotting a basic scatterplot in 3D using code from another SO post (Matplotlib scatter plot legend (top answer)) but want to have the points opacity relative to the 'depth' of the point as with ax.scatter depthshade=True.

enter image description here

I had to use ax.plot because ax.scatter doesn't appear to work well with legends on 3D plots.

I'm wondering if there is a way to get a similar aesthetic for ax.plot.

Thanks!

Carlos G. Oliver
  • 315
  • 4
  • 14
  • What do you mean by "camera". You can freely rotate your 3D coordinates, there is no fixed camera point. – Mr. T Apr 12 '18 at 18:24
  • 1
    I meant something like the "depth" of the points. You can see in the image I included above that more distant points (further from the camera) have lower opacity values. But when using ax.plot() all points have opacity of 1. – Carlos G. Oliver Apr 13 '18 at 03:24
  • Well, I still don't understand, which points should be more transparent than others. You just repeat "further from the camera" after I told you that this terminology means nothing, because `matplotlib` views are not static. Anyhow, `ax.plot()` has the keyword `alpha`. `alpha = 0` means transparent, `alpha = 1` makes the object opaque. You can choose freely values between 0 and 1. – Mr. T Apr 13 '18 at 07:44
  • 2
    I think the OP means the fog added to give the appearance of depth, sometimes called "depth cue" and switched on by the option in scatter plot of `depthshade=True`. No such option seems to exist in plot... – Ed Smith Apr 13 '18 at 11:30
  • Yes @EdSmith that is what I meant. Sorry if I was unclear. I removed the word 'camera' from my question. – Carlos G. Oliver Apr 13 '18 at 12:53

1 Answers1

4

It looks like you're out of luck on this, it seems plot does not have the depthshade=True feature. I think the problem is plot does not let you set a different color (or alpha value) for each points in the way scatter does, which I guess is how depthshade is applied.

A solution is to loop over all points and set colour one by one, together with the mpl_toolkits.mplot3d.art3d.zalpha helper function to give depth.

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

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

n = 100
xs = np.random.rand(n)
ys = np.random.rand(n)
zs = np.random.rand(n)
color = [1,0,0,1]

#Get normal to camera
alpha= ax.azim*np.pi/180.
beta= ax.elev*np.pi/180.
n = np.array([np.cos(alpha)*np.sin(beta), 
              np.sin(alpha)*np.cos(beta), 
              np.sin(beta)])
ns = -np.dot(n, [xs, ys, zs])
cs = mpl_toolkits.mplot3d.art3d.zalpha(color, ns)

for i in range(xs.shape[0]):
    ax.plot([xs[i]], [ys[i]], [zs[i]], 'o', color=cs[i])

plt.show()

One tricky point is that the camera position ax.elev and ax.azim need to be used to work out the normal vector. Also, when you rotate the position of the camera, this will no longer be coloured correctly. To fix this, you could register an update event as follows,

def Update(event):
    #Update normal to camera
    alpha= ax.azim*np.pi/180.
    beta= ax.elev*np.pi/180.
    n = np.array([np.cos(alpha)*np.sin(beta), 
                  np.sin(alpha)*np.cos(beta), 
                  np.sin(beta)])
    ns = -np.dot(n, [xs, ys, zs])
    cs = mpl_toolkits.mplot3d.art3d.zalpha(color, ns)
    for i, p in enumerate(points):
        p[0].set_alpha(cs[i][3])

fig.canvas.mpl_connect('draw_event', Update)

points = []
for i in range(xs.shape[0]):
    points.append(ax.plot([xs[i]], [ys[i]], [zs[i]], 'o', color=cs[i]))
Ed Smith
  • 12,716
  • 2
  • 43
  • 55
  • thanks that works! maybe worth making a feature request? – Carlos G. Oliver Apr 13 '18 at 15:48
  • As far as I know, scatter is for different coloured points so I don't think this will be developed for plot (how would you colour lines, also the above would be very inefficient). However, fixing the problem with the legend may be worth raising (although I actually get a nice legend in matplotlib 1.4.3 using scatter on your linked example (https://stackoverflow.com/questions/17411940/matplotlib-scatter-plot-legend) if I add `scatterpoints=1`) – Ed Smith Apr 13 '18 at 16:55