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
Related
I have this simple code that generates an ellipse
import matplotlib.patches as patches
import matplotlib.pyplot as plt
fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'})
ellipse = patches.Ellipse((0, 0), 4, 2, angle=45, fill=False)
ax.add_artist(ellipse)
ax.set_xlim(-2.2, 2.2)
ax.set_ylim(-2.2, 2.2)
plt.show()
This is the current output:
ellipse
I need to add axis of ellipse so it would look like this:
ellipse_output
Is there a way to do that?
I need a generic way to use in more complex ellipses, thanks.
I tried to search for parameters in patches.Ellipse() to draw those axis lines, but didn't find anything.
You can add the major and minor axes of the ellipse.
In the code i show, i do the major axis, but you need to work on the angle part (based on the points of the elipse), whereas i just set it to 45 degrees to post a quick answer.
The result of this would give the complete solution.
So, I do something like this:
import matplotlib.patches as patches
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'})
#################################
# you need to figure this bit out
#################################
ellipse = patches.Ellipse((0, 0), 4, 2, angle=45, fill=False)
ax.add_artist(ellipse)
ellipse.set_clip_box(ax.bbox)
ellipse.set_alpha(0.1)
ax.annotate("",
xy=(ellipse.center[0], ellipse.center[1] - ellipse.height / 2),
xytext=(ellipse.center[0], ellipse.center[1] + ellipse.height / 2),
arrowprops=dict(arrowstyle="<->", color="black"))
ax.annotate("",
xy=(ellipse.center[0] - ellipse.width / 2, ellipse.center[1]),
xytext=(ellipse.center[0] + ellipse.width / 2, ellipse.center[1]),
arrowprops=dict(arrowstyle="<->", color="black"))
ax.annotate("",
xy=(ellipse.center[0] - ellipse.width / 2 * np.cos(np.deg2rad(ellipse.angle)),
ellipse.center[1] - ellipse.height / 2 * np.sin(np.deg2rad(ellipse.angle))),
xytext=(ellipse.center[0] + ellipse.width / 2 * np.cos(np.deg2rad(ellipse.angle)),
ellipse.center[1] + ellipse.height / 2 * np.sin(np.deg2rad(ellipse.angle))),
arrowprops=dict(arrowstyle="<->", color="black"))
ax.set_xlim(-2.2, 2.2)
ax.set_ylim(-2.2, 2.2)
plt.show()
Which leaves you with a plot like this:
Basically, in summary, the anotate lines let you do the final bits that you require.
EDIT:
I was able to reduce to this:
import matplotlib.patches as patches
import matplotlib.pyplot as plt
fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'})
# patches.Ellipse(center, width, height, angle)
ellipse = patches.Ellipse((0, 0), 4, 2, angle=45, fill=False)
ax.add_artist(ellipse)
ellipse.set_clip_box(ax.bbox)
ax.annotate("",
xy=(ellipse.center[0] - ellipse.width+2 ,
ellipse.center[1] - ellipse.height ),
xytext=(ellipse.center[0] + ellipse.width-1,
ellipse.center[1] + ellipse.height+1),
arrowprops=dict(arrowstyle="<->", color="red"))
ax.set_xlim(-2.2, 2.2;)
ax.set_ylim(-2.2, 2.2)
plt.show()
which looks like this:
from math import sin, cos, radians
import matplotlib.patches as patches
import matplotlib.pyplot as plt
fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'})
##############
ellipse_size = (4,2)
ellipse_rotation = 45
ellipse_position = (0,0)
ellipse = patches.Ellipse(ellipse_position, ellipse_size[0], ellipse_size[1], angle=ellipse_rotation, fill=False)
ax.add_artist(ellipse)
ax.set_xlim(-2.2, 2.2)
ax.set_ylim(-2.2, 2.2)
# math for the start and end axis point positions
ax1_points = [
(ellipse_position[0]+ellipse_size[0]/2*cos(radians(ellipse_rotation)),
ellipse_position[1]+ellipse_size[0]/2*sin(radians(ellipse_rotation))),
(ellipse_position[0]+ellipse_size[0]/2*cos(radians(ellipse_rotation + 180)),
ellipse_position[1]+ellipse_size[0]/2*sin(radians(ellipse_rotation + 180)))
]
ax2_points = [
(ellipse_position[0]+ellipse_size[1]/2*cos(radians(ellipse_rotation+90)),
ellipse_position[1]+ellipse_size[1]/2*sin(radians(ellipse_rotation+90))),
(ellipse_position[0]+ellipse_size[1]/2*cos(radians(ellipse_rotation + 270)),
ellipse_position[1]+ellipse_size[1]/2*sin(radians(ellipse_rotation + 270)))]
# ax1 and ax2 contains the start and the end point of the axis ([x,y] format)
# drawing the arrows
arrowprops=dict(arrowstyle="<->", color="red")
ax.annotate("", xy=ax1_points[0], xytext=ax1_points[1], arrowprops=arrowprops)
ax.annotate("", xy=ax2_points[0], xytext=ax2_points[1], arrowprops=arrowprops)
plt.show()
produces this:
Hope this helps.
How to plot this kind of thermal plot in Python? I tried to search for any sample plot like this but didn't find one.
This image I got from the internet. I want to plot something same like this:
FROM
TO
To represent this type of data the canonical solution is, of course, a heat map. Here it is the code to produce both the figures at the top of this post.
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 5, 501)
x = np.linspace(0, 1, 201)[:, None]
T = 50 + (30-6*t)*(4*x*(1-x)) + 4*t
fig, ax = plt.subplots(layout='constrained')
hm = ax.imshow(T, cmap='plasma',
aspect='auto', origin='lower', extent=(0, 5, 0, 1))
fig.colorbar(hm)
def heat_lines(x, t, T, n):
from matplotlib.cm import ScalarMappable
from matplotlib.collections import LineCollection
lx, lt = T.shape
ones = np.ones(lx)
norm = plt.Normalize(np.min(T), np.max(T))
plasma = plt.cm.plasma
fig, ax = plt.subplots(figsize=(1+1.2*n, 9), layout='constrained')
ax.set_xlim((-0.6, n-0.4))
ax.set_ylim((x[0], x[-1]))
ax.set_xticks(range(n))
ax.tick_params(right=False,top=False, bottom=False)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["bottom"].set_visible(False)
ax.grid(axis='y')
fig.colorbar(ScalarMappable(cmap=plasma, norm=norm))
dt = round(lt/(n-1))
for pos, ix in enumerate(range(0, len(t)+dt//2, dt)):
points = np.array([ones*pos, x[:,0]]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, linewidth=72, ec=None,
color=plasma(norm(T[:,ix])))
lc.set_array(T[:,ix])
ax.add_collection(lc)
heat_lines(x, t, T, 6)
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()
If you run the code or check out example plot, you'll see discontinuities between x = (3,4) and (7,8).
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
ranges = [(0, 3), (4, 7), (8, 10)]
block_nums = np.arange(1, 11)
times = np.random.rand(10)
_seg_vals = [(block_nums[start:end + 1], times[start:end + 1]) for start, end in ranges]
line_colors = 'blue', 'orange'
line_segments = [np.column_stack([x, y]) for x, y in _seg_vals]
plt.figure()
ax = plt.axes()
ax.add_collection(LineCollection(line_segments, colors=('blue','orange')))
ax.set_xlim(0, 10)
ax.set_ylim(0, 1)
plt.show()
I could hack together something to define segments between the discontinuities, but I'd prefer a more elegant solution. Is there some way to tell matplotlib to connect the segments at the integer boundaries?
It's doubtful matplotlib supports any way of drawing discontinuities but you can implement both of your LineCollections in a shorter way. assuming capacity of each discontinuity is 1, no domain intervals are required and the only thing you need is a list of discontinuity points:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
marker_idx = np.array([3, 7])
xy_vals = np.transpose([np.arange(1, 11), np.random.rand(10)])
line_segments = np.split(xy_vals, marker_idx)
discontinuity_idx = np.transpose([marker_idx-1, marker_idx]).flatten()
discontinuity_line_segments = np.split(xy_vals[discontinuity_idx], 2)
plt.figure()
ax = plt.axes()
ax.add_collection(LineCollection(line_segments, colors=('blue','orange')))
ax.add_collection(LineCollection(discontinuity_line_segments, colors=('lightgreen', 'lightgreen')))
ax.set_xlim(0, 10)
ax.set_ylim(0, 1)
plt.show()
I have a list of color values (in either of the formats: hex ('#ffffff') or rgb (255,255,255) if that helps). These colors correspond explicitly with the line segment between points. Currently I plot a line as a collection of line segments via:
import matplotlib.pyplot as plt
import itertools
colors = itertools.cycle('#ffffff', '#ffffff', '#ff0320', '#452143', ...)
t = (0, 1, 2, 3, ...)
var1 = (43, 15, 25, 9, ...)
ax = plt.subplot2grid((3,1), (0,0), colspan=3, rowspan=1)
ps = [(t,var1) for (t,var1) in zip(t, val)]
for start, end in zip(ps[:-1], ps[1:]):
t, var1 = zip(start, end)
c = next(colors)
ax.plot(t, var1, color=c)
However since I have a color for each point I would much prefer to set a cmap for the plot. How might I accomplish converting a list of colors into a cmap which I can use when plotting a line?
As tcaswell says, use a LineCollection for this:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
# a random walk
xy = np.cumsum(np.random.randn(1000, 2), axis=0)
z = np.linspace(0, 1, 1000)
lc = LineCollection(zip(xy[:-1], xy[1:]), array=z, cmap=plt.cm.hsv)
fig, ax = plt.subplots(1, 1)
ax.add_collection(lc)
ax.margins(0.1)
plt.show()