Here is a toy piece of code that illustrates my problem:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], '-o', animated=True)
def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
ax.set_xlim(np.amin(xdata), np.amax(xdata))
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
plt.show()
If I set blit=True then the data points are plotted just how I want them. However, the x-axis labels/ticks remain static.
If I set blit=False then the x-axis labels and ticks update just how I want them. However, none of the data points are ever plotted.
How can I get both the plotted data (sine curve) and the x-asis data to update"?
First concerning blitting: Blitting is only applied to the content of the axes. It will affect the inner part of the axes, but not the outer axes decorators. Hence if using blit=True the axes decorators are not updated. Or inversely put, if you want the scale to update, you need to use blit=False.
Now, in the case from the question this leads to the line not being drawn. The reason is that the line has its animated attribute set to True. However, "animated" artists are not drawn by default. This property is actually meant to be used for blitting; but if no blitting is performed it will result in the artist neither be drawn nor blitted. It might have been a good idea to call this property blit_include or something similar to avoid confusion from its name.
Unfortunately, it looks like it's also not well documented. You find however a comment in the source code saying
# if the artist is animated it does not take normal part in the
# draw stack and is not expected to be drawn as part of the normal
# draw loop (when not saving) so do not propagate this change
So in total, one can ignore the presence of this argument, unless you use blitting. Even when using blitting, it can be ignored in most cases, because that property is set internally anyways.
To conclude the solution here is to not use animated and to not use blit.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], '-o')
def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
ax.set_xlim(np.amin(xdata), np.amax(xdata))
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init)
plt.show()
Related
I want to draw a figure in matplotib where the axis are displayed within the plot itself not on the side
I have tried the following code from here:
import math
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
a = []
for item in x:
a.append(1/(1+math.exp(-item)))
return a
x = np.arange(-10., 10., 0.2)
sig = sigmoid(x)
plt.plot(x,sig)
plt.show()
The above code displays the figure like this:
What I would like to draw is something as follows (image from Wikipedia)
This question describes a similar problem, but it draws a reference line in the middle but no axis.
One way to do it is using spines:
import math
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
a = []
for item in x:
a.append(1/(1+math.exp(-item)))
return a
x = np.arange(-10., 10., 0.2)
sig = sigmoid(x)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Move left y-axis and bottom x-axis to centre, passing through (0,0)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
# Eliminate upper and right axes
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# Show ticks in the left and lower axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
plt.plot(x,sig)
plt.show()
shows:
Basically, I want to comment on the accepted answer (but my rep doesn't allow that).
The use of
ax.spines['bottom'].set_position('center')
draws the x-axes such that it intersect the y-axes in its center. In case of asymmetric ylim this means that x-axis passes NOT through y=0. Jblasco's answer has this drawback, the intersect is at y=0.5 (the center between ymin=0.0 and ymax=1.0)
However, the reference plot of the original question has axes that intersect each other at 0.0 (which is somehow conventional or at least common).
To achieve this behaviour,
ax.spines['bottom'].set_position('zero')
has to be used.
See the following example, where 'zero' makes the axes intersect at 0.0 despite asymmetrically ranges in both x and y.
import numpy as np
import matplotlib.pyplot as plt
#data generation
x = np.arange(-10,20,0.2)
y = 1.0/(1.0+np.exp(-x)) # nunpy does the calculation elementwise for you
fig, [ax0, ax1] = plt.subplots(ncols=2, figsize=(8,4))
# Eliminate upper and right axes
ax0.spines['top'].set_visible(False)
ax0.spines['right'].set_visible(False)
# Show ticks on the left and lower axes only
ax0.xaxis.set_tick_params(bottom='on', top='off')
ax0.yaxis.set_tick_params(left='on', right='off')
# Move remaining spines to the center
ax0.set_title('center')
ax0.spines['bottom'].set_position('center') # spine for xaxis
# - will pass through the center of the y-values (which is 0)
ax0.spines['left'].set_position('center') # spine for yaxis
# - will pass through the center of the x-values (which is 5)
ax0.plot(x,y)
# Eliminate upper and right axes
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
# Show ticks on the left and lower axes only (and let them protrude in both directions)
ax1.xaxis.set_tick_params(bottom='on', top='off', direction='inout')
ax1.yaxis.set_tick_params(left='on', right='off', direction='inout')
# Make spines pass through zero of the other axis
ax1.set_title('zero')
ax1.spines['bottom'].set_position('zero')
ax1.spines['left'].set_position('zero')
ax1.set_ylim(-0.4,1.0)
# No ticklabels at zero
ax1.set_xticks([-10,-5,5,10,15,20])
ax1.set_yticks([-0.4,-0.2,0.2,0.4,0.6,0.8,1.0])
ax1.plot(x,y)
plt.show()
Final remark: If ax.spines['bottom'].set_position('zero') is used but zerois not within the plotted y-range, then the axes is shown at the boundary of the plot closer to zero.
The title of this question is how to draw the spine in the middle and the accepted answer does exactly that but what you guys draw is the sigmoid function and that one passes through y=0.5. So I think what you want is the spine centered according to your data. Matplotlib offers the spine position data for that (see documentation)
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
sigmoid = np.vectorize(sigmoid) #vectorize function
values=np.linspace(-10, 10) #generate values between -10 and 10
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
#spine placement data centered
ax.spines['left'].set_position(('data', 0.0))
ax.spines['bottom'].set_position(('data', 0.0))
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
plt.plot(values, sigmoid(values))
plt.show()
Looks like this (Github):
You can simply add:
plt.axhline()
plt.axvline()
It's not fixed to the center, but it does the job very easily.
Working example:
import matplotlib.pyplot as plt
import numpy as np
def f(x):
return np.sin(x) / (x/100)
delte = 100
Xs = np.arange(-delte, +delte +1, step=0.01)
Ys = np.array([f(x) for x in Xs])
plt.axhline(color='black', lw=0.5)
plt.axvline(color='black', lw=0.5)
plt.plot(Xs, Ys)
plt.show()
If you use matplotlib >= 3.4.2, you can use Pandas syntax and do it in only one line:
plt.gca().spines[:].set_position('center')
You might find it cleaner to do it in 3 lines:
ax = plt.gca()
ax.spines[['top', 'right']].set_visible(False)
ax.spines[['left', 'bottom']].set_position('center')
See documentation here.
Check your matplotlib version with pip freeze and update it with pip install -U matplotlib.
According to latest MPL Documentation:
ax = plt.axes()
ax.spines.left.set_position('zero')
ax.spines.bottom.set_position('zero')
I would like to generate a centered figure legend for subplot(s), for which there is a single label. For my actual use case, the number of subplot(s) is greater than or equal to one; it's possible to have a 2x2 grid of subplots and I would like to use the figure-legend instead of using ax.legend(...) since the same single label entry will apply to each/every subplot.
As a brief and simplified example, consider the code just below:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
fig.subplots_adjust(bottom=0.15)
fig.legend(mode='expand', loc='lower center')
plt.show()
plt.close(fig)
This code will generate the figure seen below:
I would like to use the mode='expand' kwarg to make the legend span the entire width of the subplot(s); however, doing so prevents the label from being centered. As an example, removing this kwarg from the code outputs the following figure.
Is there a way to use both mode='expand' and also have the label be centered (since there is only one label)?
EDIT:
I've tried using the bbox_to_anchor kwargs (as suggested in the docs) as an alternative to mode='expand', but this doesn't work either. One can switch out the fig.legend(...) line for the line below to test for yourself.
fig.legend(loc='lower center', bbox_to_anchor=(0, 0, 1, 0.5))
The handles and labels are flush against the left side of the legend. There is no mechanism to allow for aligning them.
A workaround could be to use 3 columns of legend handles and fill the first and third with a transparent handle.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
line, = ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
proxy = plt.Rectangle((0,0),1,1, alpha=0)
fig.legend(handles=[proxy, line, proxy], mode='expand', loc='lower center', ncol=3)
plt.show()
I'm having issues with redrawing the figure here. I allow the user to specify the units in the time scale (x-axis) and then I recalculate and call this function plots(). I want the plot to simply update, not append another plot to the figure.
def plots():
global vlgaBuffSorted
cntr()
result = collections.defaultdict(list)
for d in vlgaBuffSorted:
result[d['event']].append(d)
result_list = result.values()
f = Figure()
graph1 = f.add_subplot(211)
graph2 = f.add_subplot(212,sharex=graph1)
for item in result_list:
tL = []
vgsL = []
vdsL = []
isubL = []
for dict in item:
tL.append(dict['time'])
vgsL.append(dict['vgs'])
vdsL.append(dict['vds'])
isubL.append(dict['isub'])
graph1.plot(tL,vdsL,'bo',label='a')
graph1.plot(tL,vgsL,'rp',label='b')
graph2.plot(tL,isubL,'b-',label='c')
plotCanvas = FigureCanvasTkAgg(f, pltFrame)
toolbar = NavigationToolbar2TkAgg(plotCanvas, pltFrame)
toolbar.pack(side=BOTTOM)
plotCanvas.get_tk_widget().pack(side=TOP)
You essentially have two options:
Do exactly what you're currently doing, but call graph1.clear() and graph2.clear() before replotting the data. This is the slowest, but most simplest and most robust option.
Instead of replotting, you can just update the data of the plot objects. You'll need to make some changes in your code, but this should be much, much faster than replotting things every time. However, the shape of the data that you're plotting can't change, and if the range of your data is changing, you'll need to manually reset the x and y axis limits.
To give an example of the second option:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)
# You probably won't need this if you're embedding things in a tkinter plot...
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y, 'r-') # Returns a tuple of line objects, thus the comma
for phase in np.linspace(0, 10*np.pi, 500):
line1.set_ydata(np.sin(x + phase))
fig.canvas.draw()
fig.canvas.flush_events()
You can also do like the following:
This will draw a 10x1 random matrix data on the plot for 50 cycles of the for loop.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
for i in range(50):
y = np.random.random([10,1])
plt.plot(y)
plt.draw()
plt.pause(0.0001)
plt.clf()
This worked for me. Repeatedly calls a function updating the graph every time.
import matplotlib.pyplot as plt
import matplotlib.animation as anim
def plot_cont(fun, xmax):
y = []
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
def update(i):
yi = fun()
y.append(yi)
x = range(len(y))
ax.clear()
ax.plot(x, y)
print i, ': ', yi
a = anim.FuncAnimation(fig, update, frames=xmax, repeat=False)
plt.show()
"fun" is a function that returns an integer.
FuncAnimation will repeatedly call "update", it will do that "xmax" times.
This worked for me:
from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
clear_output(wait=True)
y = np.random.random([10,1])
plt.plot(y)
plt.show()
I have released a package called python-drawnow that provides functionality to let a figure update, typically called within a for loop, similar to Matlab's drawnow.
An example usage:
from pylab import figure, plot, ion, linspace, arange, sin, pi
def draw_fig():
# can be arbitrarily complex; just to draw a figure
#figure() # don't call!
plot(t, x)
#show() # don't call!
N = 1e3
figure() # call here instead!
ion() # enable interactivity
t = linspace(0, 2*pi, num=N)
for i in arange(100):
x = sin(2 * pi * i**2 * t / 100.0)
drawnow(draw_fig)
This package works with any matplotlib figure and provides options to wait after each figure update or drop into the debugger.
In case anyone comes across this article looking for what I was looking for, I found examples at
How to visualize scalar 2D data with Matplotlib?
and
http://mri.brechmos.org/2009/07/automatically-update-a-figure-in-a-loop
(on web.archive.org)
then modified them to use imshow with an input stack of frames, instead of generating and using contours on the fly.
Starting with a 3D array of images of shape (nBins, nBins, nBins), called frames.
def animate_frames(frames):
nBins = frames.shape[0]
frame = frames[0]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
for k in range(nBins):
frame = frames[k]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
del tempCS1
fig.canvas.draw()
#time.sleep(1e-2) #unnecessary, but useful
fig.clf()
fig = plt.figure()
ax = fig.add_subplot(111)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate_frames, frames)
I also found a much simpler way to go about this whole process, albeit less robust:
fig = plt.figure()
for k in range(nBins):
plt.clf()
plt.imshow(frames[k],cmap=plt.cm.gray)
fig.canvas.draw()
time.sleep(1e-6) #unnecessary, but useful
Note that both of these only seem to work with ipython --pylab=tk, a.k.a.backend = TkAgg
Thank you for the help with everything.
All of the above might be true, however for me "online-updating" of figures only works with some backends, specifically wx. You just might try to change to this, e.g. by starting ipython/pylab by ipython --pylab=wx! Good luck!
Based on the other answers, I wrapped the figure's update in a python decorator to separate the plot's update mechanism from the actual plot. This way, it is much easier to update any plot.
def plotlive(func):
plt.ion()
#functools.wraps(func)
def new_func(*args, **kwargs):
# Clear all axes in the current figure.
axes = plt.gcf().get_axes()
for axis in axes:
axis.cla()
# Call func to plot something
result = func(*args, **kwargs)
# Draw the plot
plt.draw()
plt.pause(0.01)
return result
return new_func
Usage example
And then you can use it like any other decorator.
#plotlive
def plot_something_live(ax, x, y):
ax.plot(x, y)
ax.set_ylim([0, 100])
The only constraint is that you have to create the figure before the loop:
fig, ax = plt.subplots()
for i in range(100):
x = np.arange(100)
y = np.full([100], fill_value=i)
plot_something_live(ax, x, y)
let say I have this code:
num_rows = 10
num_cols = 1
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in xrange(num_rows):
ax = axs[i]
ax.plot(np.arange(10), np.arange(10)**i)
plt.show()
the result figure has too much info and now I want to pick 1 of the axes and draw it alone in a new figure
I tried doing something like this
def on_click(event):
axes = event.inaxes.get_axes()
fig2 = plt.figure(15)
fig2.axes.append(axes)
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
but it didn't quite work. what would be the correct way to do it? searching through the docs and throw SE gave hardly any useful result
edit:
I don't mind redrawing the chosen axes, but I'm not sure how can I tell which of the axes was chosen so if that information is available somehow then it is a valid solution for me
edit #2:
so I've managed to do something like this:
def on_click(event):
fig2 = plt.figure(15)
fig2.clf()
for line in event.inaxes.axes.get_lines():
xydata = line.get_xydata()
plt.plot(xydata[:, 0], xydata[:, 1])
fig2.show()
which seems to be "working" (all the other information is lost - labels, lines colors, lines style, lines width, xlim, ylim, etc...)
but I feel like there must be a nicer way to do it
thanks
Copying the axes
The inital answer here does not work, we keep it for future reference and also to see why a more sophisticated approach is needed.
#There are some pitfalls on the way with the initial approach.
#Adding an `axes` to a figure can be done via `fig.add_axes(axes)`. However, at this point,
#the axes' figure needs to be the figure the axes should be added to.
#This may sound a bit like running in circles but we can actually set the axes'
#figure as `axes.figure = fig2` and hence break out of this.
#One might then also position the axes in the new figure to take the usual dimensions.
#For this a dummy axes can be added first, the axes can change its position to the position
#of the dummy axes and then the dummy axes is removed again. In total, this would look as follows.
import matplotlib.pyplot as plt
import numpy as np
num_rows = 10
num_cols = 1
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in xrange(num_rows):
ax = axs[i]
ax.plot(np.arange(10), np.arange(10)**i)
def on_click(event):
axes = event.inaxes
if not axes: return
fig2 = plt.figure()
axes.figure=fig2
fig2.axes.append(axes)
fig2.add_axes(axes)
dummy = fig2.add_subplot(111)
axes.set_position(dummy.get_position())
dummy.remove()
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
#So far so good, however, be aware that now after a click the axes is somehow
#residing in both figures, which can cause all sorts of problems, e.g. if you
# want to resize or save the initial figure.
Instead, the following will work:
Pickling the figure
The problem is that axes cannot be copied (even deepcopy will fail). Hence to obtain a true copy of an axes, you may need to use pickle. The following will work. It pickles the complete figure and removes all but the one axes to show.
import matplotlib.pyplot as plt
import numpy as np
import pickle
import io
num_rows = 10
num_cols = 1
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in range(num_rows):
ax = axs[i]
ax.plot(np.arange(10), np.arange(10)**i)
def on_click(event):
if not event.inaxes: return
inx = list(fig.axes).index(event.inaxes)
buf = io.BytesIO()
pickle.dump(fig, buf)
buf.seek(0)
fig2 = pickle.load(buf)
for i, ax in enumerate(fig2.axes):
if i != inx:
fig2.delaxes(ax)
else:
axes=ax
axes.change_geometry(1,1,1)
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
Recreate plots
The alternative to the above is of course to recreate the plot in a new figure each time the axes is clicked. To this end one may use a function that creates a plot on a specified axes and with a specified index as input. Using this function during figure creation as well as later for replicating the plot in another figure ensures to have the same plot in all cases.
import matplotlib.pyplot as plt
import numpy as np
num_rows = 10
num_cols = 1
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
labels = ["Label {}".format(i+1) for i in range(num_rows)]
def myplot(i, ax):
ax.plot(np.arange(10), np.arange(10)**i, color=colors[i])
ax.set_ylabel(labels[i])
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in xrange(num_rows):
myplot(i, axs[i])
def on_click(event):
axes = event.inaxes
if not axes: return
inx = list(fig.axes).index(axes)
fig2 = plt.figure()
ax = fig2.add_subplot(111)
myplot(inx, ax)
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
If you have, for example, a plot with three lines generated by the function plot_something, you can do something like this:
fig, axs = plot_something()
ax = axs[2]
l = list(ax.get_lines())[0]
l2 = list(ax.get_lines())[1]
l3 = list(ax.get_lines())[2]
plot(l.get_data()[0], l.get_data()[1])
plot(l2.get_data()[0], l2.get_data()[1])
plot(l3.get_data()[0], l3.get_data()[1])
ylim(0,1)
In a matplotlib figure I would like to enumerate all (sub)plots with a), b), c) and so on. Is there a way to do this automatically?
So far I use the individual plots' titles, but that is far from ideal as I want the number to be left aligned, while an optional real title should be centered on the figure.
import string
from itertools import cycle
from six.moves import zip
def label_axes(fig, labels=None, loc=None, **kwargs):
"""
Walks through axes and labels each.
kwargs are collected and passed to `annotate`
Parameters
----------
fig : Figure
Figure object to work on
labels : iterable or None
iterable of strings to use to label the axes.
If None, lower case letters are used.
loc : len=2 tuple of floats
Where to put the label in axes-fraction units
"""
if labels is None:
labels = string.ascii_lowercase
# re-use labels rather than stop labeling
labels = cycle(labels)
if loc is None:
loc = (.9, .9)
for ax, lab in zip(fig.axes, labels):
ax.annotate(lab, xy=loc,
xycoords='axes fraction',
**kwargs)
example usage:
from matplotlib import pyplot as plt
fig, ax_lst = plt.subplots(3, 3)
label_axes(fig, ha='right')
plt.draw()
fig, ax_lst = plt.subplots(3, 3)
label_axes(fig, ha='left')
plt.draw()
This seems useful enough to me that I put this in a gist : https://gist.github.com/tacaswell/9643166
I wrote a function to do this automatically, where the label is introduced as a legend:
import numpy
import matplotlib.pyplot as plt
def setlabel(ax, label, loc=2, borderpad=0.6, **kwargs):
legend = ax.get_legend()
if legend:
ax.add_artist(legend)
line, = ax.plot(numpy.NaN,numpy.NaN,color='none',label=label)
label_legend = ax.legend(handles=[line],loc=loc,handlelength=0,handleheight=0,handletextpad=0,borderaxespad=0,borderpad=borderpad,frameon=False,**kwargs)
label_legend.remove()
ax.add_artist(label_legend)
line.remove()
fig,ax = plt.subplots()
ax.plot([1,2],[1,2])
setlabel(ax, '(a)')
plt.show()
The location of the label can be controlled with loc argument, the distance to the axis can be controlled with borderpad argument (negative value pushes the label to be outside the figure), and other options available to legend also can be used, such as fontsize. The above script gives such figure:
A super quick way to do this is to take advantage of the fact that chr() casts integers to characters. Since a-z fall in the range 97-122, one can do the following:
import matplotlib.pyplot as plt
fig,axs = plt.subplots(2,2)
for i,ax in enumerate(axs.flat, start=97):
ax.plot([0,1],[0,1])
ax.text(0.05,0.9,chr(i)+')', transform=ax.transAxes)
which produces: