I am trying to generate a continously generated plot in matplotlib. The problem that I am facing is related to the labelling on the right y-axis. The range that shows is my desired, however there is also an additional set off labels (0, 0.2, ... 1,0).
def doAnimation():
fig, ax = plt.subplots()
def animate(i):
data=prices(a,b,c) #this gives a DataFrame with 2 columns (value 1 and 2)
plt.cla()
ax.plot(data.index, data.value1)
ax2 = ax.twinx()
ax2.plot(data.index, data.value2)
plt.gcf().autofmt_xdate()
plt.tight_layout()
return ax, ax2
call = FuncAnimation(plt.gcf(), animate, 1000)
return call
callSave = doAnimation()
plt.show()
Any ideas how can I get rid of the set: 0.0, 0.2, 0.4, 0.6, 0.8, 1.0?
This is how the graph looks:
plt.cla clears the current axes. The first time you call plt.cla, the current axes are ax (ax2 doesn't exist yet). Clearing these axes resets both the x and y range of ax to (0,1). However, on line 8, you plot to ax, and both ranges are appropriately adjusted.
On line 9, you create a new set of axes and call them ax2. When you leave the animate function, the name ax2 will go out of scope, but the axes object to which it refers will persist. These axes are now the current axes.
The second time you call animate, plt.cla clears those axes, resetting the x and y range to (0,1). Then, on line 9, you create a new set of axes and call them ax2. These axes are not the same axes as before! ax2 in fact refers to a third set of axes, which will be cleared the next time you call plt.cla. Each new call to animate makes a new set of axes. The offending axes labels appear to be bolded; in fact, they have been drawn a thousand times.
The simplest (fewest changes) fix would be to move ax2 = ax.twinx() outside of animate, and replace plt.cla with separate calls to ax.cla and ax2.cla.
I think a better approach would be to create the lines outside of animate, and modify their data within animate. While we're at it, let's replace those references to plt.gcf() with references to fig, and set tight_layout via an argument to plt.subplots.
Putting said changes together, we get,
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pandas as pd
import numpy as np
def dummy_prices():
samples = 101
xs = np.linspace(0, 10, samples)
ys = np.random.randn(samples)
zs = np.random.randn(samples) * 10 + 50
return pd.DataFrame.from_records({'value1': ys, 'value2': zs}, index=xs)
def doAnimation():
fig, ax = plt.subplots(1, 1, tight_layout=True)
fig.autofmt_xdate()
ax2 = ax.twinx()
data = dummy_prices()
line = ax.plot(data.index, data.value1)[0]
line2 = ax2.plot(data.index, data.value2, 'r')[0]
def animate(i):
data = dummy_prices()
line.set_data(data.index, data.value1)
line2.set_data(data.index, data.value2)
return line, line2
animator = FuncAnimation(fig, animate, frames=10)
return animator
def main():
animator = doAnimation()
animator.save('animation.gif')
if __name__ == '__main__':
main()
where animation.gif should look something like,
Related
Good day. This question is a follow-up of Why does legend-picking only works for `ax.twinx()` and not `ax`?.
The minimal code provided below plots two curves respectively on ax1 and ax2 = ax1.twinx(), their legend boxes are created and the bottom legend is moved to the top ax so that picker events can be used. Clicking on a legend item will hide/show the associated curve.
If ax.scatter(...) is used that works fine. If ax.plot(...) is used instead, legend picking suddenly breaks. Why? Nothing else is changed so that's quite confusing. I have tested several other plotting methods and none of them work as expected.
Here is a video of it in action: https://imgur.com/qsPYHKc.mp4
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
X = np.linspace(0, 2*np.pi, 100)
Y1 = X**0.5 * np.sin(X)
Y2 = -np.cos(X)
# This is a quick way to change the plotting function, simply modify n.
n = 0
function, container = [("scatter", "collections"),
("plot", "lines"),
("bar", "patches"),
("barbs", "collections"),
("quiver", "collections")][n]
getattr(ax1, function)(X, Y1, color="green", label="$Y_1$")
getattr(ax2, function)(X, Y2, color="red", label="$Y_2$")
# Put both legends on ax2 so that pick events also work for ax1's legend.
legend1 = ax1.legend(loc="upper left")
legend2 = ax2.legend(loc="upper right")
legend1.remove()
ax2.add_artist(legend1)
for n, legend in enumerate((legend1, legend2)):
legend_item = legend.legendHandles[0]
legend_item.set_gid(n+1)
legend_item.set_picker(10)
# When a legend element is picked, hide/show the associated curve.
def on_graph_pick_event(event):
gid = event.artist.get_gid()
print(f"Picked Y{gid}'s legend.")
ax = {1: ax1, 2: ax2}[gid]
for artist in getattr(ax, container):
artist.set_visible(not artist.get_visible())
plt.draw()
fig.canvas.mpl_connect("pick_event", on_graph_pick_event)
Ok, so I know this is not the answer, but the comments don't allow me to do this kind of brainstorming. I tried a couple of things, and noticed the following. When you print the axes of the legendHandles artists in your for loop, it returns None for both legends in the case of the scatter plot / PathCollection artists. However, in the case of the 'normal' plot / Line2D artists, it returns axes objects! And even more than that; even though in the terminal their representations seem to be the same (AxesSubplot(0.125,0.11;0.775x0.77)), if you check if they are == ax2, for the legendHandles artist of legend1 it returns False, while for the one of legend2, it returns True. What is happening here?
So I tried to not only remove legend1 from ax1 and add it again to ax2 but to also do the same with the legendHandles object. But it doesn't allow me to do that:
NotImplementedError: cannot remove artist
To me it looks like you found a bug, or at least inconsistent behaviour. Here is the code of what I tried so far, in case anybody else would like to play around with it further.
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Qt5Agg')
import numpy as np
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
X = np.linspace(0, 2*np.pi, 100)
Y1 = X**0.5 * np.sin(X)
Y2 = -np.cos(X)
USE_LINES = True # <--- set this to True or False to test both cases.
if USE_LINES:
ax1.plot(X, Y1, color="green", label="$Y_1$")
ax2.plot(X, Y2, color="red", label="$Y_2$")
else:
ax1.scatter(X, Y1, color="green", label="$Y_1$")
ax2.scatter(X, Y2, color="red", label="$Y_2$")
# Put both legends on ax2 so that pick events also work for ax1's legend.
legend1 = ax1.legend(loc="upper left")
legend2 = ax2.legend(loc="upper right")
legend1.remove()
ax2.add_artist(legend1)
# legend1.legendHandles[0].remove()
# ax2.add_artist(legend1.legendHandles[0])
for n, legend in enumerate((legend1, legend2)):
legend_item = legend.legendHandles[0]
legend_item.set_gid(n+1)
legend_item.set_picker(10)
print(
f'USE_LINES = {USE_LINES}', f'legend{n+1}',
legend_item.axes.__repr__() == legend.axes.__repr__(),
legend_item.axes == legend.axes,
legend_item.axes.__repr__() == ax2.__repr__(),
legend_item.axes == ax2, type(legend_item),
)
# When a legend element is picked, hide/show the associated curve.
def on_graph_pick_event(event):
gid = event.artist.get_gid()
print(f"Picked Y{gid}'s legend.")
ax = {1: ax1, 2: ax2}[gid]
artist = ax.lines[0] if USE_LINES else ax.collections[0]
artist.set_visible(not artist.get_visible())
plt.draw()
fig.canvas.mpl_connect("pick_event", on_graph_pick_event)
plt.show()
I am updating a line plot. There is an event trigger that starts this updating. If the trigger came from the figure that contains the plot, everything is fine. However, if the trigger came from another figure, then weird results happen: the line that's been updated appears to leave its trace uncleared.
Here is an example:
import matplotlib.pyplot as plt
import numpy as np
def onclick(event):
for ii in np.linspace(0., np.pi, 100):
y1 = y * np.sin(ii)
line1.set_ydata(y1)
ax.draw_artist(line1)
line2.set_ydata(-y1)
ax2.draw_artist(line2)
ax2.set_ylim(y1.min(), y1.max())
fig.canvas.update()
plt.pause(0.1)
x = np.linspace(0., 2*np.pi, 100)
y = np.sin(x)
fig = plt.figure()
ax = fig.add_subplot(1, 2, 1)
line1 = ax.plot(x, y)[0]
ax2 = fig.add_subplot(1, 2, 2)
line2 = ax2.plot(x, y)[0]
fig2 = plt.figure()
cid = fig2.canvas.mpl_connect('button_press_event', onclick)
plt.show()
What I see on screen:
Please note, if you resize the plot, or save it as figure, then all the residue image will be gone.
On the other hand, if change one line to:
cid = fig2.canvas.mpl_connect('button_press_event', onclick)
then it is correct. The animation works as intended.
Not sure what fig.canvas.update() would do. If you replace that line by
fig.canvas.draw_idle()
it should work as expected. In that case you would not need to draw the artists individually.
In pyplot, you can change the order of different graphs using the zorder option or by changing the order of the plot() commands. However, when you add an alternative axis via ax2 = twinx(), the new axis will always overlay the old axis (as described in the documentation).
Is it possible to change the order of the axis to move the alternative (twinned) y-axis to background?
In the example below, I would like to display the blue line on top of the histogram:
import numpy as np
import matplotlib.pyplot as plt
import random
# Data
x = np.arange(-3.0, 3.01, 0.1)
y = np.power(x,2)
y2 = 1/np.sqrt(2*np.pi) * np.exp(-y/2)
data = [random.gauss(0.0, 1.0) for i in range(1000)]
# Plot figure
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax2.hist(data, bins=40, normed=True, color='g',zorder=0)
ax2.plot(x, y2, color='r', linewidth=2, zorder=2)
ax1.plot(x, y, color='b', linewidth=2, zorder=5)
ax1.set_ylabel("Parabola")
ax2.set_ylabel("Normal distribution")
ax1.yaxis.label.set_color('b')
ax2.yaxis.label.set_color('r')
plt.show()
Edit: For some reason, I am unable to upload the image generated by this code. I will try again later.
You can set the zorder of an axes, ax.set_zorder(). One would then need to remove the background of that axes, such that the axes below is still visible.
ax2 = ax1.twinx()
ax1.set_zorder(10)
ax1.patch.set_visible(False)
I am trying to plot counts in gridded plots, but I haven't been able to figure out how to go about it.
I want:
to have dotted grids at an interval of 5;
to have major tick labels only every 20;
for the ticks to be outside the plot; and
to have "counts" inside those grids.
I have checked for potential duplicates, such as here and here, but have not been able to figure it out.
This is my code:
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
for key, value in sorted(data.items()):
x = value[0][2]
y = value[0][3]
count = value[0][4]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.annotate(count, xy = (x, y), size = 5)
# overwrites and I only get the last data point
plt.close()
# Without this, I get a "fail to allocate bitmap" error.
plt.suptitle('Number of counts', fontsize = 12)
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.axes().set_aspect('equal')
plt.axis([0, 1000, 0, 1000])
# This gives an interval of 200.
majorLocator = MultipleLocator(20)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(5)
# I want the minor grid to be 5 and the major grid to be 20.
plt.grid()
filename = 'C:\Users\Owl\Desktop\Plot.png'
plt.savefig(filename, dpi = 150)
plt.close()
This is what I get.
I also have a problem with the data points being overwritten.
Could anybody PLEASE help me with this problem?
There are several problems in your code.
First the big ones:
You are creating a new figure and a new axes in every iteration of your loop →
put fig = plt.figure and ax = fig.add_subplot(1,1,1) outside of the loop.
Don't use the Locators. Call the functions ax.set_xticks() and ax.grid() with the correct keywords.
With plt.axes() you are creating a new axes again. Use ax.set_aspect('equal').
The minor things:
You should not mix the MATLAB-like syntax like plt.axis() with the objective syntax.
Use ax.set_xlim(a,b) and ax.set_ylim(a,b)
This should be a working minimal example:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Major ticks every 20, minor ticks every 5
major_ticks = np.arange(0, 101, 20)
minor_ticks = np.arange(0, 101, 5)
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, minor=True)
ax.set_yticks(major_ticks)
ax.set_yticks(minor_ticks, minor=True)
# And a corresponding grid
ax.grid(which='both')
# Or if you want different settings for the grids:
ax.grid(which='minor', alpha=0.2)
ax.grid(which='major', alpha=0.5)
plt.show()
Output is this:
A subtle alternative to MaxNoe's answer where you aren't explicitly setting the ticks but instead setting the cadence.
import matplotlib.pyplot as plt
from matplotlib.ticker import (AutoMinorLocator, MultipleLocator)
fig, ax = plt.subplots(figsize=(10, 8))
# Set axis ranges; by default this will put major ticks every 25.
ax.set_xlim(0, 200)
ax.set_ylim(0, 200)
# Change major ticks to show every 20.
ax.xaxis.set_major_locator(MultipleLocator(20))
ax.yaxis.set_major_locator(MultipleLocator(20))
# Change minor ticks to show every 5. (20/4 = 5)
ax.xaxis.set_minor_locator(AutoMinorLocator(4))
ax.yaxis.set_minor_locator(AutoMinorLocator(4))
# Turn grid on for both major and minor ticks and style minor slightly
# differently.
ax.grid(which='major', color='#CCCCCC', linestyle='--')
ax.grid(which='minor', color='#CCCCCC', linestyle=':')
In my plot, a secondary x axis is used to display the value of another variable for some data. Now, the original axis is log scaled. Unfortunaltely, the twinned axis puts the ticks (and the labels) referring to the linear scale of the original axis and not as intended to the log scale. How can this be overcome?
Here the code example that should put the ticks of the twinned axis in the same (absolute axes) position as the ones for the original axis:
def conv(x):
"""some conversion function"""
# ...
return x2
ax = plt.subplot(1,1,1)
ax.set_xscale('log')
# get the location of the ticks of ax
axlocs,axlabels = plt.xticks()
# twin axis and set limits as in ax
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
#Set the ticks, should be set referring to the log scale of ax, but are set referring to the linear scale
ax2.set_xticks(axlocs)
# put the converted labels
ax2.set_xticklabels(map(conv,axlocs))
An alternative way would be (the ticks are then not set in the same position, but that doesn't matter):
from matplotlib.ticker import FuncFormatter
ax = plt.subplot(1,1,1)
ax.set_xscale('log')
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
ax2.xaxis.set_major_formatter(FuncFormatter(lambda x,pos:conv(x)))
Both approaches work well as long as no log scale is used.
Perhaps there exists an easy fix. Is there something I missed in the documentation?
As a workaround, I tried to obtain the ax.transAxes coordinates of the ticks of ax and put the ticks at the very same position in ax2. But there does not exist something like
ax2.set_xticks(axlocs,transform=ax2.transAxes)
TypeError: set_xticks() got an unexpected keyword argument 'transform'
This has been asked a while ago, but I stumbled over it with the same question.
I eventually managed to solve the problem by introducing a logscaled (semilogx) transparent (alpha=0) dummy plot.
Example:
import numpy as np
import matplotlib.pyplot as plt
def conversion_func(x): # some arbitrary transformation function
return 2 * x**0.5 # from x to z
x = np.logspace(0, 5, 100)
y = np.sin(np.log(x))
fig = plt.figure()
ax = plt.gca()
ax.semilogx(x, y, 'k')
ax.set_xlim(x[0], x[-1]) # this is important in order that limits of both axes match
ax.set_ylabel("$y$")
ax.set_xlabel("$x$", color='C0')
ax.tick_params(axis='x', which='both', colors='C0')
ax.axvline(100, c='C0', lw=3)
ticks_x = np.logspace(0, 5, 5 + 1) # must span limits of first axis with clever spacing
ticks_z = conversion_func(ticks_x)
ax2 = ax.twiny() # get the twin axis
ax2.semilogx(ticks_z, np.ones_like(ticks_z), alpha=0) # transparent dummy plot
ax2.set_xlim(ticks_z[0], ticks_z[-1])
ax2.set_xlabel("$z \equiv f(x)$", color='C1')
ax2.xaxis.label.set_color('C1')
ax2.tick_params(axis='x', which='both', colors='C1')
ax2.axvline(20, ls='--', c='C1', lw=3) # z=20 indeed matches x=100 as desired
fig.show()
In the above example the vertical lines demonstrate that first and second axis are indeed shifted to one another as wanted. x = 100 gets shifted to z = 2*x**0.5 = 20. The colours are just to clarify which vertical line goes with which axis.
Don't need to cover them, just Eliminate the ticks!
d= [7,9,14,17,35,70];
j= [100,80,50,40,20,10];
plt.figure()
plt.xscale('log')
plt.plot(freq, freq*spec) #plot some spectrum
ax1 = plt.gca() #define my first axis
ax1.yaxis.set_ticks_position('both')
ax1.tick_params(axis='y',which='both',direction='in');
ax1.tick_params(axis='x',which='both',direction='in');
ax2 = ax1.twiny() #generates second axis (top)
ax2.set_xlim(ax1.get_xlim()); #same limits
plt.xscale('log') #make it log
ax2.set_xticks(freq[d]); #my own 'major' ticks OVERLAPS!!!
ax2.set_xticklabels(j); #change labels
ax2.tick_params(axis='x',which='major',direction='in');
ax2.tick_params(axis='x',which='minor',top=False); #REMOVE 'MINOR' TICKS
ax2.grid()
I think you can fix your issue by calling ax2.set_xscale('log').
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.semilogx(np.logspace(1.0, 5.0, 20), np.random.random([20]))
new_tick_locations = np.array([10., 100., 1000., 1.0e4])
def tick_function(X):
V = X / 1000.
return ["%.3f" % z for z in V]
ax2 = ax.twiny()
ax2.set_xscale('log')
ax2.set_xlim(ax.get_xlim())
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(tick_function(new_tick_locations))
ax2.set_xlabel(r"Modified x-axis: $X/1000$")