Matplotlib 3d alpha transparency bug? - matplotlib

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?
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()

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.
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()

Related

Cublic spline interpolation produces straight lines

I would like to obtain a smooth curve going through specific points with integer coordinates. Instead of that I get straight line segments between the points. I tried interp1d(x,y,kind='cubic') and also CubicSpline, nothing works. Here is my code:
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d,CubicSpline
x = np.arange(34)
y = [8,3,0,1,6,2,1,7,6,2,0,2,6,0,1,6,2,2,0,2,7,0,2,8,6,3,6,2,0,1,6,2,7,2]
f = CubicSpline(x, y)
plt.figure(figsize=(10,3))
plt.plot(x, y, 'o', x, f(x))
plt.show()
and here is the result:
Can you tell me how to get smooth curves instead?
Now you are using the original x-values to draw the curve. You need a new array with much more intermediate x-values. Numpy's np.linspace() creates such an array between a given minimum and maximum.
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d, CubicSpline
y = [8, 3, 0, 1, 6, 2, 1, 7, 6, 2, 0, 2, 6, 0, 1, 6, 2, 2, 0, 2, 7, 0, 2, 8, 6, 3, 6, 2, 0, 1, 6, 2, 7, 2]
x = np.arange(len(y))
f = CubicSpline(x, y)
plt.figure(figsize=(10, 3))
xs = np.linspace(x.min(), x.max(), 500)
plt.plot(x, y, 'o', xs, f(xs))
plt.tight_layout()
plt.show()

How to plot a tuple as x axis and a list on y axis

Suppose I have a df in the following form
import pandas as pd
import numpy as np
import matplotlib as plt
import matplotlib.pyplot as plt
col1
(0, 0, 0, 0) 1
(0, 0, 0, 2) 2
(0, 0, 2, 2) 3
(0, 2, 2, 2) 4
I want to plot my index in x axis and col1 in y axis.
What I tried
plt.plot(list(df.index), df['col1'])
However, it generates a plot that is not what I am looking for.
If you give a list of 4-tuples as x for plt.plot(), they are interpreted as 4 line plots, one with the first elements from the tuples, one with the second elements, etc.
You can convert the tuples to strings to show them as such:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({'y': [1, 2, 3, 4]}, index=[(0, 0, 0, 0), (0, 0, 0, 2), (0, 0, 2, 2), (0, 2, 2, 2)])
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12, 3))
ax1.plot(list(df.index), df['y'])
ax2.plot([str(i) for i in df.index], df['y'])
plt.show()

Is there an 'empty' patch in matplotlib?

I am adding patches according to a list of ones and zeros (e.g. [1, 0, 1, 1, 0, 0, 1, 0, 1, 0]). I want to add patches where there are ones using matplotlib and leave the zeros empty. However, trying the following code raises a list index out of range error:
fig = plt.figure()
ax = plt.axes()
self.patches = []
for i, val in enumerate(my_list):
if val == 1:
self.patches.append(plt.Rectangle((i, 0), 0.9, 1, angle=0.0,
facecolor='r', edgecolor='w',
linewidth='2.0',
animated=False))
ax.add_patch(self.patches[i])
The only thing I can think of is using an else statement in the code above to add a rectangle with the same colour as the background for the zeros. Is there an empty patch object that one could use instead? I want the patches to be in the same position as the ones in the list.
use color='none' to set all colors (facecolor and edgecolor to invisible).
alternatively, you can pass visible=False to the constructor to hide the patch.
Your loop could be:
my_list = [1, 0, 1, 1, 0, 0, 1, 0, 1, 0]
patches = []
fig, ax = plt.subplots()
for i, val in enumerate(my_list):
p = plt.Rectangle((i, 0), 0.9, 1, angle=0.0,
facecolor='r', edgecolor='w',
linewidth='2.0',
animated=False, visible=bool(val))
patches.append(p)
ax.add_patch(p)
Sure, you can pass an empty patch to Matplotlib, but I wouldn't unless for some reason you want matplotlib to know about your missing data:
fig = plt.figure()
ax = plt.axes()
self.patches = []
for i, val in enumerate(my_list):
if val == 1:
self.patches.append(plt.Rectangle((i, 0), 0.9, 1, angle=0.0,
facecolor='r', edgecolor='w',
linewidth='2.0',
animated=False))
ax.add_patch(self.patches[i])
else:
self.patches.append(None)

How to make a 3d matlibplot not show masked values

The diagram should only show the masked values. As in the (manipulated) figure on the right side.
Default shows all values. In 2d diagramms there is no problem.
Is it also possible in 3d diagrams? If yes, how to?
import matplotlib.pyplot as plt
import numpy as np
Z = np.array([
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
])
x, y = Z.shape
xs = np.arange(x)
ys = np.arange(y)
X, Y = np.meshgrid(xs, ys)
M = np.ma.fromfunction(lambda i, j: i > j, (x, y))
R = np.ma.masked_where(M, Z)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, R)
#ax.plot_wireframe(X, Y, R)
#ax.plot_trisurf(X.flatten(), Y.flatten(), R.flatten())
fig.show()
Update: Matplotlib >= 3.5.0
As pointed out by eric's comment, the issue is solved in matplotlib <= 3.5.0, and the code from the OP works as expected. So right now, if you can probably your best option is to update matplotlib.
The original answer is left here for situations were updating matplotlib might not be an option.
Old: Matplotlib < 3.5.0
The bad news is that it seems that plot_surface() just ignores masks. In fact there is an open issue about it.
However, here they point out a workaround that although it's far from perfect it may allow you get some acceptable results. The key issue is that NaN values will not be plotted, so you need to 'mask' the values that you don't want to plot as np.nan.
Your example code would become something like this:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
Z = np.array([
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
[ 1, 1, 1, 1, 1, ],
])
x, y = Z.shape
xs = np.arange(x)
ys = np.arange(y)
X, Y = np.meshgrid(xs, ys)
R = np.where(X>=Y, Z, np.nan)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, R, rstride=1, linewidth=0)
fig.show()
*I had to add the rstride=1 argument to the plot_surface call; otherwise I get a segmentation fault... o_O
And here's the result:

matplotlib invisible bar if height is zero

I'm plotting a bar graph without axes. I'd like to only show bars with non-zero values. If it is zero, I want no bar at all. Currently it will show a tiny line at the zero axis, I want that to disappear. How can I do that?
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
data = (0, 1890,865, 236, 6, 1, 2, 0 , 0, 0, 0 ,0 ,0 ,0, 0, 0)
ind = range(len(data))
width = 0.9 # the width of the bars: can also be len(x) sequence
p1 = plt.bar(ind, data, width)
plt.xlabel('Duration 2^x')
plt.ylabel('Count')
plt.title('DBFSwrite')
plt.axis([0, len(data), -1, max(data)])
ax = plt.gca()
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
plt.savefig('myfig')
See the very thin lines at x=0 and x=7-16? I'd like to eliminate those.
You can make use of numpy's arrays, and create a mask which you can use to filter out the indices where data has value 0.
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
data = np.array([0, 1890,865, 236, 6, 1, 2, 0 , 0, 0, 0 ,0 ,0 ,0, 0, 0])
ind = np.arange(len(data))
width = 0.9 # the width of the bars: can also be len(x) sequence
mask = data.nonzero()
p1 = plt.bar(ind[mask], data[mask], width)
plt.xlabel('Duration 2^x')
plt.ylabel('Count')
plt.title('DBFSwrite')
plt.axis([0, len(data), -1, max(data)])
ax = plt.gca()
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
plt.savefig('myfig')