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()
Related
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()
This question already has answers here:
How can I change the x axis in matplotlib so there is no white space?
(2 answers)
Closed 4 years ago.
In the figure below (1) there is a lot of white space around the hexagonal grid that I cannot figure out how to remove. I've tried different methods i.e. tight_layout etc.
The code is
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
import matplotlib.cm as cm
import matplotlib.colors as colors
import numpy
def plot_som_hm(hm,colormap,a=1):
# setup hexagon offsets
m,n = hm.shape
offsety = .75 * 2*a
offsetx = numpy.sqrt(3) * a
evenrow = numpy.sqrt(3)/2 * a
# set up the figure
fig,ax = plt.subplots(figsize=(2,2))
ax.set_aspect('equal')
# define colormap
cmap = cm.ScalarMappable(None,colormap)
norm = colors.Normalize(vmin=hm.min(),vmax=hm.max())
# iterate over the hitmap, drawing a hexagon
xs = []
ys = []
for i in range(m):
for j in range(n):
# calculate center point for current hexagonal grid & draw it
offsetr = evenrow if i % 2 == 0 else 0
x,y = (j*offsetx+offsetr,-i*offsety)
hexg = RegularPolygon(
(x,y),numVertices=6,radius=a,facecolor=cmap.cmap(norm(hm[i][j]))
)
ax.add_patch(hexg)
xs.append(x)
ys.append(y)
# add a scatter plot so all hexagons show up & turn off ticks
ax.scatter(xs,ys,alpha=1.0)
ax.set_xticks([])
ax.set_yticks([])
# add a colorbar
sm = plt.cm.ScalarMappable(cmap=colormap,norm=norm)
sm._A = []
plt.colorbar(sm,ticks=range(int(hm.min()),int(hm.max())+1))
# and show the hitmap
plt.show()
which can be called by plot_som_hm(hitmap,'inferno',-0.5)
I am not sure if the whitespace is the result of calling subplots (figsize=(2,2)) or something else. Being relatively new to matplotlib I am not sure where the whitespace is coming from i.e. if it is the figure, the axis or even the plt so searching on Google has not provided any relevant answers.
II suggest to read the x- and y-limits to see how you can adjust them to your needs, i.e.:
print(ax.get_xlim())
and then e.g.
ax.set_xlim(0.5, 5.5)
or whatever fits.
The same then with the y-axis.
In a Python plot I would like to use a secondary x-axis to display some alternative values. I'm also quite fond of the latex fonts, and would like those fonts to present throughout the plot. However, I find that when I set up my secondary axis, the latex font disappears. Here's a minimum working example:
import numpy as np
import matplotlib.pyplot as plt
Xvalues = np.linspace(0,10,100)
Yvalues = np.sqrt(Xvalues)
Xticks = np.linspace(0,10,6)
AltXvalues = np.log10(Xvalues+1)
AltLabels = ["%.2f" % x for x in AltXvalues] # Round these values
fig = plt.figure()
plt.rcParams['text.usetex'] = True
ax1 = fig.add_subplot(1,1,1)
ax1.plot(Xvalues, Yvalues)
ax1.set_xticks(Xticks)
ax1.set_xlabel('$x_1$')
ax1.set_ylabel('$y$')
ax2 = ax1.twiny()
ax2.set_xlabel('$\\log_{10}\\,(x_1+1)$')
ax2.set_xticks(Xticks)
ax2.set_xticklabels(AltLabels)
plt.show()
How can I ensure that the latex font is continued on the secondary axis?
Its because you are making those labels into strings when you set AltLabels. The different font you see on the primary axis tick labels is because those labels are printed in LaTeX's math-mode. So, the simple fix is to add the math-mode operators to the AltLabel strings:
AltLabels = ["$%.2f$" % x for x in AltXvalues] # Round these values
(Note the $ signs)
I'm using quadmesh to create a simple polar projection plot. Here's a minimal script which produces basically what I'm trying to do:
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as plt
def make_plot(data,fig,subplot):
nphi,nt = data.shape
phi_coords = np.linspace(0,np.pi*2,nphi+1) - np.pi/2.
theta_coords = np.linspace(0,np.radians(35),nt+1)
ax = fig.add_subplot(subplot,projection='polar')
ax.set_thetagrids((45,90,135,180,225,270,315,360),(9,12,15,18,21,24,3,6))
ax.set_rgrids(np.arange(10,35,10),fmt='%s\u00b0')
theta,phi = np.meshgrid(phi_coords,theta_coords)
quadmesh = ax.pcolormesh(theta,phi,data)
ax.grid(True)
fig.colorbar(quadmesh,ax=ax)
return fig,ax
a = np.zeros((360,71)) + np.arange(360)[:,None]
b = np.random.random((360,71))
fig = plt.figure()
t1 = make_plot(a,fig,121)
t2 = make_plot(b,fig,122)
fig.savefig('test.png')
The above script creates a plot which looks like this:
I would like the colorbars to:
Not overlap the 6 label.
be scaled such that they are approximately the same height as the plot.
Is there any trick to make this work properly? (Note that this layout isn't the only one I will be using -- e.g. I might use a 1x2 layout, or a 4x4 layout ... It seems like there should be some way to scale the colorbar to the same height as the associated plot...)
This combination (and values near to these) seems to "magically" work for me to keep the colorbar scaled to the plot, no matter what size the display.
plt.colorbar(im,fraction=0.046, pad=0.04)
You can do this with a combination of the pad, shrink, and aspect kwargs:
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as plt
def make_plot(data,fig,subplot):
nphi,nt = data.shape
phi_coords = np.linspace(0,np.pi*2,nphi+1) - np.pi/2.
theta_coords = np.linspace(0,np.radians(35),nt+1)
ax = fig.add_subplot(subplot,projection='polar')
ax.set_thetagrids((45,90,135,180,225,270,315,360),(9,12,15,18,21,24,3,6))
ax.set_rgrids(np.arange(10,35,10),fmt='%s\u00b0')
theta,phi = np.meshgrid(phi_coords,theta_coords)
quadmesh = ax.pcolormesh(theta,phi,data)
ax.grid(True)
cb = fig.colorbar(quadmesh,ax=ax, shrink=.5, pad=.2, aspect=10)
return fig,ax,cb
a = np.zeros((360,71)) + np.arange(360)[:,None]
b = np.random.random((360,71))
fig = plt.figure()
t1 = make_plot(a,fig,121)
t2 = make_plot(b,fig,122)
figure.colorbar doc
The best value for these parameters will depend on the aspect ratio of the axes.
The size of the axes seems to not get shrink-wrapped to the polar plot, thus in the 1x2 arrangement there is a lot of space above and below the plot that are part in the axes object, but empty. The size of the color bar is keyed off of the rectangular size, not the round size, hence why the default values are not working well. There is probably a way to do the shrink-wrapping, but I do not know how to do that.
An alternate method is to force your figure to be the right aspect ratio ex:
fig.set_size_inches(10, 4) # for 1x2
fig.set_size_inches(4, 10) # for 2x1
which makes the sub plots square, so the default values more-or-less work.
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.