matplotlib - How do you keep the axes constant while adding new data? - python

I'm using matplotlib to display data that is constantly being updated (changes roughly 10 times per second). I'm using a 3D scatter plot, and I would like the axes to be fixed to a specific range, since the location of the data with respect to the edges of the plot is what is important.
Currently whenever I add new data, the axes will reset to being scaled by the data, rather than the size I want (when I have hold=False). If I set hold=True, the axes will remain the right size, but the new data will be overlayed on the old data, which is not what I want.
I can get it to work if I rescale the axes everytime I get new data, but this seems like an inefficient way to do this, especially since I need to do all other formatting again as well (adding titles, legends, etc)
Is there some way in which I can specify the properties of the plot just once, and this will remain fixed as I add new data?
Here is a rough outline of my code, to help explain what I mean:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
X_MAX = 50
Y_MAX = 50
Z_MAX = 50
fig = plt.figure(1)
ax = fig.add_subplot(111, projection='3d')
ax.set_title("My Title")
ax.set_xlim3d([0, X_MAX])
ax.set_ylim3d([0, Y_MAX])
ax.set_zlim3d([0, Z_MAX])
ax.set_autoscale_on(False)
# This is so the new data replaces the old data
# seems to be replacing the axis ranges as well, maybe a different method should be used?
ax.hold(False)
plt.ion()
plt.show()
a = 0
while a < 50:
a += 1
ax.scatter( a, a/2+1, 3, s=1 )
# If I don't set the title and axes ranges again here, they will be reset each time
# I want to know if there is a way to only set them once and have it persistent
ax.set_title("My Title")
ax.set_xlim3d([0, X_MAX])
ax.set_ylim3d([0, Y_MAX])
ax.set_zlim3d([0, Z_MAX])
plt.pause(0.001)
EDIT:
1. I have also tried ax.set_autoscale_on(False), but with no success
2. I tried this with a regular 2D scatter plot, and the same issue still exists
3. Found a related question which also still doesn't have an answer

I would do something like this (note removal of hold(False) ):
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
X_MAX = 50
Y_MAX = 50
Z_MAX = 50
fig = plt.figure(1)
ax = fig.add_subplot(111, projection='3d')
ax.set_title("My Title")
ax.set_xlim3d([0, X_MAX])
ax.set_ylim3d([0, Y_MAX])
ax.set_zlim3d([0, Z_MAX])
ax.set_autoscale_on(False)
plt.ion()
plt.show()
a = 0
sct = None
while a < 50:
a += 1
if sct is not None:
sct.remove()
sct = ax.scatter( a, a/2+1, 3, s=1 )
fig.canvas.draw()
plt.pause(0.001)
Where you remove just the added scatter plot each time through the loop.

Related

Animating a function where function parameters change with time using FuncAnimation

I am trying to animate a one-dimensional function where the function inputs are same but function parameters are changing with time. The function I am trying to animate is
f(x)=sin(a* pi * x)/(b*x)+ (x-1)^4
Here the data to be plotted is same, but a, b are changing with every update.I am using python and matplotlib library. My initial attempt is as follows:
fig,ax = plt.subplots()
line, = ax.plot([],[])
def animate(i,func_params):
x = np.linspace(-0.5,2.5,num = 200)
a=func_params[i][0]
b=func_params[i][1]
y=np.sin(a*math.pi*x)/b*x + (x-1)**4
line.set_xdata(x)
line.set_ydata(y)
return line,
ani = animation.FuncAnimation(fig,animate,frames=len(visualize_pop),fargs=(visualize_func,),interval = 100,blit=True)
plt.show()
The above code is not plotting anything.
EDIT: Updated code based on comment.
Your problem is that with plot([],[]) you give matplotlib no data and therefore no way do determine the limits of the axes. Therefore it uses some default values which are way out of the range of the data you actually want to plot. Therefore you have two choices:
1) Set the limits to some values that will contain all your plotted data for all cases,
e.g.
ax.set_xlim([-0.5,2.5])
ax.set_ylim([-2,6])
2) Let ax compute the limits automatically each frame and re-scale the plot see here using these two commands within your animate function (note that this option only works correctly if you turn blitting off):
ax.relim()
ax.autoscale_view()
Here still a completely working version of your code (the commands for solution (1) are commented out and I changed some of the notations):
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig,ax = plt.subplots()
x = np.linspace(-0.5,2.5,num = 200)
line, = ax.plot([],[])
#ax.set_xlim([-0.5,2.5])
#ax.set_ylim([-2,6])
##assuming some parameters, because none were given by the OP:
N = 20
func_args = np.array([np.linspace(1,2,N), np.linspace(2,1,N)])
def animate(i,func_params):
a=func_params[0,i]
b=func_params[1,i]
y=np.sin(a*np.pi*x)/b*x + (x-1)**4
line.set_xdata(x)
line.set_ydata(y)
ax.relim()
ax.autoscale_view()
return line, ax
##blit=True will not update the axes labels correctly
ani = FuncAnimation(
fig,animate,frames=N, fargs=(func_args,),interval = 100 #, blit=True
)
plt.show()

How to change xticks in matplotlib with locator.param

I am drawing figures for scientific article.
In such article the room is scarse and the figures must be readable. Therefore an x axis must have 3 or 4 ticks maximum. The default behaviour for matplotlib is to have overlapping plenty of xticks label. It is very painful and time consuming to manually adjust the exact ticks needed, trying to prevent overlapping. I find the command ax.locator_params that seem to do the job, but it has a weird behaviour, and place ticks at unwanted position outside of the data limit.
Here is a code exploring 4 different ways to change ticks. The last two are not using locator_param(), but are bulky and doesn't work in every situation. How can I make work locator_param() or use something painless ?
Regards,
import numpy as np
import matplotlib.pyplot as plt
plt.close('all')
x = np.linspace(-0.7,0.7,num = 50)
y = np.linspace(0,1,num = 100)
ext = (x[0],x[-1],y[-1],y[0])
xx,yy = np.meshgrid(x,y,indexing = 'ij')
U = np.cos(xx**2 + yy**2)
fig, ax_l = plt.subplots(1,4)
fig.set_size_inches(4*3.45, 3, forward=True)
for ax in ax_l :
ax.set_xlabel('x')
ax.set_ylabel('y')
im = ax.imshow(U,interpolation = 'nearest', extent = ext, aspect = 'equal')
### method 1 ####
ax_l[0].locator_params(axis = 'x',tight=True, nbins=3)
### method 2 ####
ax_l[1].locator_params(axis = 'x',tight=False, nbins=3)
### method 3 ####
ticks = ax_l[2].get_xticks()
ax_l[2].set_xticks(ticks[0::2])
### method 4 ####
x_lim = ax_l[3].get_xlim()
ax_l[3].set_xticks(np.linspace(x_lim[0],x_lim[1],num=3))
fig.tight_layout()
plt.show()

matplotlib plotting in loop, removing colorbar but whitespace remains

My code is something (roughly) like this:
UPDATE: I've redone this with some actual mock-up code that reflects my general problem. Also, realized that the colorbar creation is in the actual loop as otherwise there's nothing to map it to. Sorry for the code before, typed it up in frantic desperation at the very end of the workday :).
import numpy
import matplotlib as mplot
import matplotlib.pyplot as plt
import os
#make some mock data
x = np.linspace(1,2, 100)
X, Y = np.meshgrid(x, x)
Z = plt.mlab.bivariate_normal(X,Y,1,1,0,0)
fig = plt.figure()
ax = plt.axes()
'''
Do some figure-related stuff that take up a lot of time,
I want to avoid having to do them in the loop over and over again.
They hinge on the presence of fig so I can't make
new figure to save each time or something, I'd have to do
them all over again.
'''
for i in range(1,1000):
plotted = plt.plot(X,Y,Z)
cbar = plt.colorbar(ax=ax, orientation = 'horizontal')
plt.savefig(os.path.expanduser(os.path.join('~/', str(i))))
plt.draw()
mplot.figure.Figure.delaxes(fig, fig.axes[1]) #deletes but whitespace remains
'''
Here I need something to remove the colorbar otherwise
I end up with +1 colorbar on my plot at every iteration.
I've tried various things to remove it BUT it keeps adding whitespace instead
so doesn't actually fix anything.
'''
Has anyone come across this problem before and managed to fix it? Hopefully this is enough
for an idea of the problem, I can post more code if needed but thought it'd be less of a clutter if I just give an overview example.
Thanks.
colorbar() allows you explicitly set which axis to render into - you can use this to ensure that they always appear in the same place, and not steal any space from another axis. Furthermore, you could reset the .mappable attribute of an existing colorbar, rather than redefine it each time.
Example with explicit axes:
x = np.linspace(1,2, 100)
X, Y = np.meshgrid(x, x)
Z = plt.mlab.bivariate_normal(X,Y,1,1,0,0)
fig = plt.figure()
ax1 = fig.add_axes([0.1,0.1,0.8,0.7])
ax2 = fig.add_axes([0.1,0.85,0.8,0.05])
...
for i in range(1,5):
plotted = ax1.pcolor(X,Y,Z)
cbar = plt.colorbar(mappable=plotted, cax=ax2, orientation = 'horizontal')
#note "cax" instead of "ax"
plt.savefig(os.path.expanduser(os.path.join('~/', str(i))))
plt.draw()
I had a very similar problem, which I finally managed to solve by defining a colorbar axes in a similar fashion to:
Multiple imshow-subplots, each with colorbar
The advantage compared to mdurant's answer is that it saves defining the axes location manually.
import matplotlib.pyplot as plt
import IPython.display as display
from mpl_toolkits.axes_grid1 import make_axes_locatable
from pylab import *
%matplotlib inline
def plot_res(ax,cax):
plotted=ax.imshow(rand(10, 10))
cbar=plt.colorbar(mappable=plotted,cax=cax)
fig, axarr = plt.subplots(2, 2)
cax1 = make_axes_locatable(axarr[0,0]).append_axes("right", size="10%", pad=0.05)
cax2 = make_axes_locatable(axarr[0,1]).append_axes("right", size="10%", pad=0.05)
cax3 = make_axes_locatable(axarr[1,0]).append_axes("right", size="10%", pad=0.05)
cax4 = make_axes_locatable(axarr[1,1]).append_axes("right", size="10%", pad=0.05)
# plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0.3, hspace=0.3)
N=10
for j in range(N):
plot_res(axarr[0,0],cax1)
plot_res(axarr[0,1],cax2)
plot_res(axarr[1,0],cax3)
plot_res(axarr[1,1],cax4)
display.clear_output(wait=True)
display.display(plt.gcf())
display.clear_output(wait=True)

Moving matplotlib legend outside of the axis makes it cutoff by the figure box

I'm familiar with the following questions:
Matplotlib savefig with a legend outside the plot
How to put the legend out of the plot
It seems that the answers in these questions have the luxury of being able to fiddle with the exact shrinking of the axis so that the legend fits.
Shrinking the axes, however, is not an ideal solution because it makes the data smaller making it actually more difficult to interpret; particularly when its complex and there are lots of things going on ... hence needing a large legend
The example of a complex legend in the documentation demonstrates the need for this because the legend in their plot actually completely obscures multiple data points.
http://matplotlib.sourceforge.net/users/legend_guide.html#legend-of-complex-plots
What I would like to be able to do is dynamically expand the size of the figure box to accommodate the expanding figure legend.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
ax.grid('on')
Notice how the final label 'Inverse tan' is actually outside the figure box (and looks badly cutoff - not publication quality!)
Finally, I've been told that this is normal behaviour in R and LaTeX, so I'm a little confused why this is so difficult in python... Is there a historical reason? Is Matlab equally poor on this matter?
I have the (only slightly) longer version of this code on pastebin http://pastebin.com/grVjc007
Sorry EMS, but I actually just got another response from the matplotlib mailling list (Thanks goes out to Benjamin Root).
The code I am looking for is adjusting the savefig call to:
fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable
This is apparently similar to calling tight_layout, but instead you allow savefig to consider extra artists in the calculation. This did in fact resize the figure box as desired.
import matplotlib.pyplot as plt
import numpy as np
plt.gcf().clear()
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
handles, labels = ax.get_legend_handles_labels()
lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1))
text = ax.text(-0.2,1.05, "Aribitrary text", transform=ax.transAxes)
ax.set_title("Trigonometry")
ax.grid('on')
fig.savefig('samplefigure', bbox_extra_artists=(lgd,text), bbox_inches='tight')
This produces:
[edit] The intent of this question was to completely avoid the use of arbitrary coordinate placements of arbitrary text as was the traditional solution to these problems. Despite this, numerous edits recently have insisted on putting these in, often in ways that led to the code raising an error. I have now fixed the issues and tidied the arbitrary text to show how these are also considered within the bbox_extra_artists algorithm.
Added: I found something that should do the trick right away, but the rest of the code below also offers an alternative.
Use the subplots_adjust() function to move the bottom of the subplot up:
fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot.
Then play with the offset in the legend bbox_to_anchor part of the legend command, to get the legend box where you want it. Some combination of setting the figsize and using the subplots_adjust(bottom=...) should produce a quality plot for you.
Alternative:
I simply changed the line:
fig = plt.figure(1)
to:
fig = plt.figure(num=1, figsize=(13, 13), dpi=80, facecolor='w', edgecolor='k')
and changed
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
to
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,-0.02))
and it shows up fine on my screen (a 24-inch CRT monitor).
Here figsize=(M,N) sets the figure window to be M inches by N inches. Just play with this until it looks right for you. Convert it to a more scalable image format and use GIMP to edit if necessary, or just crop with the LaTeX viewport option when including graphics.
Here is another, very manual solution. You can define the size of the axis and paddings are considered accordingly (including legend and tickmarks). Hope it is of use to somebody.
Example (axes size are the same!):
Code:
#==================================================
# Plot table
colmap = [(0,0,1) #blue
,(1,0,0) #red
,(0,1,0) #green
,(1,1,0) #yellow
,(1,0,1) #magenta
,(1,0.5,0.5) #pink
,(0.5,0.5,0.5) #gray
,(0.5,0,0) #brown
,(1,0.5,0) #orange
]
import matplotlib.pyplot as plt
import numpy as np
import collections
df = collections.OrderedDict()
df['labels'] = ['GWP100a\n[kgCO2eq]\n\nasedf\nasdf\nadfs','human\n[pts]','ressource\n[pts]']
df['all-petroleum long name'] = [3,5,2]
df['all-electric'] = [5.5, 1, 3]
df['HEV'] = [3.5, 2, 1]
df['PHEV'] = [3.5, 2, 1]
numLabels = len(df.values()[0])
numItems = len(df)-1
posX = np.arange(numLabels)+1
width = 1.0/(numItems+1)
fig = plt.figure(figsize=(2,2))
ax = fig.add_subplot(111)
for iiItem in range(1,numItems+1):
ax.bar(posX+(iiItem-1)*width, df.values()[iiItem], width, color=colmap[iiItem-1], label=df.keys()[iiItem])
ax.set(xticks=posX+width*(0.5*numItems), xticklabels=df['labels'])
#--------------------------------------------------
# Change padding and margins, insert legend
fig.tight_layout() #tight margins
leg = ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0)
plt.draw() #to know size of legend
padLeft = ax.get_position().x0 * fig.get_size_inches()[0]
padBottom = ax.get_position().y0 * fig.get_size_inches()[1]
padTop = ( 1 - ax.get_position().y0 - ax.get_position().height ) * fig.get_size_inches()[1]
padRight = ( 1 - ax.get_position().x0 - ax.get_position().width ) * fig.get_size_inches()[0]
dpi = fig.get_dpi()
padLegend = ax.get_legend().get_frame().get_width() / dpi
widthAx = 3 #inches
heightAx = 3 #inches
widthTot = widthAx+padLeft+padRight+padLegend
heightTot = heightAx+padTop+padBottom
# resize ipython window (optional)
posScreenX = 1366/2-10 #pixel
posScreenY = 0 #pixel
canvasPadding = 6 #pixel
canvasBottom = 40 #pixel
ipythonWindowSize = '{0}x{1}+{2}+{3}'.format(int(round(widthTot*dpi))+2*canvasPadding
,int(round(heightTot*dpi))+2*canvasPadding+canvasBottom
,posScreenX,posScreenY)
fig.canvas._tkcanvas.master.geometry(ipythonWindowSize)
plt.draw() #to resize ipython window. Has to be done BEFORE figure resizing!
# set figure size and ax position
fig.set_size_inches(widthTot,heightTot)
ax.set_position([padLeft/widthTot, padBottom/heightTot, widthAx/widthTot, heightAx/heightTot])
plt.draw()
plt.show()
#--------------------------------------------------
#==================================================
I tried a very simple way, just make the figure a bit wider:
fig, ax = plt.subplots(1, 1, figsize=(a, b))
adjust a and b to a proper value such that the legend is included in the figure

Save a subplot in matplotlib

Is it possible to save (to a png) an individual subplot in a matplotlib figure? Let's say I have
import pyplot.matplotlib as plt
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
ax1.plot([1,2,3],[4,5,6])
ax2.plot([3,4,5],[7,8,9])
Is it possible to save each of the two subplots to different files or at least copy them separately to a new figure to save them?
I am using version 1.0.0 of matplotlib on RHEL 5.
While #Eli is quite correct that there usually isn't much of a need to do it, it is possible. savefig takes a bbox_inches argument that can be used to selectively save only a portion of a figure to an image.
Here's a quick example:
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
# Make an example plot with two subplots...
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax1.plot(range(10), 'b-')
ax2 = fig.add_subplot(2,1,2)
ax2.plot(range(20), 'r^')
# Save the full figure...
fig.savefig('full_figure.png')
# Save just the portion _inside_ the second axis's boundaries
extent = ax2.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
fig.savefig('ax2_figure.png', bbox_inches=extent)
# Pad the saved area by 10% in the x-direction and 20% in the y-direction
fig.savefig('ax2_figure_expanded.png', bbox_inches=extent.expanded(1.1, 1.2))
The full figure:
Area inside the second subplot:
Area around the second subplot padded by 10% in the x-direction and 20% in the y-direction:
Applying the full_extent() function in an answer by #Joe 3 years later from here, you can get exactly what the OP was looking for. Alternatively, you can use Axes.get_tightbbox() which gives a little tighter bounding box
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
from matplotlib.transforms import Bbox
def full_extent(ax, pad=0.0):
"""Get the full extent of an axes, including axes labels, tick labels, and
titles."""
# For text objects, we need to draw the figure first, otherwise the extents
# are undefined.
ax.figure.canvas.draw()
items = ax.get_xticklabels() + ax.get_yticklabels()
# items += [ax, ax.title, ax.xaxis.label, ax.yaxis.label]
items += [ax, ax.title]
bbox = Bbox.union([item.get_window_extent() for item in items])
return bbox.expanded(1.0 + pad, 1.0 + pad)
# Make an example plot with two subplots...
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax1.plot(range(10), 'b-')
ax2 = fig.add_subplot(2,1,2)
ax2.plot(range(20), 'r^')
# Save the full figure...
fig.savefig('full_figure.png')
# Save just the portion _inside_ the second axis's boundaries
extent = full_extent(ax2).transformed(fig.dpi_scale_trans.inverted())
# Alternatively,
# extent = ax.get_tightbbox(fig.canvas.renderer).transformed(fig.dpi_scale_trans.inverted())
fig.savefig('ax2_figure.png', bbox_inches=extent)
I'd post a pic but I lack the reputation points

Categories

Resources