Transparency for Poly3DCollection plot in matplotlib - python
I am trying to draw some objects with the fabulous Matplotlib package for Python. These objects consist of points implemented with plt.scatter() and patches implemented with Poly3DCollection. I would like to have the patches with a slight transparency so that the points and edges behind the patches can be seen.
Here the code and plot I already generated. Seems I am almost there, just missing the feature of transparency. Interestingly, if I first plot the Ploy3DCollection and afterwards the scatter points, the points can be seen, but not the edges.
Anyone having a suggestion for me?
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = list(zip(x, y, z))
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
ax.add_collection3d(Poly3DCollection(poly3d, facecolors='w', linewidths=1, alpha=0.5))
plt.show()
I made a slight modification to the OP code and got the transparency working. It appears that the facecolors argument of Poly3DCollection overrides the transparency argument, so the solution was to set the color in a separate call to either Poly3DCollection.set_color or Poly3DCollection.set_facecolor:
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = zip(x, y, z)
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
collection = Poly3DCollection(poly3d, linewidths=1, alpha=0.2)
face_color = [0.5, 0.5, 1] # alternative: matplotlib.colors.rgb2hex([0.5, 0.5, 1])
collection.set_facecolor(face_color)
ax.add_collection3d(collection)
plt.show()
Interestingly, if you explicitly set the edge color with collection.set_edgecolor('k'), the edges will also honor the transparency setting.
I found a nice workaround: After plotting the data, do another plot on top with the same color and lighter line style. Instead of Poly3DCollection I use Line3DCollection, so no faces are plotted. The result looks very much as anticipated.
See below the new plot and the script creating it.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = list(zip(x, y, z))
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
ax.add_collection3d(Poly3DCollection(poly3d, facecolors='w', linewidths=1, alpha=0.5))
ax.add_collection3d(Line3DCollection(poly3d, colors='k', linewidths=0.2, linestyles=':'))
plt.show()
Thanks a lot Chilichiller and Julian. Your examples are very useful to me at present, because I am working on a little project about 3D representation of matrices with matplotlib, and Poly3DCollection seems suitable for the task.
A little note, that maybe can be useful to future readers.
Running your examples in Python 3 gives TypeError: 'zip' object is not subscriptable.
The simplest solution is to wrap the return value of zip in a call to list() (as indicated by "Dive Into Python 3": http://www.diveintopython3.net/porting-code-to-python-3-with-2to3.html).
Here is a version that uses only one call to Poly3DCollection, where edgecolors='k' controls the color of the line and facecolors='w' controls the color of the faces. Note how Matplotlib colors the edges behind the polygon in a lighter gray color.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = list(zip(x, y, z))
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
ax.add_collection3d(Poly3DCollection(poly3d, edgecolors='k', facecolors='w', linewidths=1, alpha=0.5))
plt.show()
Caution The API for Poly3DCollection is rather confusing: the accepted keyword arguments are all of colors, edgecolor, edgecolors, facecolor, and facecolors (using aliases and decorators to define multiple kwargs to mean the same thing, where the "s" is optional for facecolor and edgecolor).
This bug has been fixed in the new matplotlib. I'm running version 1.5.1.
You can see your version by running python, then doing:
import matplotlib
matplotlib.__version__
You can get matplotlib using pip. If you're on Ubuntu, run this from a terminal:
sudo apt-get install python-pip
sudo pip install matplotlib
Related
How do I create a Rubix Cube in python using Matplotlib?
I have been trying to make a Rubix Cube in python using Matplotlib. I've struggled to get each face of the cube to have its own colour? How do I do this in Matplotlib? import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import numpy as np axes = [3, 3, 3] data = np.ones(axes) alpha = 0.9 colours = np.empty(axes + [4]) colours[0] = [1, 0, 0, alpha] # red colours[1] = [0, 0, 1, alpha] # blue colours[2] = [1, 1, 0, alpha] # yellow figure = plt.figure() ax = figure.add_subplot(111, projection = '3d') ax.voxels(data, facecolors = colours, edgecolors = 'grey') plt.show()
How to determine which cubes the line passes through
I was looking for a way to build cubes of the same size, then draw a line through this space and output the result in the form of coordinates of cubes that this line intersects and paint these cubes with a different color. The line can be either straight or curved. I used matplotlib to plot cubes and lines. From these sources: https://www.geeksforgeeks.org/how-to-draw-3d-cube-using-matplotlib-in-python/ Representing voxels with matplotlib Example code: from mpl_toolkits.mplot3d import Axes3D import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d.art3d import Poly3DCollection axes = [5, 5, 5] # Create Data data = np.ones(axes, dtype=np.bool) # Controll Tranperency alpha = 0.3 # Control colour colors = np.empty(axes + [4], dtype=np.float32) colors[0] = [1, 0, 0, alpha] # red colors[1] = [0, 1, 0, alpha] # green colors[2] = [0, 0, 1, alpha] # blue colors[3] = [1, 1, 0, alpha] # yellow colors[4] = [1, 1, 1, alpha] # grey # Plot figure fig = plt.figure() ax = fig.add_subplot(111, projection='3d') x1 = [1, 4] y1 = [0, 5] z1 = [0, 5] ax.plot3D(x1, y1, z1, 'black', linewidth = 5) # Voxels is used to customizations of # the sizes, positions and colors. ax.voxels(data, facecolors=colors, edgecolors='grey') result In short: I need to plot a grid of cubes and draw a line through it. After determine which cubes this line intersects. Is it possible to do this in Matplotlib or do I need to use another library to solve my question?
God, why do I put myself though this. Anyways, here is an iterative solution because I do not feel like doing linear algebra. I tried and I failed. # Here be dragons def linelamb(x,y,z): return lambda s: [int(i) for i in [x[0]+s*(x[1]-x[0]), y[0]+s*(y[1]-y[0]), z[0]+s*(z[1]-z[0])]] line = linelamb(x1,y1,z1) hitboxes = np.zeros(axes) x,y,z = 0,0,0 for r in [i for i in np.arange(0,1,0.001)]: xnew,ynew,znew = line(r) if not (x == xnew and y == ynew and z == znew): hitboxes[xnew,ynew,znew] = 1 x,y,z = xnew,ynew,znew ax.voxels(hitboxes, facecolors=[0,0,0,0.5], edgecolors='black'); I spent some extra time to make this more adaptable but my brain stopped working. You might want to adaptively change the step size of the range but good luck.
Background colors matplotlib
I made this picture with matplotlib. I would like to split the background in two slightly colorfull side with two legends in each of them "mu < mu_{0}" for the left and "\mu > \mu_{0}$ for the right. Do you know how to do that ? THanks and regards.
You can use plt.fill to specify the area of the graph to shade. You can also used plt.text to annotate the sections. Here's an example of this for a graph not too dissimilar to yours (symmetric around the y-axis and bounded above by 1 and below by 0): import numpy as np import matplotlib.pyplot as plt x = np.linspace(-np.pi, np.pi, num=100) fig, ax = plt.subplots(1, 1) ax.plot(x, np.abs(np.sin(x))) # Get the left and right extent of the area to shade LHS, RHS = ax.get_xlim() # Specify the area to shade as the corners of the square we're interested in ax.fill([0, RHS, RHS, 0], [0, 0, 1, 1], c='C1', alpha=0.3) ax.fill([0, LHS, LHS, 0], [0, 0, 1, 1], c='C2', alpha=0.3) ax.text(-np.pi/2, 0.4, '$x < 0$') ax.text(np.pi/2, 0.4, '$x > 0$')
Plotting randomly stacked cubes in 3D- mplot3d?
I am exploring random stackings of cubes. I started with 2D and can generate random packings within a rectangle like this: Now I have the code to generalize the stacking to 3D, but I am struggling to generalize the visualization. An example data set is, filling a 3x3x3 cube with 1x1x1 and 2x2x2 cubes, #the coordinates of a corner vertex of the 19 1x1x1 cubes x1 = [1, 0, 2, 0, 0, 0, 2, 1, 0, 1, 2, 2, 0, 0, 0, 2, 0, 1, 1] y1 = [1, 1, 0, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 2, 1, 0, 0, 0, 0] z1 = [2, 1, 1, 0, 1, 2, 2, 2, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2] #the coordinates of a corner vertex of the 1 2x2x2 cube x2 = [1] y2 = [1] z2 = [0] # I believe the random filling is working because # the total volumes equal: 19 + 2**3 = 3**3 #I would like to start with the lists X = [x1,x2] Y = [y1,y2] Z = [z1,z2] sizes = [1,2] #because I want to generalize the visualization to n sizes So far, all I have the knowledge to do is plot a 3D scatter of the data from matplotlib import pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') for ii in range(len(sizes)): ax.scatter(X[ii],Y[ii],Z[ii]) plt.show() I would like to make a plot more like this, except with variable sizes. Any help would be greatly appreciated! I have a lot to learn about matplotlib/pyplot and so on. I have made a little bit of progress: import matplotlib.pyplot as plt from matplotlib.patches import Rectangle, PathPatch from mpl_toolkits.mplot3d import Axes3D import mpl_toolkits.mplot3d.art3d as art3d def cube(a,b,c,l): for zz in [c,c+l]: for i in ["x","y","z"]: side = Rectangle((a, b), l,l) ax.add_patch(side) art3d.pathpatch_2d_to_3d(side, z=zz, zdir=i) fig = plt.figure() ax=fig.gca(projection='3d') cube(0,0,0,1) ax.set_xlim3d(-2, 2) ax.set_ylim3d(-2, 2) ax.set_zlim3d(-2, 2) plt.show() This plots a single cube. EDIT: More progress, I am now very close import matplotlib.pyplot as plt from matplotlib.patches import Rectangle, PathPatch from mpl_toolkits.mplot3d import Axes3D import mpl_toolkits.mplot3d.art3d as art3d cmap = plt.get_cmap('spring') #define the colors of the plot colors = [cmap(i) for i in np.linspace(0.1, 0.9, n+1)] def cube(a,b,c,l): #plots a cube of side l at (a,b,c) for ll in [0,l]: for i in range(3): dire= ["x","y","z"] xdire = [b,a,a] ydire = [c,c,b] zdire = [a,b,c] side = Rectangle((xdire[i], ydire[i]),facecolors[np.where(sizes == l)[0]],edgecolor='black') ax.add_patch(side) art3d.pathpatch_2d_to_3d(side, z=zdire[i]+ll, zdir=dire[i]) def plotter3D(X,Y,Z,sizes): #run cube(a,b,c,l) over the whole data set for iX in range(len(X)): x = X[iX] y = Y[iX] z = Z[iX] for ix in range(len(x)): cube(x[ix],y[ix],z[ix],sizes[iX]) fig = plt.figure() #open a figure ax=fig.gca(projection='3d') #make it 3d plotter3D(X,Y,Z,sizes) #generate the cubes from the data set ax.set_xlim3d(0, length) #set the plot ranges ax.set_ylim3d(0, width) ax.set_zlim3d(0, height) plt.show() This generates the desired output, although it seems to be see-through in some places when viewed from certain angles. You can see this in the small cube-- dead center at coordinates (1.5,2,3) Any idea how to fix this? Another edit: The solution outined above has two problems: (1) I can't get equal aspect ratios for the three axes, and (2) The cubes are see-through from certain angles. Here's what the output looks like for a larger system
Suggestions to plot overlapping lines in matplotlib?
Does anybody have a suggestion on what's the best way to present overlapping lines on a plot? I have a lot of them, and I had the idea of having full lines of different colors where they don't overlap, and having dashed lines where they do overlap so that all colors are visible and overlapping colors are seen. But still, how do I that.
I have the same issue on a plot with a high degree of discretization. Here the starting situation: import matplotlib.pyplot as plt grid=[x for x in range(10)] graphs=[ [1,1,1,4,4,4,3,5,6,0], [1,1,1,5,5,5,3,5,6,0], [1,1,1,0,0,3,3,2,4,0], [1,2,4,4,3,2,3,2,4,0], [1,2,3,3,4,4,3,2,6,0], [1,1,3,3,0,3,3,5,4,3], ] for gg,graph in enumerate(graphs): plt.plot(grid,graph,label='g'+str(gg)) plt.legend(loc=3,bbox_to_anchor=(1,0)) plt.show() No one can say where the green and blue lines run exactly and my "solution" import matplotlib.pyplot as plt grid=[x for x in range(10)] graphs=[ [1,1,1,4,4,4,3,5,6,0], [1,1,1,5,5,5,3,5,6,0], [1,1,1,0,0,3,3,2,4,0], [1,2,4,4,3,2,3,2,4,0], [1,2,3,3,4,4,3,2,6,0], [1,1,3,3,0,3,3,5,4,3], ] for gg,graph in enumerate(graphs): lw=10-8*gg/len(graphs) ls=['-','--','-.',':'][gg%4] plt.plot(grid,graph,label='g'+str(gg), linestyle=ls, linewidth=lw) plt.legend(loc=3,bbox_to_anchor=(1,0)) plt.show() I am grateful for suggestions on improvement!
Just decrease the opacity of the lines so that they are see-through. You can achieve that using the alpha variable. Example: plt.plot(x, y, alpha=0.7) Where alpha ranging from 0-1, with 0 being invisible.
imagine your panda data frame is called respone_times, then you can use alpha to set different opacity for your graphs. Check the picture before and after using alpha. plt.figure(figsize=(15, 7)) plt.plot(respone_times,alpha=0.5) plt.title('a sample title') plt.grid(True) plt.show()
Depending on your data and use case, it might be OK to add a bit of random jitter to artificially separate the lines. from numpy.random import default_rng import pandas as pd rng = default_rng() def jitter_df(df: pd.DataFrame, std_ratio: float) -> pd.DataFrame: """ Add jitter to a DataFrame. Adds normal distributed jitter with mean 0 to each of the DataFrame's columns. The jitter's std is the column's std times `std_ratio`. Returns the jittered DataFrame. """ std = df.std().values * std_ratio jitter = pd.DataFrame( std * rng.standard_normal(df.shape), index=df.index, columns=df.columns, ) return df + jitter Here's a plot of the original data from Markus Dutschke's example: And here's the jittered version, with std_ratio set to 0.1:
Replacing solid lines by dots or dashes works too g = sns.FacetGrid(data, col='config', row='outputs', sharex=False) g.map_dataframe(sns.lineplot, x='lag',y='correlation',hue='card', linestyle='dotted')
Instead of random jitter, the lines can be offset just a little bit, creating a layered appearance: import matplotlib.pyplot as plt from matplotlib.transforms import offset_copy grid = list(range(10)) graphs = [[1, 1, 1, 4, 4, 4, 3, 5, 6, 0], [1, 1, 1, 5, 5, 5, 3, 5, 6, 0], [1, 1, 1, 0, 0, 3, 3, 2, 4, 0], [1, 2, 4, 4, 3, 2, 3, 2, 4, 0], [1, 2, 3, 3, 4, 4, 3, 2, 6, 0], [1, 1, 3, 3, 0, 3, 3, 5, 4, 3]] fig, ax = plt.subplots() lw = 1 for gg, graph in enumerate(graphs): trans_offset = offset_copy(ax.transData, fig=fig, x=lw * gg, y=lw * gg, units='dots') ax.plot(grid, graph, lw=lw, transform=trans_offset, label='g' + str(gg)) ax.legend(loc='upper left', bbox_to_anchor=(1.01, 1.01)) # manually set the axes limits, because the transform doesn't set them automatically ax.set_xlim(grid[0] - .5, grid[-1] + .5) ax.set_ylim(min([min(g) for g in graphs]) - .5, max([max(g) for g in graphs]) + .5) plt.tight_layout() plt.show()