Matplotlib: Iteratively draw/erase one plot over a background plot - python-3.x

I'm trying to accomplish something hopefully pretty basic. Hoping for a few pointers!
I want to draw two circles: one that stays on the screen, and one that iteratively grows larger. For the one that changes, I essentially want to use a loop to draw and erase, so that it will draw a larger circle on each iteration (accomplishing an effect where it appears that a second, animated circle is growing). Below is what I've managed to work out so far. It's drawing the second circle progressively larger with each loop, but not erasing.
import matplotlib as plt
plt.use('TkAgg')
import matplotlib.pyplot as plt
#Initialize a variable
CHANGE = 0.3
#Make static 1st circle
circle1 = plt.Circle((0.5, 0.5), 0.2, color='white')
fig, ax = plt.subplots()
plt.axis('off')
ax.add_artist(circle1)
fig.set_facecolor("black")
#Animate dynamic 2nd circle
def frange(start, stop, step):
i = start
while i < stop:
yield i
i += step
for step in frange(0, .6, .01):
circle2 = plt.Circle((0.5, 0.5), CHANGE, color='gray', fill=False)
ax.add_artist(circle2)
plt.draw()
plt.pause(.001)
#plt.cla( )
CHANGE = CHANGE + step
Note: I played around with adding plt.cla() into the loop. While I was able to get it to erase/redraw circle 2 by doing that, it seems to draw over the original circle 1, versus keeping both visible in the same plot.
~~~~~~~~~~~~~
A) What it should like on each loop:
B) What it's looking like when not erasing:
(Using Python3.6.5 via PycharmCE)

Instead of multiple calls to add_artist and remove(), you should simply call the the Circle class's set_radius method. This will be more performant.

Connecting the dots, based on ImportanceOfBeingEarnest's comment, for any newbies out there like me:
Added the following starred line of code into the loop:
for step in frange(0, .6, .01):
circle2 = plt.Circle((0.5, 0.5), CHANGE, color='gray', fill=False)
ax.add_artist(circle2)
plt.draw()
plt.pause(.001)
**circle2.remove()**
CHANGE = CHANGE + step

Related

How to combine two geometries into one plot in Python

Question background: I am trying to make two geometries in a one plot in python. I have made one geometry which is an object having mesh as shown in figure below. The respective code is also mentioned here.
df_1_new = pd.DataFrame()
df_1_new['X_coordinate']=pd.Series(x_new)
df_1_new['Y_coordinate']=pd.Series(y_new)
df_1_new['node_number'] = df_1_new.index
df_1_new = df_1_new[['node_number','X_coordinate','Y_coordinate']]
plt.scatter(x_new, y_new)
plt.show
The second geometry, which is a circle and I made this geometry running below code.
from matplotlib import pyplot as plt, patches
plt.rcParams["figure.figsize"] = [9.00, 6.50]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot()
circle1 = plt.Circle((2, 2), radius=5, fill = False)
ax.add_patch(circle1)
ax.axis('equal')
plt.show()
My question: How can I combine both geometries mentioned above in a one plot. I would like to place my circle around my geometry (object). Geometry has a centroid (2, 2) and I want to place my circle's centroid exactly on the centroid of geometry therefore I will be having a circle around my geometry. What code I should write. Kindly help me on this.
For your reference: I want my plot just like in below picture.
you need to do all the plotting between the subplot creation and before you issue the plt.show() command, as any command after it will create a new figure.
from matplotlib import pyplot as plt, patches
plt.rcParams["figure.figsize"] = [9.00, 6.50]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot()
# other plt.scatter or plt.plot here
plt.scatter([3,4,5,6,4],[5,4,2,3,2]) # example
circle1 = plt.Circle((2, 2), radius=5, fill = False)
ax.add_patch(circle1)
ax.axis('equal')
plt.show()
image example
to get the points inside the circle, you need to play with the circle radius and center till you get it right.
something you can do is to make the circle at the np.median of your x and y values, so you are sure about the center position.

Modify position of colorbar so that extend triangle is above plot

So, I have to make a bunch of contourf plots for different days that need to share colorbar ranges. That was easily made but sometimes it happens that the maximum value for a given date is above the colorbar range and that changes the look of the plot in a way I dont need. The way I want it to treat it when that happens is to add the extend triangle above the "original colorbar". It's clear in the attached picture.
I need the code to run things automatically, right now I only feed the data and the color bar range and it outputs the images, so the fitting of the colorbar in the code needs to be automatic, I can't add padding in numbers because the figure sizes changes depending on the area that is being asked to be plotted.
The reason why I need this behavior is because eventually I would want to make a .gif and I can't have the colorbar to move in that short video. I need for the triangle to be added, when needed, to the top (and below) without messing with the "main" colorbar.
Thanks!
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize, BoundaryNorm
from matplotlib import cm
###############
## Finds the appropriate option for variable "extend" in fig colorbar
def find_extend(vmin, vmax, datamin, datamax):
#extend{'neither', 'both', 'min', 'max'}
if datamin >= vmin:
if datamax <= vmax:
extend="neither"
else:
extend="max"
else:
if datamax <= vmax:
extend="min"
else:
extend="both"
return extend
###########
vmin=0
vmax=30
nlevels=8
colormap=cm.get_cmap("rainbow")
### Creating data
z_1=30*abs(np.random.rand(5, 5))
z_2=37*abs(np.random.rand(5, 5))
data={1:z_1, 2:z_2}
x=range(5)
y=range(5)
## Plot
for day in [1, 2]:
fig = plt.figure(figsize=(4,4))
## Normally figsize=get_figsize(bounds) and bounds is retrieved from gdf.total_bounds
## The function creates the figure size based on the x/y ratio of the bounds
ax = fig.add_subplot(1, 1, 1)
norm=BoundaryNorm(np.linspace(vmin, vmax, nlevels+1), ncolors=colormap.N)
z=data[day]
cs=ax.contourf(x, y, z, cmap=cmap, norm=norm, vmin=vmin, vmax=vmax)
extend=find_extend(vmin, vmax, np.nanmin(z), np.nanmax(z))
fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, extend=extend)
plt.close(fig)
You can do something like this: putting a triangle on top of the colorbar manually:
fig, ax = plt.subplots()
pc = ax.pcolormesh(np.random.randn(20, 20))
cb = fig.colorbar(pc)
trixy = np.array([[0, 1], [1, 1], [0.5, 1.05]])
p = mpatches.Polygon(trixy, transform=cb.ax.transAxes,
clip_on=False, edgecolor='k', linewidth=0.7,
facecolor='m', zorder=4, snap=True)
cb.ax.add_patch(p)
plt.show()

want matplotlib slider plot called from class [duplicate]

I am trying to draw a scatter plot with a point that moves based on a parameter adjusted by a slider. I need the parameter to be the position in a list of the coordinates. I have it so the scatter plot gets drawn and I can manually move the point by change the position, but when I try to implement the slider it is displayed, but can not be drug to update the plot. Any help would be great. What I have so far is below. Thanks.
%pylab
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import animation
def on_change(val):
p=int(val)/1
def chart():
x=[0,0.5,1]
y=[0,0.5,1]
z=[0,0.5,1]
p=0
fig = pyplot.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot([0,0,0,0,0,1,1,0,0,1,1,1,1,1,1,0],[0,1,1,0,0,0,0,0,1,1,0,0,1,1,1,1],[0,0,1,1,0,0,1,1,1,1,1,0,0,1,0,0],c='black',zorder=10)
ax.scatter(x[p],y[p],z[p],zorder=0)
ax.set_xlabel('Width')
ax.set_ylabel('Depth')
ax.set_zlabel('Height')
slider_ax = plt.axes([0.15, 0.05, 0.7, 0.02])
slider = Slider(slider_ax, "min", 0, 2, valinit=1, color='blue')
pyplot.show()
chart()
You have to keep a reference to the Slider object around or it (and it's call backs) get garbage collected. See long discussion at https://github.com/matplotlib/matplotlib/issues/3105.
The documentation on this has been clarified for 1.4.

Window size incorrect on matplotlib animation

Trying to get an animation of a rotating arrow in a Jupyter notebook.
Can't get the window size and circle display correct.
I'm trying to get an animation of a rotating arrow in matplotlib. This is part of a jupyter engineering mechanics book I'm building for my students.
The idea of the question is that the animation shows what the two dimensional force balance is of multiple vectors on a node (the black dot in the code).
The animation is based on the following three sources:
1) Drawing a shape
2) Matplotlib animation
3) Arrow animation
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as patches
from matplotlib import animation, rc
from IPython.display import HTML
from math import degrees,radians,cos,sin,atan,acos,sqrt
# Create figure
fig, ax = plt.subplots()
# Axes labels and title are established
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_ylim(-100,100) #<---- This window size is not displayed
ax.set_xlim(-100,100) #<---- This window size is not displayed
ax.set_aspect('equal', adjustable='box')
#the circle
circle = plt.Circle((0, 0), radius=10, fc='black')
plt.gca().add_patch(circle) #<---- The circle is not displayed
#arrow1 (more arrows will me added)
arrow1x=[]
arrow1y=[]
arrow1dx=[]
arrow1dy=[]
for t in range(1000):
if t <= 250:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(t/250*100)
arrow1dy.append(0)
elif t <= 500:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(100)
arrow1dy.append(0)
elif t <= 750:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(100*cos(radians((t-500)/250*180.)))
arrow1dy.append(100*sin(radians((t-500)/250*180.)))
else:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append((100-100*(t-750)/250)*-sin(radians((t-750)/250*180.)))
arrow1dy.append((100-100*(t-750)/250)*-sin(radians((t-750)/250*180.)))
patch = patches.Arrow(arrow1x[0], arrow1y[0], arrow1dx[0], arrow1dy[0])
#the animation (I have no idea how this works:)
def init():
ax.add_patch(patch)
return patch,
def animate(t):
ax.clear()
patch = plt.Arrow(arrow1x[t], arrow1y[t], arrow1dx[t], arrow1dy[t])
ax.add_patch(patch)
return patch,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=1000, interval=20,
blit=True)
HTML(anim.to_jshtml())
As a result of this code I would like to see a square screen with range (-100 x 100,-100 y 100), the black node and the arrow.
What I'm seeing is a square screen (0 x 1,0 y 1), the rotating arrow, and no black dot.
There is no error output in jupyter which makes this really difficult to follow. Additionally the code takes really long to compile, which is also something that is not desired for a webpage, if this keeps taking so long I think i should look in a pre-compiled image (any tips for that perhaps ?).
Thus for some reason the window size and the dot are not adopted, but as far as I'm seeing the code from the sources is adopted as depicted on the webpages.
You took inappropriate part of "Arrow animation". Since you have static elements on your plot, you don't want to fully clear your ax: you should remove one patch during execution of animate function. Just replace ax.clear() with the next lines:
global patch
ax.patches.remove(patch)

Adding a colorbar outside an image without shrinking it

I'm trying to plot a 3x3 subplots, the first line using imshow, and second and third are regular graphs. All the 3x3 plots share the same x-axis, so it's really important that they will be perfectly aligned (I didn't use sharey=true because I wanted the y-ticks). I was able to put the graphs legends outside the figure in the right (the legend is the same legend for the whole 3 figures in the same row) by using:
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
But I couldn't find how to put the colorbar of the first row above them, without shrinking the third plot in the first row. I was trying to use "make_axes_locatable":
from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(powers_ax)
cax = divider.append_axes("right", size="5%", pad=1)
plt.colorbar(im, cax=cax)
Or "inset_axes":
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
axins = inset_axes(powers_ax, width="10%", height="100%", loc=5,
bbox_to_anchor=(0.5, 0, 1, 1), bbox_transform=powers_ax.transAxes)
plt.colorbar(im, cax=axins)
Or even this trick:
fig.colorbar(im2, ax=ax[0, :].ravel().tolist(), location='right', shrink=0.6)
But nothing gave me the result I watned.
Any ideas?
* EDIT *
As #ImportanceOfBeingErnest suggested, the solution was tweaking the first parameter in the bbox_to_anchor (from 0.5 to 1.15 in this case):
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
axins = inset_axes(powers_ax, width="5%", height="100%", loc=5,
bbox_to_anchor=(1.15, 0, 1, 1), bbox_transform=powers_ax.transAxes)
plt.colorbar(im2, cax=axins)

Resources