Related
I have typed a class for the matplotlib chart markers, everything works fine if I add markers to specific axes. But I want to add markers to all axes at once. Then I don't know how to get the Y value for the axis other than the one I clicked on.
class MplMarkers():
def __init__(self, fig, enable=None):
self.mpl_fig = fig
self.mpl_markers = []
self.mpl_markers_event = []
self.mpl_bbox_props = dict(boxstyle="round,pad=0.5", fc="w", ec="k", lw=1)
if enable:
self.enable()
def add(self, event):
if event.inaxes:
# add marker for current ax ######################
if event.button is MouseButton.RIGHT and event.key is None:
an = event.inaxes.annotate(bbox=self.mpl_bbox_props, text=' X={}\n Y={:4f}'.format(matplotlib.dates.num2date(event.xdata, tz=None).strftime("%H:%M:%S.%f"), event.ydata), xy=(event.xdata,0.98),
color='black', fontsize=8, alpha=0.5, xycoords=('data', 'axes fraction'), annotation_clip=False, verticalalignment='top', horizontalalignment='left' , rotation = 0)
vl = event.inaxes.axvline(x=event.xdata, linewidth=1.0, alpha=0.5, color='b', label='M-' + str(event.xdata))
self.mpl_markers.append({'x': event.xdata, 'y':event.ydata, 'an_cid':an, 'vl_cid':vl})
event.inaxes.figure.canvas.draw_idle()
# add marker for all ax ######################
if event.button is MouseButton.RIGHT and event.key == 'shift':
if len(self.mpl_fig.axes) > 1:
for a in self.mpl_fig.axes:
an = a.annotate(bbox=self.mpl_bbox_props, text=' X={}\n Y={:4f}'.format(matplotlib.dates.num2date(event.xdata, tz=None).strftime("%H:%M:%S.%f"), event.ydata), xy=(event.xdata,0.98),
color='black', fontsize=8, alpha=0.5, xycoords=('data', 'axes fraction'), annotation_clip=False, verticalalignment='top', horizontalalignment='left' , rotation = 0)
vl = a.axvline(x=event.xdata, linewidth=1.0, alpha=0.5, color='b', label='M-' + str(event.xdata))
self.mpl_markers.append({'x': event.xdata, 'y':event.ydata, 'an_cid':an, 'vl_cid':vl})
a.figure.canvas.draw_idle()
def remove(self, event):
if event.inaxes:
# remove all markers ##################
if event.button is MouseButton.RIGHT and event.key == 'control':
for m in self.mpl_markers:
m['an_cid'].remove()
m['vl_cid'].remove()
del m
self.mpl_markers=[]
for a in self.mpl_fig.axes:
a.figure.canvas.draw_idle()
def enable(self):
self.mpl_markers_event.append(self.mpl_fig.canvas.mpl_connect('button_press_event', self.add))
self.mpl_markers_event.append(self.mpl_fig.canvas.mpl_connect('button_press_event', self.remove))
def disable(self):
if self.mpl_markers_event:
for me in self.mpl_markers_event:
self.mpl_fig.canvas.mpl_disconnect(me)
How can I get the Y value for the rest of the axes?
In the code part - "# add marker for all ax ######################"
In this form, I get the Y value of the clicked axis on all axes
screenshot
I was trying to animate arrow and used the script from the following answer. Plus my animation has scatter-points and text too.
This is the script that I am using:-
from my_func import Pitch
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
## Pitch for plotting the football-pitch in the background
pitch = Pitch(line_color='grey', pitch_color='#121212', orientation='horizontal')
fig, ax = pitch.create_pitch()
x_start, y_start = (50, 35)
x_end, y_end = (90, 45)
x_1, y_1, x_2, y_2 = 50.55, 35.1375, 89.45, 44.8625
x = np.linspace(x_1, x_2, 20)
y = np.linspace(y_1, y_2, 20)
sc_1 = ax.scatter([], [], color="crimson", zorder=4, s=150, alpha=0.7, edgecolor="w")
sc_2 = ax.scatter([], [], color="crimson", zorder=4, s=150, alpha=0.7, edgecolor="w")
title = ax.text(50, 65, "", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5}, ha="center")
def animate(i):
if i == 1:
sc_1.set_offsets([x_start, y_start])
title.set_text("Start of Action 01")
if i == 2:
plt.pause(0.5)
title.set_text("Action 02")
## plot arrow
## haven't included ax.cla() as it was removing the pitch in the background
if i <= len(x):
patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
ax.add_patch(patch)
## plot scatter point
if i > len(x):
plt.pause(0.2)
title.set_text("Action 03")
sc_2.set_offsets([x_end, y_end])
return sc_1, patch, sc_2, title,
ani = animation.FuncAnimation(
fig=fig, func=animate, interval=50, blit=True)
plt.show()
The result is showing a little bit of animation and then giving me the following error:
File "test.py", line 33, in animate
patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
IndexError: index 20 is out of bounds for axis 0 with size 20
[1] 14116 abort (core dumped) python test.py
I am a begineer in matplotlib animations and don't know how to solve this error what should I change in my code to remove the error and generate the animated output.
The modifications are as follows: 1) plt.axes() is added to ax. 2) The return value of the animation function is set to ax. 3) The number of objects to be animated is 20, so frames=20 is used.
# from my_func import Pitch
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
## Pitch for plotting the football-pitch in the background
# pitch = Pitch(line_color='grey', pitch_color='#121212', orientation='horizontal')
# fig, ax = pitch.create_pitch()
fig,ax = plt.subplots()
x_start, y_start = 50, 35
x_end, y_end = 90, 45
x_1, y_1, x_2, y_2 = 50.55, 35.1375, 89.45, 44.8625
x = np.linspace(x_1, x_2, 20)
y = np.linspace(y_1, y_2, 20)
ax = plt.axes(xlim=(50, 90), ylim=(35, 45))
sc_1 = ax.scatter([], [], color="crimson", zorder=1, s=150, alpha=0.7, edgecolor="w")
sc_2 = ax.scatter([], [], color="crimson", zorder=1, s=150, alpha=0.7, edgecolor="w")
title = ax.text(50, 65, "", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5}, ha="center")
def animate(i):
if i == 1:
sc_1.set_offsets([x_start, y_start])
title.set_text("Start of Action 01")
if i == 2:
plt.pause(0.5)
title.set_text("Action 02")
## plot arrow
## haven't included ax.cla() as it was removing the pitch in the background
if i <= len(x):
ax.clear()
#patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
#ax.add_patch(patch)
ax.arrow(x_start, y_start, x[i]-x_start, y[i]-y_start, head_width=1, head_length=1, fc='black', ec='black')
ax.set(xlim=(50,90), ylim=(35,45))
## plot scatter point
if i == len(x)-1:
plt.pause(0.2)
title.set_text("Action 03")
sc_2.set_offsets([x_end, y_end])
return sc_1, sc_2, title, ax
ani = animation.FuncAnimation(fig=fig, func=animate, frames=20, interval=200, repeat=False, blit=True)
plt.show()
I have a plot where I want to interactively adjust the position of a patch and use the interactive update to be used in recalculating a variable.
I have plotted and image and added a closed contour (represented using PathPatch)
Here is an example image. example image with contour where information is calculated from the pixels enclosed by the contour. The pixel mask needs to be updated so that plots on right can also be updated.
Currently I plot everything and then use an event to move the contour, I tried using transforms and this worked to move the contour but when I tried using the transform to update the binary mask needed to recalculate the plots on the right side, the transformation it returns is incorrect. On top of this the transforms caused the contour to move when zooming in and out from the interactive window.
I need to figure out how to shift the contour and re-plot it and then extract the shift and apply to extracting a new mask of enclosed points to update the calculation.
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.path as mplpath
import matplotlib.patches as patches
from matplotlib.widgets import Button, Slider
# GENERATE SOME DATA TO PLOT
# event functions
def press(event):
print('press', event.key)
sys.stdout.flush()
if (event.key == 'left'):
shift[0] -= 1.0
elif (event.key == 'right'):
shift[0] += 1.0
elif (event.key == 'up'):
shift[1] += 1
elif (event.key == "down"):
shift[1] -= 1.0
# this is where I don't know how to properly transform and update[!
fig.canvas.draw()
class Recalculate(object):
def update(self, event):
x_update = [x_ for x_ in x_pts]
y_update = [y_ for y_ in y_pts]
verts_test = np.column_stack((x_update, y_update))
new_contour = mplpath.Path(verts_test, closed=True)
# create mask
new_grid = getMask(im_vol_resize, new_contour)
# calculate the flow
q_sum = []
for i in range(N): #assume size doesn't change
q_mask_sum = np.sum(test_q[:,:,i]*new_grid.astype(image_dtype))
q_sum.append(q_mask_sum*np.prod(spacing[0:2])/(100.0*scale**2)) # cm^3
print("total flow {0} cm^3/beat".format(np.trapz( q_sum, dx = np.mean(mean_diff)/1000)))
q_sum_roll = np.roll(q_sum, -16)
#q_interp = interpolate.CubicSpline(new_time, q_sum_roll, bc_type='periodic')
q_interp = interpolate.InterpolatedUnivariateSpline(new_time, q_sum_roll, k=3, ext=0)
q_test = q_interp(t_interp)
#plotting update
grid_handle.set_data(new_grid)
#print(new_time)
scat_data = np.stack((new_time, q_sum), axis=1)
#ax2.relim() # make sure all the data fits
#ax2.autoscale()
#print(scat_data.shape)
scatter_handle.set_offsets(scat_data)
interp_handle[-1].set_ydata(q_test)
ax3.relim() # make sure all the data fits
ax3.autoscale()
ax2.set_xlim(ax3.get_xlim())
ax2.set_ylim(ax3.get_ylim())
fig.canvas.draw()
def update_cine(val):
ind = int(slider.val)
s = [slice(ind, ind + 1) if i == 2 else slice(None)
for i in range(3)]
for sd in xyz_keys:
im = cubes[sd][s].squeeze()
cine_ax_dict[sd].set_data(im)
ax6.set_title("time: {0}".format(new_time[ind]))
ax2.scatter(new_time, q_sum, c='b', label='flowRate')
ax2.scatter(new_time[ind], q_sum[ind], c='r', label='time={0}'.format(new_time[ind]))
fig.canvas.draw()
#figures
gs = plt.GridSpec(6, 6, wspace=0.2, hspace=0.2)
fig = plt.figure(figsize=(17, 9))
fig.canvas.mpl_connect('key_press_event', press)
ax = fig.add_subplot(gs[3:,0:3])
#xl = ax.set_xlabel('easy come, easy go')
#ax.set_title('Press a key')
patch = patches.PathPatch(contour, facecolor=(1.0,165./255.0,0,0.25), lw=1 )#, alpha=0.5)
patch_handle = ax.add_patch(patch)
#plt.plot(x_pts, y_pts)
x_bounds = [round((bounds[0]-4)*scale), round((bounds[1]+4)*scale)]
y_bounds = [round((bounds[2]-4)*scale), round((bounds[3]+4)*scale)]
ax.set_xlim(x_bounds)
ax.set_ylim(y_bounds)
ax.imshow(im_vol_resize, interpolation='bilinear', cmap="gray", alpha=1.0)
ax5 = fig.add_subplot(gs[0:3,0:3])
patch2 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))#, alpha=0.5)
ax5.imshow(im_vol_resize, interpolation='bilinear', cmap="gray", alpha=1.0)
patch_handle2 = ax5.add_patch(patch2)
#new_contour = patch_.get_path()
ax5.set_xlim(x_bounds)
ax5.set_ylim(y_bounds)
# show ROI mask
grid_handle = ax.imshow(grid, interpolation='None', cmap="gray", alpha=0.1)
#print(dir(grid_handle))
ax2 = fig.add_subplot(gs[0:3, 4:])
scatter_handle = ax2.scatter(new_time, q_sum, c='b', label='flowRate')
ax2.scatter(new_time[0], q_sum[0], c='r', label='cine time')
#print(dir(scatter_handle))
#ax2.scatter(dict_time["y"], q_sum, label='flowRatey')
#ax2.scatter(dict_time["z"], q_sum, label='flowRatez')
#ax2.plot(dict_time["x"], q_sum)
#ax2.set_xlabel(r'$t$', fontsize=20)
ax2.set_title('terminal ICA Waveform', fontsize=20)
ax2.set_ylabel(r'Q, $Q(t)$ $cm^3/min$', fontsize=20)
ax2.xaxis.set_ticks_position('none')
ax2.xaxis.set_ticklabels([])
ax2.legend(loc='center left', bbox_to_anchor=(1, 0.9))
ax3 = fig.add_subplot(gs[3:, 4:])
ax3.set_xlabel(r'time $t$ $milliseconds$', fontsize=20)
ax3.set_ylabel(r'Flowrate $cm^3/min$', fontsize=20)
ax3.set_xlim(ax2.get_xlim())
ax3.set_ylim(ax2.get_ylim())
interp_handle = ax3.plot(t_interp, q_test, c='b', linestyle='-', label='interp')
#print(dir(interp_handle[0]))
ax3.legend(loc='center left', bbox_to_anchor=(1, 0.9))
cine_ax_dict = {}
ax6 = fig.add_subplot(gs[0:2,2:4])
ax6.set_title("time: {0}".format(new_time[0]))
cine_ax_dict["x"] = ax6.imshow(cubes['x'][:,:,0], interpolation='bilinear', cmap="viridis", alpha=1.0)
patch6 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))
ax6.add_patch(patch6)
ax6.set_xlim(x_bounds)
ax6.set_ylim(y_bounds)
ax7 = fig.add_subplot(gs[2:4,2:4])
cine_ax_dict["y"] = ax7.imshow(cubes['y'][:,:,0], interpolation='bilinear', cmap="viridis", alpha=1.0)
patch7 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))
ax7.add_patch(patch7)
ax7.set_xlim(x_bounds)
ax7.set_ylim(y_bounds)
ax8 = fig.add_subplot(gs[4:,2:4])
cine_ax_dict["z"] = ax8.imshow(cubes['z'][:,:,0], interpolation='bilinear', cmap="viridis", alpha=1.0)
patch8 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))
ax8.add_patch(patch8)
ax8.set_xlim(x_bounds)
ax8.set_ylim(y_bounds)
# define slider
axcolor = 'lightslategray'
ax_c = fig.add_axes([0.25, 0.05, 0.60, 0.03], aspect="auto", facecolor=axcolor)
slider = Slider(ax_c, 'Axis %i index' % 2, 0, cubes["x"].shape[2] - 1,
valinit=0, valfmt='%i')
I'm trying to shade over a map to show "explored regions" of the dot as it moves around using FuncAnimation. This is the code I have so far:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.animation as animation
import numpy as np
import random
import scipy.stats as stats
map_x = 100
map_y = 100
fig = plt.figure(0)
plt.figure(0)
ax1 = plt.subplot2grid((2,3), (0,0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid((2,3), (0,2), colspan=1)
ax1.set_xlim([0, map_x])
ax1.set_ylim([0, map_y])
ax2.set_xlim([0, map_x])
ax2.set_ylim([0, map_y])
agent = plt.Circle((50, 1), 2, fc='r')
agent2 = plt.Circle((50, 1), 2, fc='r')
agents = [agent, agent2]
ax1.add_patch(agent)
ax2.add_patch(agent2)
def animate(i):
x, y = agent.center
x = x+.1
y = y+.1
agent.center = (x, y)
agent2.center = (x, y)
return agent,
def fillMap(x, y):
circle=plt.Circle((x,y), 4, fc='b')
ax2.add_patch(circle)
def animate2(i):
x, y = agent2.center
x = x+.1
y = y+.1
agent2.center = (x, y)
fillMap(x, y)
return agent2,
anim = animation.FuncAnimation(fig, animate, frames=200, interval=20, blit=True)
anim2 = animation.FuncAnimation(fig, animate2, frames=200, interval=20, blit=True)
plt.show()
However, it only goes into fillMap once, and only draws the blue filled in circle once, instead of everywhere where the red dot goes in the smaller subplot.
If you want the circle you added to persist on the screen you probably shouldn't use blitting. Not using blitting makes the animation slower, but it may be enough to draw a new blue circle every 20th step or so.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.animation as animation
import numpy as np
import scipy.stats as stats
map_x = 100
map_y = 100
fig = plt.figure(0)
plt.figure(0)
ax1 = plt.subplot2grid((2,3), (0,0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid((2,3), (0,2), colspan=1)
ax1.set_xlim([0, map_x])
ax1.set_ylim([0, map_y])
ax2.set_xlim([0, map_x])
ax2.set_ylim([0, map_y])
agent = plt.Circle((50, 1), 2, fc='r')
agent2 = plt.Circle((50, 1), 2, fc='r')
agents = [agent, agent2]
ax1.add_patch(agent)
ax2.add_patch(agent2)
def fillMap(x, y):
circle=plt.Circle((x,y), 4, fc='b')
ax2.add_patch(circle)
def animate2(i):
x, y = agent.center
x = x+.1
y = y+.1
agent.center = (x, y)
x, y = agent2.center
x = x+.1
y = y+.1
agent2.center = (x, y)
if i%20==0:
circle = fillMap(x, y)
anim2 = animation.FuncAnimation(fig, animate2, frames=200, interval=20, blit=False)
plt.show()
In case you want to use blitting, consider using a line to mark the region where the circle has been.
line, =ax2.plot([],[], lw=3, color="b")
xl = []; yl=[]
def fillMap(x, y):
xl.append(x); yl.append(y)
line.set_data(xl,yl)
return line
def animate2(i):
x, y = agent.center
x = x+.1
y = y+.1
agent.center = (x, y)
x, y = agent2.center
x = x+.1
y = y+.1
agent2.center = (x, y)
if i%20==0:
fillMap(x, y)
return agent, agent2, line
anim2 = animation.FuncAnimation(fig, animate2, frames=200, interval=20, blit=True)
I have a 3D graph, and I would like to annotate them with the co-ordinates. However, the annotations gets overlapped. I would like them to not overlap.
My problem is -
Annotations get overlapped
In the legends, I don't understand why there are two symbols of triangles and circles. Shouldn't it be just one?
Just for information, my data set is limited to the following points only. So even if any other parameters are hard-coded, it is okay with me.
Here is my code.
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib.pyplot as plt
import pylab
xData1=[ 24500., 2980., 2980., 13740.]
xData2=[ 8360., 8360., 24500., 5670., 2980., 2980., 11050., 13740.]
yData1=[ 179., 244., 242., 181.]
yData2=[ 132., 149., 116., 163., 247., 228., 116., 116.]
zData1=[ 1., 44., 86., 44.]
zData2=[ 86., 22., 1., 86., 43., 86., 86., 22.]
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(xData1, yData1, zData1, '^', c='r', label='cfg1')
ax.plot(xData2, yData2, zData2, 'o', c='b', label='cfg2')
for i in range(len(xData1)):
text='['+str(int(xData1[i]))+','+str(int(yData1[i]))+','+str(int(zData1[i]))+']'
x2, y2, _ = proj3d.proj_transform(xData1[i],yData1[i],zData1[i], ax.get_proj())
label = pylab.annotate(text,
xycoords='data',
xy = (x2, y2), xytext = (60, 20),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
for i in range(len(xData2)):
text='['+str(int(xData2[i]))+','+str(int(yData2[i]))+','+str(int(zData2[i]))+']'
x2, y2, _ = proj3d.proj_transform(xData2[i],yData2[i],zData2[i], ax.get_proj())
label = pylab.annotate(text,
xycoords='data',
xy = (x2, y2), xytext = (20, 20),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
ax.set_xlabel('X-Data')
ax.set_ylabel('Y-Data')
ax.set_zlabel('Z-Data')
ax.legend(ncol=3)
plt.show()
Both questions are relatively easy answers. I'll start with the second one first: There are two symbols in your legend because you didn't specify the number when you defined the legend and the default value is two. To correct, simply change:
ax.legend(ncol=3, numpoints=1)
where numpoints changes the number of points within the legend - now it's set to 1.
The answer to your first question involves manipulating the placement of the text annotations, more specifically the xytext, which gives the coordinates for the text. Replacing your second for-loop with the below should get rid of your overlapping text and give you a good example of how to change the location of the annotation boxes for any other unsightly location-issues:
for i in range(len(xData2)):
text='['+str(int(xData2[i]))+','+str(int(yData2[i]))+','+str(int(zData2[i]))+']'
x2, y2, _ = proj3d.proj_transform(xData2[i],yData2[i],zData2[i], ax.get_proj())
if i==4:
label = pylab.annotate(text,
xycoords='data',
xy = (x2, y2), xytext = (0, -50),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
elif i==6:
label = pylab.annotate(text,
xycoords='data',
xy = (x2, y2), xytext = (-40, 0),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
else:
label = pylab.annotate(text,
xycoords='data',
xy = (x2, y2), xytext = (-20, 10),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))