How to fix range of axis in plot without specifying a number - python

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).

Related

matplotlib how to set plot size (with dpi), not figure size

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.

Read height of legend in Python

I have some plots with a lot of information and lines, so sometimes I tend to put the legend outside the plot itself using bbox_to_anchor. I also prefer to have a title of the plot, but this will positionally coincide with the legend in that case. The following example below is just an illustration of the problem.
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
r = 1 + np.sin(4 * np.pi * t)
q = 1 + np.sin(6 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s, label='S')
ax.plot(t, r, label='R')
ax.plot(t, q, label='Q')
leg = ax.legend(loc=3, ncol=3, bbox_to_anchor=(.0, 1.02, 1., .102), borderaxespad=0., mode='expand')
ax.set_title('SIMPLE PLOT', y=1.1)
plt.show()
To avoid this, I set some kind of y-value (e.g. y=1.1). I would like to automate this process because I keep updating the same plot with new data, so the legend grows in size, and I need to adjust the position of the title accordingly.
Is there a way to automate this process?
Is there a function in Python that is able to read the height of the legend so that this can be used to adjust the title position?
The height of the legend is determined at draw time. You can get it after having drawn the figure via legend.get_window_extent(). The resulting bounding box is in units of pixels. In order to find the offset of the title, you will need to subtract the upper limit of the legend from the upper limit of the axes. So you need to get the axes position in pixels as well.
The title can be offset either in figure coordinates (y=1.1) or points (pad=20). I would suggest to use points here, to make it independent of the size of the axes. So you can calculate the difference in upper positions, convert from pixels to points (i.e. distance [pixels] * ppi / dpi) and add some constant offset in points (because usually you would not want the title to sit exactly on the border of the legend). Then use that number as pad.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(constrained_layout=True)
ax.plot([1,2,3], np.random.rand(3,5), label='Label')
leg = ax.legend(loc="lower center", ncol=3, bbox_to_anchor=(.0, 1.02, 1., 1.02),
borderaxespad=0, mode='expand')
fig.canvas.draw()
leg_box = leg.get_window_extent()
ax_box = ax.get_position().transformed(fig.transFigure)
pad = (leg_box.y1 - ax_box.y1)*72./fig.dpi + 6
ax.set_title('SIMPLE PLOT', pad=pad)
plt.show()
Note that here I also used constrained_layout to have the title not cropped by the figure boundaries.

Draw markers at intersections of grid's minor ticks

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)

Long vertical bar plot with matplotlib

I need to create a Python script that plots a list of (sorted) value as a vertical bar plot. I'd like to plot all the values and save it as a long vertical plot, so that both the yticks labels and bars are clearly visible. That is, I'd like a "long" verticalplot. The number of elements in the list varies (e.g. from 500 to 1000), so the use of figsize does not help as I don't know how long that should be.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
# Example data
n = 500
y_pos = np.arange(n)
performance = 3 + 10 * np.random.rand(n)
ax.barh(y_pos, np.sort(performance), align='center', color='green', ecolor='black')
ax.set_yticks(y_pos)
ax.set_yticklabels([str(x) for x in y_pos])
ax.set_xlabel('X')
How can I modify the script so that I can stretch the figure vertically and make it readable?
Change the figsize depending on the number of data values. Also, manage the y-axis limit accordingly.
The following works perfectly:
n = 500
fig, ax = plt.subplots(figsize=(5,n//5)) # Changing figsize depending upon data
# Example data
y_pos = np.arange(n)
performance = 3 + 10 * np.random.rand(n)
ax.barh(y_pos, np.sort(performance), align='center', color='green', ecolor='black')
ax.set_yticks(y_pos)
ax.set_yticklabels([str(x) for x in y_pos])
ax.set_xlabel('X')
ax.set_ylim(0, n) # Manage y-axis properly
Given below is the output picture for n=200

Preserve padding while setting an axis limit in matplotlib

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()

Categories

Resources