I have multiple lines to be drawn on the same axes, and each of them are dynamically updated (I use set_data), The issue being that i am not aware of the x and y limits of each of the lines. And axes.autoscale_view(True,True,True) / axes.set_autoscale_on(True) are not doing what they are supposed to. How do i auto scale my axes?
import matplotlib.pyplot as plt
fig = plt.figure()
axes = fig.add_subplot(111)
axes.set_autoscale_on(True)
axes.autoscale_view(True,True,True)
l1, = axes.plot([0,0.1,0.2],[1,1.1,1.2])
l2, = axes.plot([0,0.1,0.2],[-0.1,0,0.1])
#plt.show() #shows the auto scaled.
l2.set_data([0,0.1,0.2],[-1,-0.9,-0.8])
#axes.set_ylim([-2,2]) #this works, but i cannot afford to do this.
plt.draw()
plt.show() #does not show auto scaled
I have referred to these already, this , this.
In all cases I have come across, the x,y limits are known. I have multiple lines on the axes and their ranges change, keeping track of the ymax for the entire data is not practical
A little bit of exploring got me to this,
xmin,xmax,ymin,ymax = matplotlib.figure.FigureImage.get_extent(FigureImage)
But here again, i do not know how to access FigureImage from the Figure instance.
Using matplotlib 0.99.3
From the matplotlib docs for autoscale_view:
The data limits are not updated automatically when artist data are changed after the artist has been added to an Axes instance. In that case, use matplotlib.axes.Axes.relim() prior to calling autoscale_view.
So, you'll need to add two lines before your plt.draw() call after the set_data call:
axes.relim()
axes.autoscale_view(True,True,True)
Related
This is a slightly tricky one to explain. Basically, I want to make an inset plot and then utilize the convenience of mpl_toolkits.axes_grid1.inset_locator.mark_inset, but I want the data in the inset plot to be completely independent of the data in the parent axes.
Example code with the functions I'd like to use:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
data = np.random.normal(size=(2000,2000))
plt.imshow(data, origin='lower')
parent_axes = plt.gca()
ax2 = inset_axes(parent_axes, 1, 1)
ax2.plot([900,1100],[900,1100])
# I need more control over the position of the inset axes than is given by the inset_axes function
ip = InsetPosition(parent_axes,[0.7,0.7,0.3,0.3])
ax2.set_axes_locator(ip)
# I want to be able to control where the mark is connected to, independently of the data in the ax2.plot call
mark_inset(parent_axes, ax2, 2,4)
# plt.savefig('./inset_example.png')
plt.show()
The example code produces the following image:
So to sum up: The location of the blue box is entire controlled by the input data to ax2.plot(). I would like to manually place the blue box and enter whatever I want into ax2. Is this possible?
quick edit: to be clear, I understand why inset plots would have the data linked, as that's the most likely usage. So if there's a completely different way in matplotlib to accomplish this, do feel free to reply with that. However, I am trying to avoid manually placing boxes and lines to all of the axes I would place, as I need quite a few insets into a large image.
If I understand correctly, you want an arbitrarily scaled axis at a given position that looks like a zoomed inset, but has no connection to the inset marker's position.
Following your approach you can simply add another axes to the plot and position it at the same spot of the true inset, using the set_axes_locator(ip) function. Since this axis is drawn after the original inset, it will be on top of it and you'll only need to hide the tickmarks of the original plot to let it disappear completely (set_visible(False) does not work here, as it would hide the lines between the inset and the marker position).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes, mark_inset, InsetPosition
data = np.random.normal(size=(200,200))
plt.imshow(data, origin='lower')
parent_axes = plt.gca()
ax2 = inset_axes(parent_axes, 1, 1)
ax2.plot([60,75],[90,110])
# hide the ticks of the linked axes
ax2.set_xticks([])
ax2.set_yticks([])
#add a new axes to the plot and plot whatever you like
ax3 = plt.gcf().add_axes([0,0,1,1])
ax3.plot([0,3,4], [2,3,1], marker=ur'$\u266B$' , markersize=30, linestyle="")
ax3.set_xlim([-1,5])
ax3.set_ylim([-1,5])
ip = InsetPosition(parent_axes,[0.7,0.7,0.3,0.3])
ax2.set_axes_locator(ip)
# set the new axes (ax3) to the position of the linked axes
ax3.set_axes_locator(ip)
# I want to be able to control where the mark is connected to, independently of the data in the ax2.plot call
mark_inset(parent_axes, ax2, 2,4)
plt.show()
FWIW, I came up with a hack that works.
In the source code for inset_locator, I added a version of mark_inset that takes another set of axes used to define the TransformedBbox:
def mark_inset_hack(parent_axes, inset_axes, hack_axes, loc1, loc2, **kwargs):
rect = TransformedBbox(hack_axes.viewLim, parent_axes.transData)
pp = BboxPatch(rect, **kwargs)
parent_axes.add_patch(pp)
p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1, **kwargs)
inset_axes.add_patch(p1)
p1.set_clip_on(False)
p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2, **kwargs)
inset_axes.add_patch(p2)
p2.set_clip_on(False)
return pp, p1, p2
Then in my original-post code I make an inset axis where I want the box to be, pass it to my hacked function, and make it invisible:
# location of desired axes
axdesire = inset_axes(parent_axes,1,1)
axdesire.plot([100,200],[100,200])
mark_inset_hack(parent_axes, ax2, axdesire, 2,4)
axdesire.set_visible(False)
Now I have a marked box at a different location in data units than the inset that I'm marking:
It is certainly a total hack, and at this point I'm not sure it's cleaner than simply drawing lines manually, but I think for a lot of insets this will keep things conceptually cleaner.
Other ideas are still welcome.
I'm kind of confused what is going at the backend when I draw plots in matplotlib, tbh, I'm not clear with the hierarchy of plot, axes and figure. I read the documentation and it was helpful but I'm still confused...
The below code draws the same plot in three different ways -
#creating the arrays for testing
x = np.arange(1, 100)
y = np.sqrt(x)
#1st way
plt.plot(x, y)
#2nd way
ax = plt.subplot()
ax.plot(x, y)
#3rd way
figure = plt.figure()
new_plot = figure.add_subplot(111)
new_plot.plot(x, y)
Now my question is -
What is the difference between all the three, I mean what is going under the hood when any of the 3 methods are called?
Which method should be used when and what are the pros and cons of using any on those?
The names of objects
Matplotlib is strongly object oriented and its principal objects are the figure and the axes (I find the name axes a bit misleading, but probably it's just me).
You can think of the figure as a canvas, of which you typically specify the dimensions and possibly e.g., the background color etc etc. You use the canvas, the figure, essentially in two ways, placing other objects on it (mostly axes, but also text labels etc) and saving its contents with savefig.
You can think of an axes as a sort of Swiss Army knife, a handy object that offers a tool (e.g. .plot, .scatter, .hist etc) for everything, mostly. You can place one, two, ... many axes inside a figure using one of many different methods.
The plt interface
The plt procedural interface was originally developed to mimic the MATLAB™ interface but is not really different from the object oriented interface, even if you don't make a direct reference to the main objects (i.e., a figure and an axes) these objects are automatically instantiated and each plt method is, essentially, translated to a call of one of the methods of the underlying fundamental objects: e.g., a plt.plot() is a hidden_axes.plot and a plt.savefig is a hidden_figure.savefig.
In every moment you can have an handle on these hidden objects using plt.gcf and plt.gca, and this is sometimes necessary when one of the object methods has not been ported to a method in the plt namespace.
I'd like to add that the plt namespace contains also a number of convenience methods¹ to instantiate, in different ways, figure and axes.
Your examples
1st way
plt.plot(x, y)
Here you use only the plt interface, you can only use a single axes in each figure, but this is what you want when you are doing an exploration of your data,
a quick recipe that gets the work done...
2nd way
ax = plt.subplot()
ax.plot(x, y)
Here you use a convenience method in the plt namespace to give a name (and a handle) to your axes object, but btw there is also an hidden figure. You can later use the axes object to plot, to make an histogram etc, all things that you can do with the plt interface, but you can also access all its attributes and modify them with greater freedom.
3rd way
figure = plt.figure()
new_plot = figure.add_subplot(111)
new_plot.plot(x, y)
Here you start instantiating a figure using a convenience method in the plt namespace and later you use only the object oriented interface.
It is possible to bypass the plt convenience method (matplotlib.figure.Figure) but you then have to tweak the figure for a better interactive experience (after all, it's a convenience method).
Personal recommendations
I suggest bare plt.plot, plt.scatter in the context of an interactive session, possibly using IPython with its %matplotlib magic command, and also in the context of an exploratory Jupyter notebook.
On the other hand the object oriented approach, plus a few plt
convenience methods¹, is the way to go
if you have a permanent issue to solve once for all with a
customized arrangement of finely tuned subplots,
if you want to embed Matplotlib in the UI of a program you write.
There is a large gray area between these extremes and if you ask me what to do I'd just say "It depends"...
(1) The convenience methods in the plt name space are REALLY CONVENIENT! In particular, when you instantiate Figures and Axes using them, all the minute details needed to deal with interactive windows are automatically taken into account.
Method 1
plt.plot(x, y)
This lets you plot just one figure with (x,y) coordinates. If you just want to get one graphic, you can use this way.
Method 2
ax = plt.subplot()
ax.plot(x, y)
This lets you plot one or several figure(s) in the same window. As you write it, you will plot just one figure, but you can make something like this:
fig1, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
You will plot 4 figures which are named ax1, ax2, ax3 and ax4 each one but on the same window. This window will be just divided in 4 parts with my example.
Method 3
fig = plt.figure()
new_plot = fig.add_subplot(111)
new_plot.plot(x, y)
I didn't use it, but you can find documentation.
Example:
import numpy as np
import matplotlib.pyplot as plt
# Method 1 #
x = np.random.rand(10)
y = np.random.rand(10)
figure1 = plt.plot(x,y)
# Method 2 #
x1 = np.random.rand(10)
x2 = np.random.rand(10)
x3 = np.random.rand(10)
x4 = np.random.rand(10)
y1 = np.random.rand(10)
y2 = np.random.rand(10)
y3 = np.random.rand(10)
y4 = np.random.rand(10)
figure2, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
ax1.plot(x1,y1)
ax2.plot(x2,y2)
ax3.plot(x3,y3)
ax4.plot(x4,y4)
plt.show()
Other example:
Due to the 2nd answer of this question I supposed the following code
import matplotlib.pyplot as plt
for i1 in range(2):
plt.figure(1)
f, ax = plt.subplots()
plt.plot((0,3), (2, 2), 'b')
for i2 in range(2):
plt.figure(2)
f, ax = plt.subplots()
plt.plot([1,2,3], [1,2,3], 'r')
plt.savefig('foo_{}_bar_{}.jpg'.format(i2, i1))
plt.close()
plt.figure(1)
plt.plot( [1,2,3],[1,2,3], 'r')
plt.savefig('bar_{}.jpg'.format(i1))
plt.close()
to create plots bar_0.jpg and bar_1.jpg showing a blue and a red line each.
However, figures look like
instead of
How can I achieve the desired behaviour?
Note that plots foo_*.jpg have to be closed and saved during handling of the bar plots.
You're already saving the Axes objects, so instead of calling the PyPlot plot function (which draws on the last created or activated Axes), use the objects' plot function:
ax.plot(...)
If you then give both a different name, say ax1 and ax2, you can draw on the one you like without interfering with the other. All plt. commands also exist as an Axes member function, but sometimes the name changes (plt.xticks becomes ax.set_xticks for example). See the documentation of Axes for details.
To save to figures, use the Figure objects in the same way:
f.savefig(...)
This API type is only just coming to Matlab, FYI, and will probably replace the old-fashioned "draw on the last active plot" behaviour in the future. The object-oriented approach here is more flexible with minimal overhead, so I strongly recommend you use it everywhere.
If unsure, better to make it explicit:
import matplotlib.pyplot as plt
for i1 in range(2):
fig1,ax1 = plt.subplots()
fig2,ax2 = plt.subplots()
ax1.plot([0,4],[2,2],'b')
for i2 in range(2):
ax2.plot([1,2,3],[1,2,3],'r')
fig2.savefig('abc{}.png'.format(2*i1+i2))
plt.figure(1)
ax1.plot([1,2,3],[1,2,3],'r')
fig1.savefig('cba{}.png'.format(i1))
I have found that I can not get axes autoscale to work on the 1st axes after creating a second axes using twinx. Is this expected?
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = 0.05 * x**2
y2 = -1 *y1
fig, axL = plt.subplots() # Make Left Axes
axR = axL.twinx() # Make Left Axes
axL.plot(x, y1, 'g-') # Plot on Left
axL.grid()
axL.autoscale(enable=True, axis=u'both', tight=False)
plt.show()
# Do some stuff then later plot on axR
When I run the above code it autoscales in the y-direction correctly on the left axes (0 to 5) but changes the X-Axis scale to +/- 0.06 instead of the correct 0 to 10. However, once axR is no longer blank and something is plotted on axR it behaves as I would expect.
This is only an example as I first came across this issue in more complicated PyQT4 GUI that allows the user to create multiple subplots & left/right combinations. Since the user is the one manually controlling the plot creation order it is possible for the above situation to present itself.
Is there a way for autoscale to work with a blank twinx right axes. Or is the Xlimit just going to have to be manually set?
FYI, I am using Python 3.4 as part of Anaconda v2.0.1 with Matplotlib v1.3.1
Thanks.
This is merely a workaround than a proper solution or explanation.
Simply add an invisible point in the right axes so it is not completely empty:
axR.plot(0, 0, visible=False)
You have to make sure though, that the invisible point lies within the ranges of the data that you plot in axL. E.g.:
axR.plot(np.mean(x),np.mean(y1),visible=False)
As for an explanation (I'm guessing):
axR.dataLim is [-np.inf, np.inf] initially. The union of axR.dataLim and axL.dataLim still gives [-np.inf, np.inf] which is then collapsed to [0,0].
EDIT: This was fixed recently (here). Upgrading to matplotlib v1.4.* should solve the problem.
Matplotlib newbie here.
I have the following code:
from pylab import figure, show
import numpy
fig = figure()
ax = fig.add_subplot(111)
plot_data=[1.7,1.7,1.7,1.54,1.52]
xdata = range(len(plot_data))
labels = ["2009-June","2009-Dec","2010-June","2010-Dec","2011-June"]
ax.plot(xdata,plot_data,"b-")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_yticks([1.4,1.6,1.8])
fig.canvas.draw()
show()
When you run that code, the resulting chart has a run-in with the first tick label (2009-June) and the origin. How can I get the graph to move over to make that more readable? I tried to put dummy data in, but then Matplotlib (correctly) treats that as data.
add two limits to the x and y axes to shift the tick labels a bit.
# grow the y axis down by 0.05
ax.set_ylim(1.35, 1.8)
# expand the x axis by 0.5 at two ends
ax.set_xlim(-0.5, len(labels)-0.5)
the result is
Because tick labels are text objects you can change their alignment. However to get access to the text properties you need to go through the set_yticklabels function. So add the line:
ax.set_yticklabels([1.4,1.6,1.8],va="bottom")
after your set_yticks call. Alternatively if you go through the pylab library directly, instead of accessing the function through the axes object, you can just set that in one line:
pylab.yticks([1.4,1.6,1.8],va="bottom")
I suggest change Y axis limits:
ax.set_ylim([1.2, 1.8])