Setting xlim and ylim for axis in pyplot removes the padding. How to set them without changing the padding?
Example:
fig, ax = plt.subplots()
x = np.linspace(0, 200, 500)
ax.set_ylim(ymax=100)
line = ax.plot(x, data, '--', linewidth=2, label='foo bar')
plt.show()
In the plot shown, x axis will have a padding while y axis don't. How to make them both have padding while having the ylim I want?
Axes.set_ymargin and Axes.set_ylim are mutually exclusive. Setting a limit to an axis overwrites the margin.
There are two options to have a margin (padding).
a. use margins
It's possible to adapt the margin using
ax.set_ymargin(0.1) or ax.margins(y=0.1)
where 0.1 would be a 10% margin on both axis ends. (Same for x axis of course). The drawback here is that the margin is always symmetric.
b. use limits
Using the limits set by ax.set_ylim(0, 100) and adapt them to the needs.
E.g. if data is the data to plot in form of a numpy array, and we want to have a 10% margin to the bottom and a 40% margin to the top, we could use
ymin = data.min()-0.1*(data.max()-data.min())
ymax = data.max()+0.4*(data.max()-data.min())
ax.set_ylim((ymin, ymax))
It would of course equally be possible to simply set ymax to ymax = 100, if this is desired.
With matplotlib 3.5, I used autoscaling as follows;
axs[row,column].plot(divs['Dividends'], c='red',linewidth=1, marker='.', mec='blue',mfc='blue')
axs[row,column].set_ylim(bottom = 0)
axs[row,column].autoscale()
Solved this problem for me. See attached pics of the graphs for the difference autoscaling did.
Using .margins() with a value or 'tight=True' or .set_ymargin() didn't seem to do anything no matter what values I used to pad.
Changing the lower or bottom limit to <0 moved the Zero line well up the y axis when dividends in my examples are close to zero.
Graph with Autoscaling
Graph without Autoscaling
You can modify the ax.dataLim bounding box and reapply ax.autoscale_view()
Before:
fig, ax = plt.subplots()
x = np.linspace(0, 10, 11)
line = ax.plot(x, x, '--', linewidth=2, label='foo bar')
After:
pts = ax.dataLim.get_points() # numpy array [[xmin, ymin], [xmax, ymax]]
pts[1, 1] = 11 # new ymax
ax.dataLim.set_points(pts)
ax.autoscale_view()
Related
I'm doing a polar plot with matplotlib and I have almost everything ready by now. The problem is that I have several datafiles and it would be convenient to get the axis limits automatically instead of me having to set it each time. So the code is like this
def rose_diagram(x, y, N):
# Compute pie slices
theta = 2 * np.pi * x / N
radii = y
width = np.pi / 24
ax = plt.subplot(projection='polar')
ax.bar(theta, radii, width=width, bottom=0.0, color=colors, alpha=0.9)
# Format x axis
ax.set_xticks((np.linspace(0,2*np.pi,24,endpoint=False)))
ax.set_xticklabels([xlabels])
# Format y axis
max_r = y.max() + ((y.max()-y.min())/10)
ax.set_yticks((0,max_r/2,max_r))
yticks = ax.yaxis.get_major_ticks()
yticks[0].set_visible(False)
# Add nighttime
night_theta = 55*np.pi/180
night_width = 145*np.pi/180
ax.bar(night_theta,max_r,night_width, bottom=0.0, color='gray', alpha=0.3)
plt.show()
The problem is that when I add the "nighttime the axis rescales and i get this graph
polar plot
and what I want is that the nighttimebar goes until the end of the polar plot instead of it increasing the size of the axis.
If I understood the question correctly you can add ylim = ax.get_ylim() after you make your non-nighttime bars. This saves the default radial axis limits that result from just drawing the bars. Then at the end, add ax.set_ylim(ylim) to revert to those saved axis limits (that were there before you added nighttime).
I could find a way to set a figure size with dpi
px = 1/plt.rcParams['figure.dpi']
fig = plt.figure(figsize=(1580*px, 25*px))
(reference: https://matplotlib.org/stable/gallery/subplots_axes_and_figures/figure_size_units.html)
fig = plt.figure(figsize=(1580*px, 25*px))
plt.plot(xx, y[0], label='min')
plt.plot(xx, y[1], label='max')
plt.yticks(y_ticks, y_tick_labels)
plt.ylim(top=y_max)
plt.legend()
However, how do you set the plot size?
I want my plot or graph to be full of (1580px, 25px)
but if I set the figure size and plot graphs using the above code, then the graph does not fit the figure (1580px, 25px). Even worse, labels or ticks are not shown well in the figure like below.
I want my graph size to be the above white space size( for example, 1580px, 25px) and then draw ticks and labels outside the white space (then figure size should be bigger than the given plot size). But I couldn't find a way to set the plot size. I could only find a way to set the figure size.
import matplotlib.pyplot as plt
import numpy as np
def axes_with_pixels(width, height, margin=0.2):
px = 1/plt.rcParams['figure.dpi']
fig_width, fig_height = np.array([width, height]) / (1 - 2 * margin)
fig, ax = plt.subplots(figsize=(fig_width*px, fig_height*px))
fig.subplots_adjust(left=margin, right=1-margin,
bottom=margin, top=1-margin)
return fig, ax
fig, ax = axes_with_pixels(580, 80) # Specify the Axes size in pixels
X = np.linspace(0, 10, 10)
Y0 = np.sin(X)
Y1 = np.cos(X)
plt.plot(X, Y0, label='min')
plt.plot(X, Y1, label='max')
plt.legend()
As you can see, the Axes (plot area) is exactly 580 * 80 pixels. (Note, the shown width of 581 pixels is due to the offset of the right edge.)
However, axes_with_pixels can be only used to set a single Axes with a specified pixels. If you want a figure to have multiple Axes with some specified pixels, then you have to consider wspace and hspace in subplots_adjust to get the figure size.
Using Matplotlib I am trying to shade the region y > N, with N some number.
The problem is that I am not able to have the shaded region going to the end of the frame.
Here is a simple example.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,1)
x = [i for i in range(0,100,10)]
ax.plot(x,x,'-o')
N=110
ylim = max(ax.get_ylim())
ax.axhspan(N,ylim,alpha=.5)
plt.show()
The result is this:
How to have the shaded region not stopping, but arriving up to the end of the frame?
ax.autoscale() can come in handy here. Default, matplotlib automatically adapts the limits of the axes every time something is added. Normally also some padding is added to leave some free space above and below (and left and right).
Calling ax.autoscale(enable=True, axis='y', tight=True) changes this behavior for the y-axis, forcing "tight" limits, so without padding.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x = [i for i in range(0, 100, 10)]
ax.plot(x, x, '-o')
N = 110
ylim1 = max(ax.get_ylim())
ax.autoscale(enable=True, axis='y', tight=True)
ax.axhspan(N, ylim1, alpha=.5)
plt.show()
Alternatively, you could collect the limits before calling axhspan and setting them manually afterwards:
ax.plot(x, x, '-o')
N = 110
ylim0, ylim1 = ax.get_ylim()
ax.axhspan(N, ylim1, alpha=.5)
ax.set_ylim(ylim0, max(N, ylim1))
I have set of data and I made a graph by using them. The problem is the data does not look like scaled properly since y axis ranges from 0 to 30000 while x axis from -2 to 30. How can I solve this problem ? Thanks
Here is my code,
import numpy as np
import matplotlib.pyplot as plt
voltage365nm = [-1.877,-2.0,-1.5,-1.0,0.0,5.0,10.0,20.0,30.0]
voltage405nm = [-1.437,-2.0,-1.5,-1.0,0.0,5.0,10.0,20.0,30.0]
voltage546nm = [-0.768,-2.0,-1.5,-1.0,0.0,5.0,10.0,20.0,30.0]
current365nm = [0.0,5.6,151.1,428,1164,5760,9870,1626,20700]
current405nm = [0.0,-8.2,-2.6,70.2,278,1954,2460,3970,5021]
current546nm = [0.0,-6.5,-6.1,-5.4,248,1435,2240,3250,3750] plt.plot(voltage365nm,current405nm,"r--",marker="s",label="$\lambda$=365 nm")
plt.plot(voltage405nm,current405nm,"b-.",marker="o",label="$\lambda$=405nm")
plt.plot(voltage546nm,current546nm,"g-",marker="^",label="$\lambda$=546nm")
plt.legend(loc='best')
plt.xlabel("Voltage (V)")
plt.ylabel("Current (I x $10^{-13}A}$)")
plt.title("Current vs Voltage")
plt.grid(b=True, which='major', color='g', linestyle='--')
plt.grid(b=True, which='minor', color='r', linestyle='--', alpha=0.2)
plt.show()
You could use plt.xlim([-10,0]) and plt.ylim([-10,0]) to specify the minimum and maximum values of your axes.
I ran your code, and got the following chart:
Is your concern that the data points along the lower end of your y axis are "scrunched up"? If that's the case, perhaps plotting on the log axis might help. Use the set_yscale('log') method like so:
ax = plt.gca()
ax.set_yscale('log')
With that, I get the following chart:
The issue with this of course is that some of the y-axis values are negative, and thus can't be directly plotted on a log scale. The complete solution would involve adding a constant to all currents such that they're positive.
PS -- I think there's a bug in one of your plt.plot commands:
plt.plot(voltage365nm,current405nm,"r--",marker="s",label="$\lambda$=365 nm")
should be
plt.plot(voltage365nm, current365nm,"r--",marker="s",label="$\lambda$=365 nm")
I would additionally put the lower values in an inset as a zoom in
# your existing code before plt.show()
left, bottom, width, height = [0.32, 0.55, 0.4, 0.3]
ax2 = fig.add_axes([left, bottom, width, height])
ax2.plot(voltage365nm,current365nm,"r--",marker="s",label="$\lambda$=365 nm")
ax2.plot(voltage405nm,current405nm,"b-.",marker="o",label="$\lambda$=405nm")
ax2.plot(voltage546nm,current546nm,"g-",marker="^",label="$\lambda$=546nm")
ax2.set_xlim(-2.1, 1)
ax2.set_ylim(-100, 1500)
plt.grid(b=True, which='major', color='g', linestyle='--')
plt.show()
I would like to produce a plot with a grid, so that a full line is drawn at major ticks, and intersections of minor ticks are marked by squares (or any customisable marker).
Here is an example of what I'm trying to achieve:
I generated this plot with the following code, using RegularPolyCollection:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import RegularPolyCollection
# Define dimensions and create plotting objects
width_squares = 6
height_squares = 6
figure = plt.figure()
ax = figure.add_subplot(111)
# Define ticks
x_minors = np.linspace(0, width_squares, 5 * width_squares + 1)
x_majors = np.linspace(0, width_squares, width_squares + 1)
y_minors = np.linspace(0, height_squares, 5 * height_squares + 1)
y_majors = np.linspace(0, height_squares, height_squares + 1)
# Set ticks
ax.set_xticks(x_majors)
ax.set_xticks(x_minors, minor=True)
ax.set_yticks(y_majors)
ax.set_yticks(y_minors, minor=True)
# Define window
ax.set_xlim((0, 6))
ax.set_ylim((0, 6))
# Draw the point collection: squares rotated by 45°
offsets = [(x, y) for x in x_minors for y in y_minors]
points = RegularPolyCollection(
4,
sizes=(1,),
offsets=offsets,
color=('lightgray',),
transOffset=ax.transData,
rotation=0.7857
)
ax.add_collection(points)
# Draw the grid at major ticks
ax.grid(True, which='major', axis='both', color='lightgray')
plt.show()
However, the plot I'm actually trying to produce are way bigger, and performance is at stake.
Unfortunately, drawing a large collection of points is very time consuming.
I also investigated based on this question, and I produced a similar result by drawing vertical lines with linestyle set to "None", so that only intersections are marked, but the time consumption is similar to the collection approach.
I suspect there should be a combination of parameters for the plt.grid function that would produce what I want, but I could not understand the effect of markevery and other keyword arguments (while I do understand their meaning when used with Line2D objects).
Is there a standard way to produce such a grid? If so, is it possible to make it little time-consuming?
I am not sure if you tried the version from one of the answers provided in the link you shared. The main modification I had to do was to turn on the minor ticks while getting the x-tick and y-tick data. Do you have any numbers comparing the time complexity for this approach and the Line2D?
# Draw the grid at major ticks
ax.grid(True, which='major', axis='both')
ax.set_aspect('equal')
def set_grid_cross(ax):
xticks = ax.get_xticks(minor=True)
yticks = ax.get_yticks(minor=True)
xgrid, ygrid = np.meshgrid(xticks, yticks)
kywds = dict()
grid_lines = ax.plot(xgrid, ygrid, 'o', ms=2, color='lightgray', alpha=0.5)
set_grid_cross(ax)