Updating arrow position in matplotlib - python

I would like to update the arrow position while in a loop of plots. I found this post that has an analogous question for the situation in which the patch is a rectangle. Below, the solution proposed in the mentioned post with the addition of the Arrow patch.
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle, Arrow
import numpy as np
nmax = 10
xdata = range(nmax)
ydata = np.random.random(nmax)
fig, ax = plt.subplots()
ax.plot(xdata, ydata, 'o-')
ax.xaxis.set_ticks(xdata)
plt.ion()
rect = plt.Rectangle((0, 0), nmax, 1, zorder=10)
ax.add_patch(rect)
arrow = Arrow(0,0,1,1)
ax.add_patch(arrow)
for i in range(nmax):
rect.set_x(i)
rect.set_width(nmax - i)
#arrow.what --> which method?
fig.canvas.draw()
plt.pause(0.1)
The problem with the Arrow patch is that apparently it does not have a set method related with its position as the Rectangle patch has. Any tip is welcome.

The matplotlib.patches.Arrow indeed does not have a method to update its position. While it would be possible to change its transform dynamically, I guess the easiest solution is to simply remove it and add a new Arrow in each step of the animation.
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle, Arrow
import numpy as np
nmax = 9
xdata = range(nmax)
ydata = np.random.random(nmax)
plt.ion()
fig, ax = plt.subplots()
ax.set_aspect("equal")
ax.plot(xdata, ydata, 'o-')
ax.set_xlim(-1,10)
ax.set_ylim(-1,4)
rect = Rectangle((0, 0), nmax, 1, zorder=10)
ax.add_patch(rect)
x0, y0 = 5, 3
arrow = Arrow(1,1,x0-1,y0-1, color="#aa0088")
a = ax.add_patch(arrow)
plt.draw()
for i in range(nmax):
rect.set_x(i)
rect.set_width(nmax - i)
a.remove()
arrow = Arrow(1+i,1,x0-i+1,y0-1, color="#aa0088")
a = ax.add_patch(arrow)
fig.canvas.draw_idle()
plt.pause(0.4)
plt.waitforbuttonpress()
plt.show()

Related

Animating moving square in python

I'm new to python's matplotlib, and i want to animate a 1x1 square that moves diagonally across a grid space. I have written this bit of code that almost does what i want it to do, but the previous positions of the rectangle are still visible.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
from matplotlib.patches import Rectangle
moving_block = [[0,0],[1,1],[2,2],[3,3],[4,4],[5,5]]
fig, ax = plt.subplots()
#set gridlines and lines every one unit
ax.grid(which='both')
ax.axis([0,5,0,5])
rectangle = Rectangle(moving_block[0], 1,1)
ax.add_patch(rectangle)
def animate(i):
ax.add_patch(Rectangle(moving_block[i], 1,1))
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=len(moving_block), interval=300, repeat=True)
plt.show()
How can i make only the current rectangle visible? Should i be using something other than this ax.add_patch(Rectangle) function?
Added cleaning "ax", at each iteration in the function "animate".
If you are satisfied with the answer, do not forget to vote for it :-)
import matplotlib.pyplot as plt
import matplotlib.animation
from matplotlib.patches import Rectangle
moving_block = [[0,0],[1,1],[2,2],[3,3],[4,4],[5,5]]
fig, ax = plt.subplots()
#set gridlines and lines every one unit
ax.grid(which='both')
ax.axis([0, 5, 0, 5])
rectangle = Rectangle(moving_block[0], 1,1)
ax.add_patch(rectangle)
def animate(i):
ax.clear()
ax.axis([0, 5, 0, 5])
ax.grid(which='both')
ax.add_patch(Rectangle(moving_block[i], 1,1))
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=len(moving_block), interval=300, repeat=True)
plt.show()

Animation issue in Python

I want the green rectangle to not disappear as it moves from one value to another in matrix b. For example, the rectangle is around 0.24671953. Then the rectangle stays on this value. Then another rectangle appears onto the next value which is 0.25959473. Then another rectangle appears on 0.41092171, with the previous two rectangles not disappearing.
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import seaborn as sns
import numpy as np
from celluloid import Camera
a = np.array([[0.24671953, 0.25959473, 0.85494718],
[0.60553861, 0.76276659, 0.41092171],
[0.37356358, 0.69378785, 0.46988614]])
b = np.array([[0.24671953,0.25959473],
[0.41092171,0.46988614],
[0.37356358,0.60553861]])
annot=True
fig, ax1 = plt.subplots(1)
camera = Camera(fig)
sns.set_style('white')
ax1 = sns.heatmap(a, linewidth=0.5,ax=ax1,annot=annot)
for bb in b.flatten():
ax1.add_patch(plt.Rectangle((np.where(a == bb)[1][0],
np.where(a == bb)[0][0]), 1, 1, fc='none', ec='green', lw=5, clip_on=False))
camera.snap()
animation = camera.animate(interval=800)
animation.save('animation2.gif')
plt.show()
It looks like celluloid clears the existing plot at each snap. You can recreate the plot from scratch at each step. The rectangles can be stored in a list.
To avoid that new colorbar positions are set at each step, you can use sns.heatmap's cbar_ax= parameter to always use the same colorbar ax:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from celluloid import Camera
a = np.array([[0.24671953, 0.25959473, 0.85494718],
[0.60553861, 0.76276659, 0.41092171],
[0.37356358, 0.69378785, 0.46988614]])
b = np.array([[0.24671953, 0.25959473],
[0.41092171, 0.46988614],
[0.37356358, 0.60553861]])
fig, (ax1, cbar_ax) = plt.subplots(ncols=2, gridspec_kw={'width_ratios': [20, 1]})
camera = Camera(fig)
sns.set_style('white')
rectangles = []
for bb in b.flatten():
sns.heatmap(a, linewidth=0.5, ax=ax1, annot=True, cbar_ax=cbar_ax)
rectangles.append(plt.Rectangle((np.where(a == bb)[1][0], np.where(a == bb)[0][0]), 1, 1,
fc='none', ec='green', lw=5, clip_on=False))
for rect in rectangles:
ax1.add_patch(rect)
camera.snap()
animation = camera.animate(interval=800)
animation.save('animation2.gif')
plt.show()
An alternative could be to directly use matplotlib's animation framework. Then, the code could look like:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import seaborn as sns
import numpy as np
a = np.array([[0.24671953, 0.25959473, 0.85494718],
[0.60553861, 0.76276659, 0.41092171],
[0.37356358, 0.69378785, 0.46988614]])
b = np.array([[0.24671953, 0.25959473],
[0.41092171, 0.46988614],
[0.37356358, 0.60553861]])
fig, ax1 = plt.subplots()
sns.set_style('white')
sns.heatmap(a, linewidth=0.5, ax=ax1, annot=True)
def animate(i):
bb = b.flatten()[i]
patch = ax1.add_patch(plt.Rectangle((np.where(a == bb)[1][0], np.where(a == bb)[0][0]), 1, 1,
fc='none', ec='green', lw=5, clip_on=False))
return patch,
animation = FuncAnimation(fig, animate, frames=range(0, b.size), interval=800, repeat=False)
animation.save('animation2.gif')
plt.show()

Python - zoom a part of a plot

I have a main plot and I'm trying to include a detail of a zoomed part in the same plot.
Most of my tries end with the error:
Can not reset the axes. You are probably trying to re-use an artist in more than one Axes which is not supported.
I've seen examples that work but none of them have an iteration for the subplot. Can somebody help me with this issue?
My code is very similar to this:
import matplotlib.pyplot as plt
import numpy.random as rnd
from matplotlib.patches import Ellipse
NUM = 250
ells = [Ellipse(xy=rnd.rand(2)*10, width=rnd.rand(), height=rnd.rand(), angle=rnd.rand()*360)
for i in range(NUM)]
fig = plt.figure(0)
ax = fig.add_subplot(111, aspect='equal')
for e in ells:
ax.add_artist(e)
e.set_clip_box(ax.bbox)
e.set_alpha(rnd.rand())
e.set_facecolor(rnd.rand(3))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
#Subfigure
ax2 = plt.axes([.5, .3, .2, .2])
#I have to iterate in a subset of ells
plt.xticks([])
plt.yticks([])
plt.setp(ax2, xticks=[], yticks=[])
plt.show()

Matplotlib: How to get a colour-gradient as an arrow next to a plot?

I am trying to create a plot with matplotlib that includes several different lines, offset artificially, that are coloured according to the RedBlue colourmap mpl.cm.RdBu. Now I want an arrow next to the plot that acts as an effective colourscale, meaning that it should have a colour gradient.
So far, I have managed to create the arrow itself using annotate with the help of this answer and drew a "Rainbow arrow" inside the plot using this brilliant answer (Note: You will need matplotlib 2.2.4 or older to run this part of the code, see comments.).
This is the MWE I can produce so far:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.transforms
import matplotlib.path
from matplotlib.collections import LineCollection
# from https://stackoverflow.com/questions/47163796/using-colormap-with-annotate-arrow-in-matplotlib
def rainbowarrow(ax, start, end, cmap="viridis", n=50,lw=3):
cmap = plt.get_cmap(cmap,n)
# Arrow shaft: LineCollection
x = np.linspace(start[0],end[0],n)
y = np.linspace(start[1],end[1],n)
points = np.array([x,y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, cmap=cmap, linewidth=lw)
lc.set_array(np.linspace(0,1,n))
ax.add_collection(lc)
# Arrow head: Triangle
tricoords = [(0,-0.4),(0.5,0),(0,0.4),(0,-0.4)]
angle = np.arctan2(end[1]-start[1],end[0]-start[0])
rot = matplotlib.transforms.Affine2D().rotate(angle)
tricoords2 = rot.transform(tricoords)
tri = matplotlib.path.Path(tricoords2, closed=True)
ax.scatter(end[0],end[1], c=1, s=(2*lw)**2, marker=tri, cmap=cmap,vmin=0)
ax.autoscale_view()
def plot_arrow(data,n):
fig, subfig = plt.subplots(1,1,figsize=(6.28,10)) # plotsize, etc
colorP=mpl.cm.RdBu(0.2)
i = 0
while i<=n-1:
subfig.plot(data[i,0], (data[i,1])+i, lw=2, color=mpl.cm.RdBu(1-i/20)) # plot of data
i=i+1
subfig.annotate('', xy=(1.1,0), xycoords='axes fraction', xytext=(1.1,1),
arrowprops=dict(arrowstyle="<-", lw = 3))
subfig.annotate('A', xy=(1.1,0), xycoords='axes fraction', xytext=(1.1,1))
subfig.annotate('B', xy=(1.1,0), xycoords='axes fraction', xytext=(1.1,0))
rainbowarrow(subfig, (1.1,3), (1.1,5), cmap='RdBu_r', n=100,lw=3)
plt.show(fig)
plt.close(fig)
# things to plot
np.random.seed(19680802)
n = 20
i = 0
data = np.empty([n,2,10])
while i<=n-1:
data[i]=np.sin(np.random.rand(10))
i = i+1
# actual plot
plot_arrow(data,n)
Here's the graph generated:
In a nutshell: I want the annotation arrow outside the plot to have the colour of the colourmap, as the small rainbow arrow inside the plot.

How do I draw a rectangle on the legend in matplotlib?

I am trying to plot a rectangle onto the legend in matplotlib.
To illustrate how far I have gotten I show my best attempt, which does NOT work:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
Fig = plt.figure()
ax = plt.subplot(111)
t = np.arange(0.01, 10.0, 0.01)
s1 = np.exp(t)
ax.plot(t, s1, 'b-', label = 'dots')
leg = ax.legend()
rectangle = Rectangle((leg.get_frame().get_x(),
leg.get_frame().get_y()),
leg.get_frame().get_width(),
leg.get_frame().get_height(),
fc = 'red'
)
ax.add_patch(rectangle)
plt.show()
The rectangle just isn't draw anywhere in the figure.
If I look at the values of leg.get_frame().get_x(), leg.get_frame().get_y()), leg.get_frame().get_width() and leg.get_frame().get_height(), I see that they are
0.0, 0.0, 1.0 and 1.0 respectively.
My problem thus sees to be, to find the co-ordinates of the frame of the legend.
It would be really great if you could help me out.
Thanks for reading this far.
This link may have the exact thing you are looking for.
http://matplotlib.org/users/legend_guide.html#creating-artists-specifically-for-adding-to-the-legend-aka-proxy-artists
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
red_patch = mpatches.Patch(color='red', label='The red data')
plt.legend(handles=[red_patch])
plt.show()
The trouble is that the position of the legend is not known in advance. Only by the time you render the figure (calling plot()), is the position decided.
A solution I came across is to draw the figure twice. In addition, I've used axes coordinates (default is data coordinates) and scaled the rectangle so you still see a bit of the legend behind it. Note that I had to set the legend and rectangle zorder as well; the legend gets drawn later than the rectangle and thus the rectangle otherwise disappears behind the legend.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
Fig = plt.figure()
ax = plt.subplot(111)
t = np.arange(0.01, 10.0, 0.01)
s1 = np.exp(t)
ax.plot(t, s1, 'b-', label = 'dots')
leg = ax.legend()
leg.set_zorder(1)
plt.draw() # legend position is now known
bbox = leg.legendPatch.get_bbox().inverse_transformed(ax.transAxes)
rectangle = Rectangle((bbox.x0, bbox.y0),
bbox.width*0.8, bbox.height*0.8,
fc='red', transform=ax.transAxes, zorder=2)
ax.add_patch(rectangle)
plt.show()

Categories

Resources