3D surface not transparent inspite of setting alpha - python

I am trying to create a 3D surface with transparency. When I try the following code below, I expect to get two semi-transparent faces of a cube. However, both the faces are opaque inspite of supplying the alpha=0.5 argument. Any pointer on why this is happening and how to fix it ? I am using Python 3.3 (IPython notebook with the QT backend)and Matplotlib 1.3.1.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d as mp3d
bot = [(0, 0, 0),
(1, 0, 0),
(1, 1, 0),
(0, 1, 0),
]
top = [(0, 0, 1),
(1, 0, 1),
(1, 1, 1),
(0, 1, 1),
]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
face1 = mp3d.art3d.Poly3DCollection([bot], alpha=0.5, linewidth=1)
face2 = mp3d.art3d.Poly3DCollection([top], alpha=0.5, linewidth=1)
ax.add_collection3d(face1)
ax.add_collection3d(face2)

Based on David Zwicker's input, I was able to get transparency working by setting the facecolor directly as a 4-tuple with alpha.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d as mp3d
bot = [(0, 0, 0),
(1, 0, 0),
(1, 1, 0),
(0, 1, 0),
]
top = [(0, 0, 1),
(1, 0, 1),
(1, 1, 1),
(0, 1, 1),
]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
face1 = mp3d.art3d.Poly3DCollection([bot], alpha=0.5, linewidth=1)
face2 = mp3d.art3d.Poly3DCollection([top], 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, 0, 1, alpha))
ax.add_collection3d(face1)
ax.add_collection3d(face2)

Related

How to cycle colors in Matplotlib PatchCollection?

I am trying to automatically give each Patch in a PatchCollection a color from a color map like tab20.
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(5,5))
coords = [
(0, 0),
(1, 2),
(1, 3),
(2, 2),
]
patches = [plt.Circle(coords[i], 0.1) for i in range(len(coords))]
patch_collection = PatchCollection(patches, cmap='tab20', match_original=True)
ax.add_collection(patch_collection)
ax.set_xlim(-1, 3)
ax.set_ylim(-1, 4)
plt.axis('equal')
But the above code is drawing each circle using the same color. How can the colors be cycled?
Here I've sampled the tab20 colormap, so that the RGBA array cmap.colors has exactly 20 different entries, then I've assigned this RGBA array to the keyword argument facecolors that every collection accepts.
Not just for cosmetics, I've added a colormap, so that it's possible to recognize the order in which the circles were drawn.
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
from numpy.random import rand, seed
seed(20230104)
N = 20
coords = rand(N,2)*[2,1.2]
cmap = plt.get_cmap('tab20', N)
fig, ax = plt.subplots()
patches = [plt.Circle(coord, 0.06) for coord in coords]
# use facecolors=...
collection = PatchCollection(patches, facecolors=cmap.colors[:N-1])
ax.add_collection(collection)
cb = plt.colorbar(plt.cm.ScalarMappable(plt.Normalize(-0.5, N-0.5), cmap))
cb.set_ticks(range(N), labels=('%02d'%(n+1) for n in range(N)))
ax.autoscale(collection)
ax.set_aspect(1)
Overdone Version
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
from numpy.random import rand, seed
seed(20230104)
N = 20
coords = rand(N, 2) * [2, 1.2]
cmap = plt.get_cmap("tab20", N)
patches = (plt.Circle(coord, 0.06) for coord in coords)
fig, ax = plt.subplots()
im = ax.add_collection(
PatchCollection(
patches,
facecolors=cmap.colors,
edgecolors="w",
linewidth=2,
cmap=cmap,
norm=plt.Normalize(-0.50, N - 0.50),
)
)
cb = plt.colorbar(
im,
location="bottom",
fraction=0.05,
aspect=50,
drawedges=True,
)
cb.set_ticks(range(N), labels=("%02d" % (n + 1) for n in range(N)))
cb.dividers.set_color(ax._facecolor)
cb.dividers.set_linewidth(3)
ax.autoscale()
ax.set_aspect(1)
This gives each patch its color from a fixed subset of colors in the selected colormap, repeating as necessary:
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
num_col = 3
cmap = plt.cm.tab20
fig, ax = plt.subplots(figsize=(5,5))
coords = [
(0, 0),
(1, 2),
(1, 3),
(2, 2),
]
patches = [plt.Circle(coords[i], 0.1) for i in range(len(coords))]
patch_collection = PatchCollection(patches, facecolor=cmap.colors[0:num_col])
ax.add_collection(patch_collection)
ax.set_xlim(-1, 3)
ax.set_ylim(-1, 4)
plt.axis('equal')
Output:
This gives a random color from the selected colormap by using numpy to generate a list of random numbers, then using the patch objects set_array method:
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(5,5))
coords = [
(0, 0),
(1, 2),
(1, 3),
(2, 2),
]
patches = [plt.Circle(coords[i], 0.1) for i in range(len(coords))]
color_vals = np.random.rand(len(patches))
patch_collection = PatchCollection(patches, cmap='tab20', match_original=True)
patch_collection.set_array(color_vals)
ax.add_collection(patch_collection)
ax.set_xlim(-1, 3)
ax.set_ylim(-1, 4)
plt.axis('equal')
Output:
I don't think match_original=True is necessary as you want to change the default color of the original patches. I'm sure there other ways of doing this as well. This SO post was helpful: setting color range in matplotlib patchcollection

Adding circle to a plot results in a 2nd scale on the axis and nothing being displayed on the plot

Once I attempt to add circles to the plot nothing displays on the plot.
Without the circle code, the x and y axis scale has major grid lines 2,3,4,5,6,7,8, and the data displays.
Once I add the circle code it appears the 2nd scale of 0 to 1.0 is added to both axis.
I believe this is why the plot is blank. No data has a value between 0 and 1.
Not sure why the 2nd axis scale is being added.
import matplotlib.pyplot as plt
import matplotlib.path
from matplotlib.patches import Circle
sample = (5, 5)
circles = [(2, 8), (4, 6), (5, 7)]
squares = [(4, 2), (6, 2), (6, 4)]
plt.figure("Concept", (5, 5))
plt.set_cmap('gray')
# plt.axis((0, 10, 0, 10), option='equal')
plt.axis('equal')
plt.scatter(*sample, marker="D", label="??", color='0.0')
plt.scatter([x for x, y in circles], [y for x, y in circles], marker="o", color='.20')
plt.scatter([x for x, y in squares], [y for x, y in squares], marker="s", color='.33')
# k = 3 nearest neighbors
circle3 = Circle((5, 5), 2, facecolor='none',
edgecolor='black', linestyle='--', alpha=0.8)
plt.axes().add_patch(circle3)
# k = 5 nearest neighbors
circle5 = Circle((5, 5), 3.2, facecolor='none',
edgecolor='black', linestyle=':', alpha=1.0)
plt.axes().add_patch(circle5)
plt.grid(True)
plt.show()

How to use set_clip_path() with multiple polygons?

I'm trying to clip a cloud of points by several polygons, but I don't know if this is possible with plt.axis.set_clip_path().
Since set_clip_path() requires a Path or a Patch as arguments, how could you create a geometry formed by several Polygons? It would be something like a plt.MultiPolygon(), but that doesn't exist. I've tried to create a matplotlib.PatchCollection with all the Polygons, but that does not work.
Here is the desired goal (from upper to lower figure):
Here is how I'd like the code to look like:
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import numpy as np
fig, ax = plt.subplots()
points = np.array([np.random.random(100)*400,
np.random.random(100)*100]).T
A = plt.Polygon( np.array([( 0, 0),(50,100),(100, 0)]), color='w', ec='k' )
B = plt.Polygon( np.array([(120 , 0),(170 , 100), (220, 0)]), color='w', ec='k' )
C = plt.Polygon( np.array([(240 , 0),(290 , 100), (340, 0)]), color='w', ec='k' )
[ax.add_patch(i) for i in (A,B,C)]
ax.scatter(points[:,0], points[:,1], zorder=3).set_clip_path([A,B,C])
You can concatenate the vertices and the codes of all polygons, and use them to create a "compound path". Matplotlib's path tutorial contains an example creating a histogram from just one compound path.
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
import numpy as np
points = np.array([np.random.random(100) * 400,
np.random.random(100) * 100]).T
A = plt.Polygon(np.array([(0, 0), (50, 100), (100, 0)]), color='w', ec='k')
B = plt.Polygon(np.array([(120, 0), (170, 100), (220, 0)]), color='w', ec='k')
C = plt.Polygon(np.array([(240, 0), (290, 100), (340, 0)]), color='w', ec='k')
fig, ax = plt.subplots()
all_polys = [A, B, C]
[ax.add_patch(i) for i in all_polys]
vertices = np.concatenate([i.get_path().vertices for i in all_polys])
codes = np.concatenate([i.get_path().codes for i in all_polys])
dots = ax.scatter(points[:, 0], points[:, 1], zorder=3)
dots.set_clip_path(PathPatch(Path(vertices, codes), transform=ax.transData))
plt.show()

Matplotlib: Specify format of bin values in a histogram's tick labels

I am customizig the histogram of a dataset by specifying its exact bins and I would like to know how to set the format of the x-tick labels to 2 decimal places, which is especially usefull when working with subplots.
The following code works well when the intervals' values have a few decimal places :
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
x = np.random.randn(1000)
intervals = [(-4, -3.5), (-3.5, -3), (-3, -2.5), (-2.5, -2), (-2, -1.5), (-1.5, -1), (-1, -0.5), (-0.5, 0), (0, 0.5), (0.5, 1), (1, 1.5), (1.5, 2.5), (2.5, 3), (3, 3.5), (3.5, 4)]
bins = pd.IntervalIndex.from_tuples(intervals)
histogram = pd.cut(x, bins).value_counts().sort_index()
fig = plt.figure(figsize = (16,8))
plt.subplot(2, 1, 1)
histogram.plot(kind='bar')
plt.title('First subplot')
plt.xlabel('Value')
plt.ylabel('Realisations')
plt.subplot(2, 1, 2)
histogram.plot(kind='bar')
plt.title('Second subplot')
plt.xlabel('Value')
plt.ylabel('Realisations')
plt.show()
But when they have loads of decimal places, this becomes :
Setting a higher figure height using figsize=(16,15) for example is a possible workaround but doesn't solve the problem. Is there an elegant way to set the displayed number of decimal places in the bins ?
You'll have to format the interval index yourself and then set the labels:
xtl = [f'({l:.2f}, {r:.2f}]' for l,r in zip(bins.values.left, bins.values.right)]
plt.gca().set_xticklabels(xtl)
Example:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
x = np.random.randn(1000)
bins = pd.IntervalIndex.from_breaks(np.linspace(-4.1234567, 4.1234567, 10))
histogram = pd.cut(x, bins).value_counts().sort_index()
xtl = [f'({l:.2f}, {r:.2f}]' for l,r in zip(bins.values.left, bins.values.right)]
fig = plt.figure(figsize = (16,8))
plt.subplot(2, 1, 1)
histogram.plot(kind='bar')
plt.title('First subplot')
plt.xlabel('Value')
plt.ylabel('Realisations')
plt.gca().set_xticklabels(xtl)
plt.subplot(2, 1, 2)
histogram.plot(kind='bar')
plt.title('Second subplot')
plt.xlabel('Value')
plt.ylabel('Realisations')
plt.gca().set_xticklabels(xtl)
plt.show()

Changing a Matplotlib Rectangle to Circle in a Legend

Consider the following plotting code:
plt.figure(figsize=(10,6))
for k in range(nruns):
plt.plot(Thing1['Data'][:,k],color='Grey',alpha=0.10)
plt.plot(Thing2[:,1],Thing2[:,4],'ko')
a = plt.Rectangle((0, 0), 1, 1, fc="Grey",alpha=0.50)
b = plt.Rectangle((0, 0), 1, 1, fc="Black", alpha=1.00)
plt.legend([a,b], ["Thing1","Thing2"],loc=2,fontsize='small')
plt.xlabel("Time",fontsize=16)
plt.ylabel("Hijinks",fontsize=16)
plt.show()
I'd really like "b" to be a circle, rather than a rectangle. But I'm rather horrid at matplotlib code, and especially the use of proxy artists. Any chance there's a straightforward way to do this?
You're very close. You just need to use a Line2D artist and set its properties like ususal:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10,6))
fakexy = (0, 0)
a = plt.Rectangle(fakexy, 1, 1, fc="Grey",alpha=0.50)
b = plt.Line2D(fakexy, fakexy, linestyle='none', marker='o', markerfacecolor="Black", alpha=1.00)
ax.legend([a, b], ["Thing1", "Thing2"], loc='upper left', fontsize='small')
ax.set_xlabel("Time", fontsize=16)
ax.set_ylabel("Hijinks", fontsize=16)
I get:
There is a much easier way to do this with newer versions of matplotlib.
from pylab import *
p1 = Rectangle((0, 0), 1, 1, fc="r")
p2 = Circle((0, 0), fc="b")
p3 = plot([10,20],'g--')
legend([p1,p2,p3], ["Red Rectangle","Blue Circle","Green-dash"])
show()
Note this is not my work. This is obtained from Matplotlib, legend with multiple different markers with one label.

Categories