Cut parts of plotted artists - matplotlib

Assume I have drawn some background image like this:
fig, ax = plt.subplots()
imdata = np.random.randn(10, 10)
im = ax.imshow(imdata, extent=(0, 1, 0, 1), aspect='auto',
cmap='coolwarm', interpolation='nearest')
Now I'm adding a number of rectangles like:
rect = matplotlib.patches.Rectangle((0.3,0.3),0.4,0.4)
ax.add_artist(rect)
Now I want to cut several other rectangles out of the previously added rectangle, so the underlying image is shown again. By cut, I really mean that specifying such a "deletion rectangle" will cut out parts from the previously drawn rectangles. So if they overlap, only the overlapping parts will be cut away. Where the "deletion rectangles" do not intersect space occupied by the rectangles above, nothing shall happen to the visible area.
How can I achieve that?

You can use a path to construct the rectangles. To position the rectangles the vertices of the path can be translated and transformed. Then, using the fact that inverted vertices will be cut out of a path, one create holes in the outer rectangle.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch, Rectangle
fig, ax = plt.subplots()
imdata = np.random.randn(10, 10)
# create rectangle, coordinates are ignored
rec = Rectangle((0,0),1,1).get_path()
#the big rectangle
r0 = rec.vertices+0.5
# r1 and r2 are the rectangles to cut out of r0
r1 = 0.6+rec.vertices[::-1]*0.35
r2 = 1+rec.vertices[::-1]*0.35
path = Path(vertices=np.concatenate([r0, r1, r2]),
codes=np.concatenate([rec.codes]*3))
im = ax.imshow(imdata, extent=(0, 2, 0, 2), aspect='equal',
cmap='coolwarm', interpolation='nearest')
patch = PathPatch(path, facecolor='w')
ax.add_patch(patch)
plt.tight_layout()
plt.show()
Or, a solution which makes it easier to specify the coordinates of the rectangles:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch, Rectangle
fig, ax = plt.subplots()
imdata = np.random.randn(10, 10)
def create_rec(x0, y0, width, height):
rec_patch = Rectangle((x0, y0),width, height)
rec_path = rec_patch.get_path()
rec_path = rec_patch.get_patch_transform().transform_path(rec_path)
return rec_path.vertices, rec_path.codes
#the big rectangle
r0,c = create_rec(0.3, 0.6, 1, 1.2)
# r1 and r2 are the rectangles to cut out of r0
r1,c = create_rec(0.4, 0.7, 0.3, 0.4)
r2,c = create_rec(0.8, 1, 0.4, 0.5)
path = Path(vertices=np.concatenate([r0, r1[::-1], r2[::-1]]),
codes=np.concatenate([c]*3))
im = ax.imshow(imdata, extent=(0, 2, 0, 2), aspect='equal',
cmap='coolwarm', interpolation='nearest')
patch = PathPatch(path, facecolor='w')
ax.add_patch(patch)
plt.tight_layout()
plt.show()
To account for the case where the rectangle is partially outside the original rectangle, the following (based on the second solution) might help:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch, Rectangle
fig, ax = plt.subplots()
imdata = np.random.randn(10, 10)
def create_rec(x0, y0, width, height):
rec_patch = Rectangle((x0, y0),width, height)
rec_path = rec_patch.get_path()
rec_path = rec_patch.get_patch_transform().transform_path(rec_path)
return rec_path.vertices, rec_path.codes
#the big rectangle
r0,c = create_rec(0.3, 0.6, 1, 1.2)
# r1 and r2 are the rectangles to cut out of r0
r1,c = create_rec(0.2, 0.5, 0.3, 0.4)
r2,c = create_rec(0.8, 1, 0.4, 0.5)
path = Path(vertices=np.concatenate([r0, r1[::-1], r2[::-1]]),
codes=np.concatenate([c]*3))
im = ax.imshow(imdata, extent=(0, 2, 0, 2), aspect='equal',
cmap='coolwarm', interpolation='nearest')
patho = Path(vertices=r0,codes=c)
patcho = PathPatch(patho, facecolor='none', edgecolor="none")
ax.add_patch(patcho)
patch = PathPatch(path, facecolor='w', clip_path=patcho, edgecolor="none")
ax.add_patch(patch)
plt.show()

Related

adjust the location of color bar in subplots containing color and line plots

I am new to python programming. I was trying to make two subplots using matplotlib containing a line plot (panel-a) and 2-D color plot using imshow() (panel-b). I want the colorbar to be shown on the right side with same size as the color plot and it should not be within the subplot box limit.
`
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# Panel (a)
x1 = np.linspace(2, -2, 5)
y1 = np.linspace(-2, 2, 5)
# Panel (b)
N = 10
arr = np.random.random((N, N))
x_lims = list(map(dt.datetime.fromtimestamp, [982376726, 982377321]))
x_lims = mdates.date2num(x_lims)
y_lims = [0, 40]
fig, ax = plt.subplots(2, 1, figsize=(14, 10))
ax[0].plot(x1, y1)
ax[0].set_ylim(-2, 2)
ax[0].set_xlim(2, -2)
ax[0].set_xticks([2, 1, 0, -1, -2])
ax[0].set_yticks([-2, -1, 0, 1, 2])
im = ax[1].imshow(arr, extent=[x_lims[0], x_lims[1], y_lims[0],
y_lims[1]],
aspect='auto')
divider = make_axes_locatable(ax[1])
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(im, cax=cax, label="diff. en. flux")
ax[1].xaxis_date()
date_format = mdates.DateFormatter('%H:%M:%S')
ax[1].xaxis.set_major_formatter(date_format)

multi animation whit subplot

I got some sort of a problem with a pendulum animation, I tried to display my animation (the pendulum's movement) next to a graph in two separate axes, but when I try my code, it barely works displaying two axes that overlap on one another... Here is what I tried:
PS: best would be that the circles I was intended to add at the end of my pendulum appear on the final animation, but I really have no idea how to put them only on a particular ax
from numpy import sin, cos, pi, array
import numpy as np
import scipy.integrate
import matplotlib.pyplot as plt
import matplotlib.animation as animation
g = 10
y0 = np.array([np.pi / 2.0, 0]) # angle, vitesse
j = 0.2
def f(y, t):
return np.array([y[1], -g * np.sin(y[0])-j*y[1]])
t = np.linspace(0, 100, 10000)
y = scipy.integrate.odeint(f, y0, t)
theta, thetadot = y[:, 0], y[:, 1]
fig, axs = plt.subplots(1,2)
axs[0] = fig.add_subplot(xlim=(-1.5, 1.5), ylim=(-1.5, 1.5))
axs[0].grid()
axs[0].set_box_aspect(1)
# anchor = plt.Circle((0, 0), 0.01, color='black')
# mass = plt.Circle((sin(y0[0]),-cos(y0[0])), 0.2, color='black')
pendulums = axs[0].plot((0, sin(y0[0])), (0, -cos(y0[0])), 'o-', color = 'black')
# plt.gca().add_patch(weight) # adding circles
# plt.gca().add_patch(attach)
phase = axs[1].plot(theta,thetadot)
def animate(i):
angle = theta[i]
x = (0, sin(angle))
y = (0, -cos(angle))
#mass.center = (x[1],y[1])
pendulums[0].set_data(x, y)
anim = animation.FuncAnimation(fig, animate, interval=10)
plt.show()

How to show ranges of values with a color assigned in the legend?

With this code i'm creating colorbar scales with the function make_colormap. Source:Create own colormap using matplotlib and plot color scale
import matplotlib.colors as mcolors
def make_colormap(seq):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples. The floats should be increasing
and in the interval (0,1).
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return mcolors.LinearSegmentedColormap('CustomMap', cdict)
c = mcolors.ColorConverter().to_rgb
rvb = make_colormap([c('grey'), c('grey'), norm(3), c('sandybrown'), c('sandybrown'),
norm(5), c('yellow'), c('yellow'), norm(10), c('navajowhite'),
c('navajowhite'), norm(15),c('lightgreen'), c('lightgreen'),norm(20),c('lime'), c('lime'),
norm(50),c('limegreen'), c('limegreen'),norm(80),c('forestgreen'), c('forestgreen'),norm(120),
c('green'), c('green'),norm(160),c('darkgreen'), c('darkgreen'),norm(200),c('teal'), c('teal'),norm(300),
c('mediumaquamarine'), c('mediumaquamarine'),norm(500),c('lightseagreen'), c('lightseagreen'),norm(700),
c('lightskyblue'), c('lightskyblue')])
So in variable rvb i'm asssing a color to ranges of values. How can i assing a color to an specific ranges of values? For example: Grey to 0-3, sandybrown to 4-5, yellow to 6-10, etc.
The map is this:
Also i want to the legend show those values assigned. For example Grey color 0-3, sandybrown 4-5, etc.
Something similar to this image (no need to be equal to the image, just need to show ranges with colors):
I also will show you part of my code when i create the map:
fig = plt.figure('map', figsize=(7,7), dpi=200)
ax = fig.add_axes([0.1, 0.12, 0.80, 0.75], projection=ccrs.PlateCarree())
plt.title('xxx')
plt.xlabel('LONGITUD')
plt.ylabel('LATITUD')
ax.outline_patch.set_linewidth(0.3)
l = NaturalEarthFeature(category='cultural', name='admin_0_countries', scale='50m', facecolor='none')
ax.add_feature(l, edgecolor='black', linewidth=0.25)
img = ax.scatter(lons, lats, s=7, c=ppvalues, cmap=rvb,norm=norm,
marker='o', transform=ccrs.PlateCarree())
handles, labels = img.legend_elements(alpha=0.2)
plt.legend(handles, labels,prop={'weight':'bold','size':10}, title='Meteorological\nStations',title_fontsize=9, scatterpoints=2);
cb = plt.colorbar(img, extend='both',
spacing='proportional', orientation='horizontal',
cax=fig.add_axes([0.12, 0.12, 0.76, 0.02]))
ax.set_extent([-90.0, -60.0, -20.0, 0.0], crs=ccrs.PlateCarree())
I don't understand the function in the question, but I have coded how to create a legend with a specified color, specified label, and specified ticks, and how to give a color bar a specified tick. Please correct the addition of colors and the tick spacing in the color bar.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.colors import LinearSegmentedColormap
list_color = ['grey','sandybrown','sandybrown','yellow',
'navajowhite','lightgreen','lime','limegreen',
'forestgreen','green','darkgreen','teal',
'mediumaquamarine','lightseagreen','lightskyblue']
list_label = ['0-3', '4-5', '6-10', '11-15',
'16-20', '21-50', '51-80', '81-120',
'121-160', '161-200','201-300','301-500',
'501-700','701-900','901-1200']
list_ticks = np.linspace(0, 1, 15)
vmin,vmax = 0, 1
cm = LinearSegmentedColormap.from_list('custom_cmap', list_color, N=len(list_color))
plt.imshow(np.linspace(0, 1, 25).reshape(5,5), cmap=cm, interpolation='nearest', vmin=vmin, vmax=vmax)
cbar = plt.colorbar( orientation='horizontal', extend='neither', ticks=list_ticks)
cbar.ax.set_xticklabels(list_label, rotation=45, fontsize=14)
all_patches = []
for h,l in zip(list_color, list_label):
patch = mpatches.Patch(color=h, label=l)
all_patches.append(patch)
plt.legend(handles=all_patches, loc='upper right', ncol=3, bbox_to_anchor=(3, 1))
plt.show()

Update Matplotlib 3D Graph Based on Real Time User Input

I am trying to get matplotlib to create a dynamic 3d graph based on user input - but I can't get the graph to update. If I use the exact same code but without the "projection='3d'" setting, the program works correctly - but as soon as the graph is changed to display in 3d - it doesn't work.
Any help would be greatly appreciated.
3D Graph Code (graph doesn't update)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
plt.subplots_adjust(left=0.25, bottom=0.25)
x = np.arange(0.0, 1.0, 0.1)
a0 = 5
b0 = 1
y = a0 * x + b0
z = np.zeros(10)
l, = plt.plot(x, y, z)
# Set size of Axes
plt.axis([0, 1, -10, 10])
# Place Sliders on Graph
ax_a = plt.axes([0.25, 0.1, 0.65, 0.03])
ax_b = plt.axes([0.25, 0.15, 0.65, 0.03])
# Create Sliders & Determine Range
sa = Slider(ax_a, 'a', 0, 10.0, valinit=a0)
sb = Slider(ax_b, 'b', 0, 10.0, valinit=b0)
def update(val):
a = sa.val
b = sb.val
l.set_ydata(a*x+b)
fig.canvas.draw_idle()
sa.on_changed(update)
sb.on_changed(update)
plt.show()
2D Graph Code (graph updates properly)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111)
plt.subplots_adjust(left=0.25, bottom=0.25)
x = np.arange(0.0, 1.0, 0.1)
a0 = 5
b0 = 1
y = a0 * x + b0
l, = plt.plot(x, y)
# Set size of Axes
plt.axis([0, 1, -10, 10])
# Place Sliders on Graph
ax_a = plt.axes([0.25, 0.1, 0.65, 0.03])
ax_b = plt.axes([0.25, 0.15, 0.65, 0.03])
# Create Sliders & Determine Range
sa = Slider(ax_a, 'a', 0, 10.0, valinit=a0)
sb = Slider(ax_b, 'b', 0, 10.0, valinit=b0)
def update(val):
a = sa.val
b = sb.val
l.set_ydata(a*x+b)
fig.canvas.draw_idle()
sa.on_changed(update)
sb.on_changed(update)
plt.show()
The line in the 3D case needs to be updated in all 3 dimensions (even the data in some dimension stays the same). In order to do so, you have to set the 2D data using set_data and the third dimension using set_3d_properties. So updating y would look like this:
l.set_data(x, a*x+b)
l.set_3d_properties(z)

Plotting images relative to each other in Matplotlib

I'm working on a robot that has a downward facing camera. To show how my localization filter is doing, I'd like to plot the grass patches in a graph with the path of the robot. This means that I have to place rotated images in a graph. Is it possible to plot an image relative to the origin, or each other, with rotation?
I want to avoid using ndimage.rotate as it rotates the data of the image, not the image in the axes.
I tried to use:
import import matplotlib.pyplot as mplplt
import matplotlib.transforms as mpltf
import matplotlib.image as mplimg
grass = mplimg.imread("grass0.png")
grass = mplimg.imread("grass1.png")
fig = mplplt.figure()
ax = mplplt.subplot(111)
#Show previous grass image
ax.imshow(grass, extent = (-5, 5, -5, 5))
#Next image
tf = mpltf.Affine2D().rotate_deg(30).translate(1,1)
ax.imshow(grass1, extent = (-5, 5, -5, 5))#, transform = tf)
mplplt.show()
to no avail, the image was located on top of the previous one.
Here's an example with two copies of an image, different rotations and translations, one over the next. Plenty of limit-adjusting and Z-ordering and making a comprehensible function and all left to do. I Frankensteined your code and http://matplotlib.org/examples/api/demo_affine_image.html:
import matplotlib.pyplot as mplplt
import matplotlib.transforms as mpltf
import matplotlib.image as mplimg
def imshow_affine(ax, z, *kl, **kwargs):
im = ax.imshow(z, *kl, **kwargs)
x1, x2, y1, y2 = im.get_extent()
im._image_skew_coordinate = (x2, y1)
return im
grass = mplimg.imread("hammer_map.png")
fig = mplplt.figure()
ax = mplplt.subplot(111)
tf = mpltf.Affine2D().rotate_deg(30).translate(-1,-1) + ax.transData
im1 = imshow_affine(ax, grass, interpolation='none',
origin='lower',
extent=[1, 7, 0, 5], clip_on=False, zorder=2)
im1.set_transform(tf)
tf = mpltf.Affine2D().rotate_deg(190).translate(-2,1) + ax.transData
im2 = imshow_affine(ax, grass, interpolation='none',
origin='lower',
extent=[-3, 3, -2, 3], clip_on=False, zorder=1)
im2.set_transform(tf)
mplplt.show()