Redrawing plot on Basemap in Python - python

I am plotting a scatter plot on a Basemap. However, the data with this scatter plot changes based on user input. I would like to clear the data (only the data -- not the entire basemap figure) and re-plot new scatter points.
This question is similar but was not answered (http://stackoverflow.com/questions/8429693/python-copy-basemap-or-remove-data-from-figure)
Currently I am closing the figure with clf(); however, this requires me to re-draw the entire basemap and scatter plot together. On top of this, I am doing all of the redrawing inside of a wx panel. The basemap redraw takes too long and am hoping that there is an easy way to simply re-plot scatter points only.
#Setting up Map Figure
self.figure = Figure(None,dpi=75)
self.canvas = FigureCanvas(self.PlotPanel, -1, self.figure)
self.axes = self.figure.add_axes([0,0,1,1],frameon=False)
self.SetColor( (255,255,255) )
#Basemap Setup
self.map = Basemap(llcrnrlon=-119, llcrnrlat=22, urcrnrlon=-64,
urcrnrlat=49, projection='lcc', lat_1=33, lat_2=45,
lon_0=-95, resolution='h', area_thresh=10000,ax=self.axes)
self.map.drawcoastlines()
self.map.drawcountries()
self.map.drawstates()
self.figure.canvas.draw()
#Set up Scatter Plot
m = Basemap(llcrnrlon=-119, llcrnrlat=22, urcrnrlon=-64,
urcrnrlat=49, projection='lcc', lat_1=33, lat_2=45,
lon_0=-95, resolution='h', area_thresh=10000,ax=self.axes)
x,y=m(Long,Lat)
#Scatter Plot (they plot the same thing)
self.map.plot(x,y,'ro')
self.map.scatter(x,y,90)
self.figure.canvas.draw()
Then I do an some type of update on my (x,y)...
#Clear the Basemap and scatter plot figures
self.figure.clf()
Then I repeat all of the above code. (I also have to redo my box sizers for my panel -- I did not include these).
Thanks!

The matplotlib.pyplot.plot documentation mentions that the plot() command returns a Line2D artist which has xdata and ydata properties, so you might be able to do the following:
# When plotting initially, save the handle
plot_handle, = self.map.plot(x,y,'ro')
...
# When changing the data, change the xdata and ydata and redraw
plot_handle.set_ydata(new_y)
plot_handle.set_xdata(new_x)
self.figure.canvas.draw()
I haven't managed to get the above to work for collections, or 3d projections, unfortunately.

Most plotting functions return Collections object. If so then you can use remove() method. In your case, I would do the following:
# Use the Basemap method for plotting
points = m.scatter(x,y,marker='o')
some_function_before_remove()
points.remove()

Related

GridSpec and ConnectionPatch Issues

I'm trying to have one large plot, and then use arrows to connect outliers to smaller plots. I used gridspec to create my plots, and on their own they show up perfectly.
However, when I try to use ConnectionPatch to add an artist for the arrows, it changes the size of all the subplots.
Here is my code setting up the figure and subplots using gridspec:
fig2 = plt.figure(constrained_layout=True)
gs = fig2.add_gridspec(ncols=3, nrows=9, figure=fig2)
f2_ax1 = fig2.add_subplot(gs[0:-4, :-1])
f2_ax2 = fig2.add_subplot(gs[2:-4, 2])
f2_ax3 = fig2.add_subplot(gs[5:-1, 0])
f2_ax4 = fig2.add_subplot(gs[5:-1, 1])
f2_ax5 = fig2.add_subplot(gs[5:-1,2])
This creates the plots I want (see picture):
Here is how I'm adding arrows to the plots, using a for loop to iterate through each subplot:
coordsA = "data" #The arrows are plotting using the same coordinates as the data points
coordsB = "data"
i = 0
for sub_plot in sub_plot_list: #Adding the arrows to the subplots
xy1 = (x_oulier_list[i], y_outlier_list[i]) #Fetches coordinates for beginning of arrow
xy2 = arrow_end_list[i] #Fetches coordinates for the pointy end of arrow
con = ConnectionPatch(xyA=(x_outlier_list[i],y_outlier_list[i]), xyB=arrow_end_list[i],
coordsA=coordsA, coordsB=coordsB,
axesA=f2_ax1, axesB=sub_plot,
arrowstyle="->", shrinkB=5)
f2_ax1.add_artist(con) #Adds arrow to plot
i += 1
Both versions use fig2.tight_layout().
Here is the plot after adding arrows:
Any idea how I can fix it so that the dimensions of the plot don't change when I add arrows?
Looks like the problem is with constrained_layout, which does not seem to handle ConnectionPatch very well (constrained_layout is still experimental, maybe it is worth raising an issue on matplotlib's github in case someone can figure the problem out).
An easy fix is to request that the ConnectionPatch objects are not taken into account when calculating the layout using:
(...)
con = ConnectionPatch(xyA=(x_outlier_list[i],y_outlier_list[i]), xyB=arrow_end_list[i],
coordsA=coordsA, coordsB=coordsB,
axesA=f2_ax1, axesB=sub_plot,
arrowstyle="->", shrinkB=5)
con.set_in_layout(False)
(...)

Problem updating imshow in loop and colormap after loop

I have two imshow() problems that I suspect are closely related.
First, I can't figure out how to use set_data() to update an image I've created with imshow().
Second, I can't figure out why the colorbar I add to the imshow() plot after I'm done updating the plot doesn't match the colorbar I add to an imshow() plot of the same data that I create from scratch after I'm done taking data. The colorbar of the second plot appears to be correct.
Background.
I'm collecting measurement data in two nested loops, with each loop controlling one of the measurement conditions. I'm using pyplot.imshow() to plot my results, and I'm updating the imshow() plot every time I take data in the inner loop.
What I have works in terms of updating the imshow() plot but it seems to be getting increasingly slower as I add more loop iterations, so it's not scaling well. (The program I've included in with this post creates a plot that is eight rows high and six columns wide. A "real" plot might be 10x or 20x this size, in both dimensions.)
I think what I want to do is use the image's set_data() method but I can't figure out how. What I've tried either throws an error or doesn't appear to have any effect on the imshow() plot.
Once I'm done with the "take the data" loops, I add a colorbar to the imshow() plot I've been updating. However, the colorbar scale is obviously bogus.
In contrast, if I take create an entirely new imshow() plot, using the data I took in the loops, and then add a colorbar, the colorbar appears to be correct.
My hunch is the problem is with the vmin and vmax values associated with the imshow() plot I'm updating in the loops but I can't figure out how to fix it.
I've already looked at several related StackOverflow posts. For example:
update a figure made with imshow(), contour() and quiver()
Update matplotlib image in a function
How to update matplotlib's imshow() window interactively?
These have helped, in that they've pointed me to set_data() and given me solutions to some other
problems I had, but I still have the two problems I mentioned at the start.
Here's a simplified version of my code. Note that there are repeated zero values on the X and Y axes. This is on purpose.
I'm running Python 3.5.1, matplotlib 1.5.1, and numpy 1.10.4. (Yes, some of these are quite old. Corporate IT reasons.)
import numpy as np
import matplotlib.pyplot as plt
import random
import time
import warnings
warnings.filterwarnings("ignore", ".*GUI is implemented.*") # Filter out bogus matplotlib warning.
# Create the simulated data for plotting
v_max = 120
v_step_size = 40
h_max = 50
h_step_size = 25
scale = 8
v_points = np.arange(-1*abs(v_max), 0, abs(v_step_size))
v_points = np.append(v_points, [-0.0])
reversed_v_points = -1 * v_points[::-1] # Not just reverse order, but reversed sign
v_points = np.append(v_points, reversed_v_points)
h_points = np.arange(-1*abs(h_max), 0, abs(h_step_size))
h_points = np.append(h_points, [-0.0])
reversed_h_points = -1 * h_points[::-1] # Not just reverse order, but reversed sign
h_points = np.append(h_points, reversed_h_points)
h = 0 # Initialize
v = 0 # Initialize
plt.ion() # Turn on interactive mode.
fig, ax = plt.subplots() # So I have access to the figure and the axes of the plot.
# Initialize the data_points
data_points = np.zeros((v_points.size, h_points.size))
im = ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
ax.set_title('Dummy title for initial plot')
# Set up the X-axis ticks and label them
ax.set_xticks(np.arange(len(h_points)))
ax.set_xticklabels(h_points)
ax.set_xlabel('Horizontal axis measurement values')
# Set up the Y-axis ticks and label them
ax.set_yticks(np.arange(len(v_points)))
ax.set_yticklabels(v_points)
ax.set_ylabel('Vertical axis measurement values')
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.show()
for v, v_value in enumerate(v_points):
for h, h_value in enumerate(h_points):
# Measurement goes here.
time.sleep(0.1) # Simulate the measurement delay.
measured_value = scale * random.uniform(0.0, 1.0) # Create simulated data
data_points[v][h] = measured_value # Update data_points with the simulated data
# Update the heat map with the latest point.
# - I *think* I want to use im.set_data() here, not ax.imshow(), but how?
ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.draw()
# Create a colorbar
# - Except the colorbar here is wrong. It goes from -0.10 to +0.10 instead
# of matching the colorbar in the second imshow() plot, which goes from
# 0.0 to "scale". Why?
cbar = ax.figure.colorbar(im, ax=ax)
cbar.ax.set_ylabel('Default heatmap colorbar label')
plt.pause(0.0001) # In interactive mode, need a small delay to get the colorbar to appear
plt.show()
fig2, ax2 = plt.subplots() # So I have access to the figure and the axes of the plot.
im = ax2.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
ax2.set_title('Dummy title for plot with pseudo-data')
# Set up the X-axis ticks and label them
ax2.set_xticks(np.arange(len(h_points)))
ax2.set_xticklabels(h_points)
ax2.set_xlabel('Horizontal axis measurement values')
# Set up the Y-axis ticks and label them
ax2.set_yticks(np.arange(len(v_points)))
ax2.set_yticklabels(v_points)
ax2.set_ylabel('Vertical axis measurement values')
# Create a colorbar
cbar = ax2.figure.colorbar(im, ax=ax2)
cbar.ax.set_ylabel('Default heatmap colorbar label')
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.show()
dummy = input("In interactive mode, press the Enter key when you're done with the plots.")
OK, I Googled some more. More importantly, I Googled smarter and figured out my own answer.
To update my plot inside my nested loops, I was using the command:
ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
What I tried using to update my plot more efficiently was:
im.set_data(data_points)
What I should have used was:
im.set_data(data_points)
im.autoscale()
This updates the pixel scaling, which fixed both my "plot doesn't update" problem and my "colorbar has the wrong scale" problem.

matplotlib colorbar update/remove destroys axes layout

I have matplotlib embedded in a tkinter gui. With the seaborn heatmap function, I create a heatmap and a colorbar which works as I want it to when creating the first plot. However, if I plot again, this will not overwrite the colorbar but add another colorbar to my figure. I end up with to many colorbars this way.
The figure I create for the plot contains two axes:
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001F36C8C3390>, <matplotlib.axes._subplots.AxesSubplot object at 0x000001F36D6ABF98>]
the first one is the plot itself and the second the colorbar and looks like this:
plot 1
If I plot again, the result is:
plot 2
Simply deleting the colorbar with
self.fig.axes[1].remove()
before creating the next plot doesn't do the trick because it will just remove the colorbar but the layout of the plot keeps shrinking:
plot 3
plot 4
Note that the figure size stays the same but the size of the plot keeps getting smaller when I plot again and the colorbar moves futher to the left while the entire right part of the plot stays white.
As I create a tkinter gui, the ploting window is initialized when the program is first run.
self.fig = plt.figure.Figure( facecolor = "white", figsize = (7,4))
self.ax = self.fig.subplots()
self.x_data = x_data
self.y_data = y_data
when somebody presses a plot button the plot is created
def plot_on_plotframe(self):
self.ax.cla()
#executes required matplotlib layout
self.plotlayout[self.plot_type]()
print('plottype: {}'.format(self.plot_type))
#print('Plot xdata: {}'.format(self.x_data))
self.canvas.draw()
I need to make different types of plot and the proper type is selected by plotlayout:
self.ax = sns.heatmap(self.x_data, vmin=self.settings[2][0], vmax=self.settings[2][1], cmap='viridis', fmt=self.settings[0], annot=self.settings[1], linewidths=0.5, annot_kws={'size': 8}, ax = self.ax)
self.ax.set_xticklabels(self.ax.get_xticklabels(), rotation=0)
self.fig.tight_layout()
It would be fantastic if somebody could tell me why matplotlib messes with the layout even after I delete the old colorbar in the first place. I think this has something to do with the gridSpec but I don't get how I tell matplotlib to reset the layout properly. Also any other suggestions on how to resovle this?
Thanks in advance

Clear overlay scatter on matplotlib image

So I am back again with another silly question.
Consider this piece of code
x = linspace(-10,10,100);
[X,Y]=meshgrid(x,x)
g = np.exp(-(square(X)+square(Y))/2)
plt.imshow(g)
scat = plt.scatter(50,50,c='r',marker='+')
Is there a way to clear only the scatter point on the graph without clearing all the image?
In fact, I am writing a code where the appearance of the scatter point is bound with a Tkinter Checkbutton and I want it to appear/disappear when I click/unclick the button.
Thanks for your help!
The return handle of plt.scatter has several methods, including remove(). So all you need to do is call that. With your example:
x = np.linspace(-10,10,100);
[X,Y] = np.meshgrid(x,x)
g = np.exp(-(np.square(X) + np.square(Y))/2)
im_handle = plt.imshow(g)
scat = plt.scatter(50,50,c='r', marker='+')
# image, with scatter point overlayed
scat.remove()
plt.draw()
# underlying image, no more scatter point(s) now shown
# For completeness, can also remove the other way around:
plt.clf()
im_handle = plt.imshow(g)
scat = plt.scatter(50,50,c='r', marker='+')
# image with both components
im_handle.remove()
plt.draw()
# now just the scatter points remain.
(almost?) all matplotlib rendering functions return a handle, which have some method to remove the rendered item.
Note that you need the call to redraw to see the effects of remove() -- from the remove help (my emphasis):
Remove the artist from the figure if possible. The effect will not be
visible until the figure is redrawn, e.g., with
:meth:matplotlib.axes.Axes.draw_idle.

Autoscale a matplotlib Axes to make room for legend

I am plotting a 2D view of a spacecraft orbit using matplotlib. On this orbit, I identify and mark certain events, and then list these events and the corresponding dates in a legend. Before saving the figure to a file, I autozoom on my orbit plot, which causes the legend to be printed directly on top of my plot. What I would like to do is, after autoscaling, somehow find out the width of my legend, and then expand my xaxis to "make room" for the legend on the right side of the plot. Conceptually, something like this;
# ... code that generates my plot up here, then:
ax.autoscale_view()
leg = ax.get_legend()
leg_width = # Somehow get the width of legend in units that I can use to modify my axes
xlims = ax.get_xlim()
ax.set_xlim( [xlims[0], xlims[1] + leg_width] )
fig.savefig('myplot.ps',format='ps')
The main problem I'm having is that ax.set_xlim() takes "data" specific values, whereas leg.get_window_extent reports in window pixels (I think), and even that only after the canvas has been drawn, so I'm not sure how I can get the legend "width" in a way that I can use similar to above.
You can save the figure once to get the real legend location, and then use transData.inverted() to transform screen coordinate to data coordinate.
import pylab as pl
ax = pl.subplot(111)
pl.plot(pl.randn(1000), pl.randn(1000), label="ok")
leg = pl.legend()
pl.savefig("test.png") # save once to get the legend location
x,y,w,h = leg.get_window_extent().bounds
# transform from screen coordinate to screen coordinate
tmp1, tmp2 = ax.transData.inverted().transform([0, w])
print abs(tmp1-tmp2) # this is the with of legend in data coordinate
pl.savefig("test.png")

Categories