1

Using matplotlib, I am trying to create a 3d plot that has three semi-transparent planes along the xy, yz, and xz planes. I am basing my code off of this post, which has a partial workaround for a transparency bug reported three years ago.

If you try out the below code and rotate the graph, you'll see that there are sudden color shifts in the areas where the planes overlap. For example below you see the center area suddenly change from green to blue. Is there a further workaround to prevent this?

Color shift upon rotation


Here is my code:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d as mp3d

xy = [ (-1, -1, 0),
       ( 1, -1, 0),
       ( 1,  1, 0),
       (-1,  1, 0),
    ]

yz = [ (0, -1, -1),
       (0,  1, -1),
       (0,  1,  1),
       (0, -1,  1),
    ]

xz = [ (-1, 0, -1),
       ( 1, 0, -1),
       ( 1, 0,  1),
       (-1, 0,  1),
    ]

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter([-1, -1, -1, -1, 1, 1, 1, 1], [-1, -1, 1, 1, -1, -1, 1, 1], [-1, 1, -1, 1, -1, 1, -1, 1])
face1 = mp3d.art3d.Poly3DCollection([xy], alpha=0.5, linewidth=1)
face2 = mp3d.art3d.Poly3DCollection([yz], alpha=0.5, linewidth=1)
face3 = mp3d.art3d.Poly3DCollection([xz], alpha=0.5, linewidth=1)

# This is the key step to get transparency working
alpha = 0.5
face1.set_facecolor((0, 0, 1, alpha))
face2.set_facecolor((0, 1, 0, alpha))
face3.set_facecolor((1, 0, 0, alpha))

ax.add_collection3d(face1)
ax.add_collection3d(face2)
ax.add_collection3d(face3)

plt.show()
Community
  • 1
  • 1
anon
  • 4,578
  • 3
  • 35
  • 54
  • 1
    Matplotlib has never designed to be a 3D plotting library. Since its 3D capabilities rely on projections to 2D space you have all kinds of overlap-not-working and which-one-is-in-front issues. For this problem here, I doubt there is any good solution, but you may try to divide every plane into 4 parts and see if it gives a visually appealing result. – ImportanceOfBeingErnest Apr 02 '17 at 07:26
  • Wow, dividing up into the 4 "quadrants" really worked. I would never have guessed that. Nice workaround! If you want to turn your comment into an answer, I can accept it. Also, can you provide some insight as to how you knew to do that? Is it experience, or was there specific reasoning behind it? – anon Apr 03 '17 at 03:27
  • The only way to accurately render transparent objects that intersect each other is with ray tracing, which is horribly slow and thus used mostly in offline generation (like movie CGI) - and rarely in data visualization. Splitting into quadrants removed your self-intersection problem. – Mike Wise Apr 03 '17 at 08:21

1 Answers1

4

As suggested in the comments, you can divide every plane into its four quadrant planes and draw those individually. This way matplotlib is able to determine which of them should be in front and the planes obey transparency.

enter image description here

A minimal example:

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

a = np.array([( 0, 0, 0),( 1, 0, 0),( 1, 1, 0),( 0, 1, 0)])
R1 = np.array([[0,-1,0],[1,0,0],[0,0,1]])
R2 = (R1[::-1].T)[:,[1,0,2]]
R3 = (R1[::-1])[:,[1,0,2]]
f = lambda a,r: np.matmul(r, a.T).T
g = lambda a,r: [a, f(a,r), f(f(a,r),r), f(f(f(a,r),r),r)]


fig = plt.figure()
ax = fig.add_subplot(111, projection=Axes3D.name)
ax.scatter([-1,1], [-1,1], [-1,1], alpha=0.0)

for i, ind , r in zip(range(3),[[0,1,2],[2,0,1],[1,2,0]], [R1,R2,R3]):
    xy = g(a[:,ind], r )
    for x in xy:
        face1 = mp3d.art3d.Poly3DCollection([x] , alpha=0.5, linewidth=1)
        face1.set_facecolor((i//2, i%2, i==0,  0.5))
        ax.add_collection3d(face1)

plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712