1

In short: I am using matplotlib to draw a scater plot and a surface. I have run into a problem so that the surface is always drawn on top of the points and not behind as it should be.

I want to solve this by preprocessing color of the points so that when overlayed by the surface I obtain the "original color", this way the surface will appear behind the points. I specify color of the points using HEX values.

The problem with the surface and points I encounter is similar to the problem described here.


Here are the details:

I have a 3D scatter plot of Blue and Red points in Matplotlib:

self.subplot_3d = self.subplot.scatter(x_red, y_red, z_red, s = 150, c = RED,  depthshade = False, lw = 0, alpha = 1)
self.subplot_3d = self.subplot.scatter(x_blue, y_blue, z_blue, s = 150, c = BLUE,  depthshade = False, lw = 0, alpha = 1)

Points are linearly separable - there exists a separating plane, which separates red points and blue points. I draw the plain using

self.plane = self.subplot.plot_surface(X, Y, Z, antialiased = True, color = RED_BACKGROUND, alpha = 0.5)

I want to draw two plots: in the first, there is no separating plain drawn - only the red and blue points(left). And in the second there is a separating plane (right).

enter image description here enter image description here

Once the separating plane is drawn, I was not able to force the plane to be drawn between red and blue points (as expected from the plane equation and the position of the points) The plane always appears on top of all the points.

The solution I was thinking about is the following: "preprocess" the color of the blue points so that when overlayed with the color of the transparent plane I obtain the original blue color. In this way the points will appear to be on top of the plane, and it should "solve" my current problem.

I have used HEX color specification, I am not sure how the color mix when I use transparency in matplotlib. I am looking for reference or suggestion how I should "counteract" red transparent color of the plane with, say, alpha = 0.2.

In another words I would like to achive something like sepicifiying: Blue = #0000FF Red = #FF0000 PreProcBlue = "#-FF00FF"' so that PreProcBlue + Red = Blue.

Thanks!

Community
  • 1
  • 1
them
  • 218
  • 4
  • 14
  • If the surface should be behind **all** points, you could simply use the `zorder` argument (e.g. `zorder=-1` for the surface, and `zorder=1` for the points). – ImportanceOfBeingErnest Jan 23 '17 at 21:35
  • And what is wrong with having the blue points in the color that comes out when being overlayed with a semitransparent plane? Then you will have red points and not-so-blue points. In any case the solution you try here, is impossible. – ImportanceOfBeingErnest Jan 23 '17 at 21:52
  • @ImportanceOfBeingErnest Thanks for the comments, To the first comment: the surface plane should be between the red and the blue points, namely on top of red points and behind blue points. `zorder` does not work, I tried it in different ways. To the second comment: I want to put side by side the case with the plane and the case without plane so I want the color of the blue points match on those two plots. I am interested in automatic output of a good quality so small changes in color do matter, otherwise, I would not bother. – them Jan 23 '17 at 23:13
  • So you could go the other direction. Coloring the points in the case without a plane in the same color as those with a plane. That would actually work, you just need to plot it once, use some color picker program to determine the color in the picture with plane, and apply it to the case without plane. – ImportanceOfBeingErnest Jan 23 '17 at 23:16
  • @ImportanceOfBeingErnest Great idea, thanks for mentioning that, however, the plots are used in a publication quality document with other plots of similar style (plots that use blue red colors), I want to stick to the same blue color across the paper... I would prefer not to switch to another blue color at this point. – them Jan 24 '17 at 18:06

1 Answers1

5

One problem is that scatter does not respect zorder. Therefore one could use plot instead.

I have compiled an example which compares plot and scatter, plotting blue dots below z=0, a plane at z=0 and red dots above z=0.
In each case I'm using a zorder of -100, 1, 100 for blue dots, plane, red dots respectively. (Note that if differences in zorder are smaller or if all are positive, this will fail)

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

fig = plt.figure(figsize=(12,6))
ax = fig.add_subplot(121, projection='3d', aspect="equal")
ax2 = fig.add_subplot(122, projection='3d', aspect="equal")
plt.subplots_adjust(0,0,1,1,0,0)

x = np.array([-1,1])
X,Y = np.meshgrid(x,x)
Z = np.zeros_like(X)

xr = np.random.rand(10,3)
xb = np.random.rand(10,3)
xb[:,2] = xb[:,2]-1.


ax2.set_title("scatter")
ax2.scatter(xb[:,0], xb[:,1], xb[:,2], s = 144, c = "b",  depthshade = False, lw = 0, alpha = 1, zorder=-100)
ax2.plot_surface(X, Y, Z, antialiased = True, color = "#800000", alpha = 0.5, zorder=0)
ax2.scatter(xr[:,0], xr[:,1], xr[:,2], s = 144, c = "r",  depthshade = False, lw = 0, alpha = 1, zorder=100)

ax.set_title("plot")
ax.plot(xb[:,0], xb[:,1], xb[:,2], c = "b",  marker="o", markersize=12, lw = 0, alpha = 1, zorder=-100)
ax.plot_surface(X, Y, Z, antialiased = True, color = "#800000", alpha = 0.5, zorder=0)
ax.plot(xr[:,0], xr[:,1], xr[:,2], c = "r",  marker="o", markersize=12, lw = 0, alpha = 1, zorder=100)

ax2.view_init(elev=32, azim=115)
ax.view_init(elev=32, azim=115)

def on_move(event):
    if event.inaxes == ax:
        ax2.view_init(elev=ax.elev, azim=ax.azim)
    elif event.inaxes == ax2:
        ax.view_init(elev=ax2.elev, azim=ax2.azim)
    else:
        return
    fig.canvas.draw_idle()

c1 = fig.canvas.mpl_connect('motion_notify_event', on_move)

plt.savefig(__file__+".png")
plt.show()

As can be seen from the picture, plot behaves as expected, showing the red dots above the plane and the blue dots below it.

enter image description here

Note that I have used matplotlib 2.0.0 to produce those plots; I am not sure, if it would be the same for 1.5.3 or below.


Some insights on why it is impossible to 'precompensate' the color of the background dots for the mixing with an overlay for the case that the resulting color should be fully blue:

We stay in the RGBA colorsceme, where each color is represented by the tuple (red, green, blue, alpha), where each channel can take values between 0 and 1.
The resulting color shall be fully blue: cres = (0,0,1,1). The color of the overlay can be some half-transparent red: cover = (1,0,0,0.5).

The color of the dot can then be calculated using the usual colormixing formulae to be cdot = (1,0,2,1).

As you can see, the blue channel would need to be 2 for that to work, which is outside the colorsystem's range. Of course you might want to verify that for any other color cover this is still true, but it already is clear intuitively: For any mixing with a transparent color cover, cdot's blue channel will be larger than 1, producing a non-existing color.

Community
  • 1
  • 1
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thanks, I will mark it as an answer since it can solve my problem and it is proper way to solve it, although I am still looking to know the formula for color mixing to solve my specific problem without updates and etc... – them Jan 26 '17 at 19:58
  • 1
    I have added some details about why it's impossible to do a mixing such that the result would be fully blue. – ImportanceOfBeingErnest Jan 26 '17 at 21:08