I have been looking for a way to reliably position the tick and axis labels in a plot in polar coordinates. Please take a look at the following example:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=[10, 5])
ax0 = fig.add_axes([0.05, 0.05, 0.4, 0.9], projection="polar")
ax1 = fig.add_axes([0.55, 0.05, 0.4, 0.9], projection="polar")
r0 = np.linspace(10, 12, 10)
theta0 = np.linspace(0, 0.1, 10)
ax0.quiver(theta0, r0, -0.1, 0.1)
ax1.quiver(theta0 + np.pi, r0, -0.1, 0.1)
ax0.set_thetamin(-2)
ax0.set_thetamax(10)
ax1.set_thetamin(178)
ax1.set_thetamax(190)
for ax in [ax0, ax1]:
# Labels
ax.set_xlabel("r")
ax.set_ylabel(r"$\theta$", labelpad=10)
# R range
ax.set_rorigin(0)
ax.set_rmin(9)
ax.set_rmax(13)
plt.show()
which results in this figure:
You can clearly see that
(a) the tick label position on the radial axis switches from bottom to top between the plots and the tick labels for theta switch from right to left.
(b) the axis label positions are fixed. I'd want the axis labels to also move with the tick labels. i.e. in the left plot, "theta" should be on the right, and in the right plot "r" should be on top.
How do I control the axis/tick labels in a way, so that they are positioned correctly? This even gets worse for e.g. a 90 degree shift, because then the theta axis is actually vertical and the tick labels are then totally off.
I think the most important bit is to become clear about how the usual notions of left, right, bottom, top translate into the polar axes in matplotlib.
The angular axis is the "x"-axis. The radial axis is the "y"-axis. The "bottom" is the outer ring. The "top" is the inner ring. "Left" is the radial axis at the start of the angular axis, "right" is the end of it.
This then allows to set the tick locations as usual, e.g.
ax.tick_params(labelleft=True, labelright=False,
labeltop=False, labelbottom=True)
for the case shown above.
The x and y labels (set_xlabel / set_ylabel) are not translated. Here left, right, top, bottom refer to the cartesian definition, just as with normal linear axes. This means that for certain positions, they cannot be used to label the axis, because they are just too far away. An alternative is to create a text at the desired position.
A complete example code:
import numpy as np
import matplotlib.pyplot as plt
fig, (ax0, ax1) = plt.subplots(ncols=2, figsize=(10,5),
subplot_kw=dict(projection="polar"))
ax0.set(thetamin=180, thetamax=230)
ax1.set(thetamin= 0, thetamax= 50)
plt.setp([ax0, ax1], rorigin=0, rmin=5, rmax=10)
ax0.tick_params(labelleft=False, labelright=True,
labeltop=True, labelbottom=False)
trans, _ , _ = ax1.get_xaxis_text1_transform(-10)
ax1.text(np.deg2rad(22.5), -0.18, "Theta Label", transform=trans,
rotation=22.5-90, ha="center", va="center")
plt.show()
To answer question (b):
ax0.yaxis.set_label_position('right')
ax1.xaxis.set_label_position('top')
In addition, I modified the ax.set_ylabel(r"$\theta$", labelpad=15)
Related
I have been playing with polar plots for some time now, but can't figure out how I get my axis labels automatically placed in the correct spot.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=[5, 5])
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], projection="polar")
r = np.random.normal(loc=50, scale=5, size=50)
theta = np.deg2rad(np.random.normal(loc=190, scale=2, size=50))
# Plot
ax.scatter(theta, r)
# Adjust limits
ax.set_rorigin(0)
ax.set_thetamin(180)
ax.set_thetamax(200)
ax.set_rmin(40)
ax.set_rmax(60)
# Labels
ax.set_xlabel("r")
ax.set_ylabel(r"$\theta$")
plt.show()
This produces such a plot:
https://ibb.co/geo4WK
As you can see, the "r" label does not appear on the top axis where the tick labels are and I have similar problem for other ranges of theta. Is there a way to always have the axis label appear with the axis that has tick labels? Or can I have the tick labels for the radii always at the bottom axis?
thanks!
You can use ax.xaxis.set_label_coords() to move "r" to the top center location. As the figure is set to be (5,5) and you have r limits adjusted, the "r" label should stay the same as you change theta.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=[5, 5])
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], projection="polar")
r = np.random.normal(loc=50, scale=5, size=50)
theta = np.deg2rad(np.random.normal(loc=190, scale=2, size=50))
# Plot
ax.scatter(theta, r)
# Adjust limits
ax.set_rorigin(0)
ax.set_thetamin(180)
ax.set_thetamax(200)
ax.set_rmin(40)
ax.set_rmax(60)
# Labels
ax.set_xlabel("r")
ax.set_ylabel(r"$\theta$")
ax.xaxis.set_label_coords(0.5, 1.05)
plt.show()
I am trying to leave some whitespace on the left side of my figure in matplotlib and cannot figure out how to do this.
From the docs, I understand that using the add_axes() method on a figure, I can place an axes at an arbitrary location.
For example, the code below should create the axes on the right half of the figure:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,4))
fig.add_axes([0.5, 0, 0.5, 1])
However, if you run this, the axes will appear on the left half of the figure instead. Is there something I am missing here?
You can use the add_axes method, to place the axes on an arbitrary position. The position list must contain the positions of the origin (measured from bottom left), and the height and width as follows:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,4))
# [x0, y0, width, height]
fig.add_axes([0.3, 0.1, 0.6, 0.8])
All relative in the figure (values between zero and 1)
However, if you want to use the default margins, and maintain the position of the other axes, you can first obtaining the original position, update it, and then set it again as follows.
fig = plt.figure(figsize=(6,4))
ax = plt.axes()
pos = ax.get_position()
pos.x0 = 0.2 # for example 0.2, choose your value
ax.set_position(pos)
pos contains x0, x1, y0 and y1, which are the positions of the Bbox of the axes.
I have a plot whose legend is anchored to the top-right corner: how can I expand the legend to fit the height of the chart?
borderaxespad=0. would expand it horizontally, but I could not find an equivalent to expand it vertically.
I am using matplotlib 2.0
Sample Code:
import numpy as np
x = np.linspace(0, 2*np.pi, 100)
data = [np.sin(x * np.pi/float(el)) for el in range(1, 5)]
fig, ax = plt.subplots(1)
for key, el in enumerate(data):
ax.plot(x, el, label=str(key))
ax.legend(bbox_to_anchor=(1.04,1), loc="upper left", borderaxespad=0., mode='expand')
plt.tight_layout(rect=[0,0,0.8,1])
Which produces:
First to explain the output from the question: When using the 2-tuple notation for bbox_to_anchor, a bounding box without extent is created. The mode="expand" will expand the legend horizontally into this bounding box, which has zero extend, effectively shrinking it to zero size.
The problem is that mode="expand" will expand the legend only horizontally.
From the documentation:
mode : {“expand”, None}
If mode is set to "expand" the legend will be horizontally expanded to fill the axes area (or bbox_to_anchor if defines the legend’s size).
For a solution you need to dig deep into the legend internals. First off you need to set the bbox-to-anchor with a 4-tuple, specifying also width and height of the bbox, bbox_to_anchor=(x0,y0,width,height), where all numbers are in normalized axes coordinates. Then you need to calculate the height of of the legend's _legend_box. Since there is some padding being set, you need to subtract that padding from the bounding box's height. In order to calculate the padding the current legend's fontsize must be known. All of this has to take place after the axes' position is last changed.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2*np.pi, 100)
data = [np.sin(x * np.pi/float(el)) for el in range(1, 5)]
fig, ax = plt.subplots(1)
for key, el in enumerate(data):
ax.plot(x, el, label=str(key))
# legend:
leg = ax.legend(bbox_to_anchor=(1.04,0.0,0.2,1), loc="lower left",
borderaxespad=0, mode='expand')
plt.tight_layout(rect=[0,0,0.8,1])
# do this after calling tight layout or changing axes positions in any way:
fontsize = fig.canvas.get_renderer().points_to_pixels(leg._fontsize)
pad = 2 * (leg.borderaxespad + leg.borderpad) * fontsize
leg._legend_box.set_height(leg.get_bbox_to_anchor().height-pad)
plt.show()
labelspacing may be what your looking for ?
fig, ax = plt.subplots(1)
for key, el in enumerate(data):
ax.plot(x, el, label=str(key))
ax.legend(labelspacing=8, loc=6, bbox_to_anchor=(1, 0.5))
plt.tight_layout(rect=[0, 0, 0.9, 1])
It is not automatic but you might find some relation with figsize (which is also 8 here).
loc=6, bbox_to_anchor=(1, 0.5) will center you legend on the right hand side of your plot.
Which gives:
This question already has answers here:
Aligning rotated xticklabels with their respective xticks
(5 answers)
Closed 4 months ago.
How can I rotate xticklabels in matplotlib so that the spacing between each xticklabel is equal?
For example with this code:
import matplotlib.pyplot as plt
import numpy as np
# Data + parameters
fontsize = 20
t = np.arange(0.0, 6.0, 1)
xticklabels = ['Full', 'token emb', 'char emb', 'char LSTM',
'token LSTM', 'feed forward','ANN']
# Plotting
fig = plt.figure(1)
ax = fig.add_subplot(111)
plt.plot(t, t)
plt.xticks(range(0, len(t) + 1))
ax.tick_params(axis='both', which='major', labelsize=fontsize)
ax.set_xticklabels(xticklabels, rotation = 45)
fig.savefig('test_rotation.png', dpi=300, format='png', bbox_inches='tight')
I obtain:
The spacing between each xticklabel is unequal. For example, the spacing between 'Full' and 'token emb' is much larger than the spacing between 'feed forward' and 'ANN'.
I use Matplotlib 2.0.0 and Python 3.5 64-bit on Windows 7 SP1 x64 Ultimate.
The labels are centered at the tickmark position. Their bounding boxes are unequal in width and might even overlap, which makes them look unequally spaced.
Since you'd always want the ticklabels to link to their tickmarks, changing the spacing is not really an option.
However you might want to align them such the the upper right corner is the reference for their positioning below the tick.
Use the horizontalalignment or ha argument for that and set it to "right":
ax.set_xticklabels(xticklabels, rotation = 45, ha="right")
This results in the following plot:
An alternative can be to keep the ticklabels horizontally centered, but also center them vertically. This leads to an equal spacing but required to further adjust their vertical position with respect to the axis.
ax.set_xticklabels(xticklabels, rotation = 45, va="center", position=(0,-0.28))
The above can be used if the ticks are specified manually like in the question (e.g. via plt.xticks or via ax.set_xticks) or if a categorical plot is used.
If instead the labels are shown automatically, one should not use set_xticklabels. This will in general let the labels and tick positions become out of sync, because set_xticklabels sets the formatter of the axes to a FixedFormatter, while the locator stays the automatic AutoLocator, or any other automatic locator.
In those cases either use plt.setp to set the rotation and alignment of existing labels,
plt.setp(ax.get_xticklabels(), ha="right", rotation=45)
or loop over them to set the respective properties,
for label in ax.get_xticklabels():
label.set_ha("right")
label.set_rotation(45)
An example would be
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
t = np.arange("2018-01-01", "2018-03-01", dtype="datetime64[D]")
x = np.cumsum(np.random.randn(len(t)))
fig, ax = plt.subplots()
ax.plot(t, x)
for label in ax.get_xticklabels():
label.set_ha("right")
label.set_rotation(45)
plt.tight_layout()
plt.show()
Here is a good resource that provides several options. They are not perfect but basically okay:
https://www.pythoncharts.com/2019/05/17/rotating-axis-labels/
UPDATE:
I looked into the documentation of the matplotlib.text.Text.set_rotation_mode (link):
set_rotation_mode(self, m)
Set text rotation mode.
Parameters:
m : {None, 'default', 'anchor'}
If None or "default", the text will be first rotated,
then aligned according to their horizontal and vertical
alignments.
If "anchor", then alignment occurs before rotation.
So if rotation_mode is not specified, the text is first rotated and then aligned. In this mode, the bounding box is not exactly the top right corner of the text even if ha="right" is used.
If rotation_mode="anchor", the text is directly rotated about the anchor point (ha="right").
Here is an example (adapted the code from here)
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
labels = ['G1_bla_bla', 'G2_bla', 'G3_bla', 'G4_bla', 'G5_bla']
men_means = [20, 34, 30, 35, 27]
women_means = [25, 32, 34, 20, 25]
x = np.arange(len(labels)) # the label locations
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
ax.bar(x - width/2, men_means, width, label='Men')
ax.bar(x + width/2, women_means, width, label='Women')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(x)
ax.set_xticklabels(
labels,
rotation=30,
ha="right",
rotation_mode="anchor") # <====== HERE is the key
ax.legend()
plt.show()
The plot now has the correct alignment:
If the rotation angle is ~45 deg, then Ernest's ha='right and gbinux's rotation_mode='anchor' are great:
ax.set_xticklabels(xticklabels, rotation=45, ha='right', rotation_mode='anchor')
However this does not work well for other rotation angles, e.g. 70 deg (see left subplot).
If the rotation angle is not ~45 deg, combine ha='right' instead with a ScaledTranslation (see right subplot).
Apply the ScaledTranslation as described in how to move a tick's label:
...
ax.set_xticklabels(xticklabels, rotation=70, ha='right')
# create offset transform (x=5pt)
from matplotlib.transforms import ScaledTranslation
dx, dy = 5, 0
offset = ScaledTranslation(dx/fig.dpi, dy/fig.dpi, scale_trans=fig.dpi_scale_trans)
# apply offset transform to all xticklabels
for label in ax.xaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
I try to plot two polar plots in one figure. See code below:
fig = super(PlotWindPowerDensity, self).get_figure()
rect = [0.1, 0.1, 0.8, 0.8]
ax = WindSpeedDirectionAxes(fig, rect)
self.values_dict = collections.OrderedDict(sorted(self.values_dict.items()))
values = self.values_dict.items()
di, wpd = zip(*values)
wpd = np.array(wpd).astype(np.double)
wpdmask = np.isfinite(wpd)
theta = self.radar_factory(int(len(wpd)))
# spider plot
ax.plot(theta[wpdmask], wpd[wpdmask], color = 'b', alpha = 0.5)
ax.fill(theta[wpdmask], wpd[wpdmask], facecolor = 'b', alpha = 0.5)
# bar plot
ax.plot_bar(table=self.table, sectors=self.sectors, speedbins=self.wpdbins, option='wind_power_density', colorfn=get_sequential_colors)
fig.add_axes(ax)
return fig
The length of the bar is the data base (how many sampling points for this sector). The colors of the bars show the frequency of certain value bins (eg. 2.5-5 m/s) in the correspondent sector (blue: low, red: high). The blue spider plot shows the mean value for each sector.
In the shown figure, the values of each plot are similar, but this is rare. I need to assign the second plot to another axis and show this axis in another direction.
EDIT:
After the nice answer of Joe, i get the result of the figure.
That's almost everything i wanted to achieve. But there are some points i wasn't able to figure out.
The plot is made for dynamicly changing data bases. Therefore i need a dynamic way to get the same location of the circles. Till now I solve it with:
start, end = ax2.get_ylim()
ax2.yaxis.set_ticks(np.arange(0, end, end / len(ax.yaxis.get_ticklocs())))
means: for second axis i alter the ticks in order to fit the ticklocs to the one's of first axis.
In most cases i get some decimal places, but i don't want that, because it corrupts the clearness of the plot. Is there a way to solve this problem more smartly?
The ytics (the radial one's) range from 0 to the next-to-last circle. How can i achieve that the values range from the first circle to the very last (the border)? The same like for the first axis.
So, as I understand it, you want to display data with very different magnitudes on the same polar plot. Basically you're asking how to do something similar to twinx for polar axes.
As an example to illustrate the problem, it would be nice to display the green series on the plot below at a different scale than the blue series, while keeping them on the same polar axes for easy comparison.:
import numpy as np
import matplotlib.pyplot as plt
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
However, ax.twinx() doesn't work for polar plots.
It is possible to work around this, but it's not very straight-forward. Here's an example:
import numpy as np
import matplotlib.pyplot as plt
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Ensure that original axes tick labels are on top of plots in twinned axes
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
main()
That does what we want, but it looks fairly bad at first. One improvement would be to the tick labels to correspond to what we're plotting:
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
However, we still have the double-grids, which are rather confusing. One easy way around this is to manually set the r-limits (and/or r-ticks) such that the grids will fall on top of each other. Alternately, you could write a custom locator to do this automatically. Let's stick with the simple approach here:
ax.set_rlim([0, 5])
ax2.set_rlim([0, 1])
Caveat: Because shared axes don't work for polar plots, the implmentation I have above will have problems with anything that changes the position of the original axes. For example, adding a colorbar to the figure will cause all sorts of problems. It's possible to work around this, but I've left that part out. If you need it, let me know, and I'll add an example.
At any rate, here's the full, stand-alone code to generate the final figure:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1977)
def main():
numpoints = 30
theta = np.linspace(0, 2*np.pi, numpoints)
r1 = np.random.random(numpoints)
r2 = 5 * np.random.random(numpoints)
params = dict(projection='polar', theta_direction=-1, theta_offset=np.pi/2)
fig, ax = plt.subplots(subplot_kw=params)
ax2 = polar_twin(ax)
ax.fill_between(theta, r2, color='blue', alpha=0.5)
ax2.fill_between(theta, r1, color='green', alpha=0.5)
plt.setp(ax2.get_yticklabels(), color='darkgreen')
plt.setp(ax.get_yticklabels(), color='darkblue')
ax.set_ylim([0, 5])
ax2.set_ylim([0, 1])
plt.show()
def polar_twin(ax):
ax2 = ax.figure.add_axes(ax.get_position(), projection='polar',
label='twin', frameon=False,
theta_direction=ax.get_theta_direction(),
theta_offset=ax.get_theta_offset())
ax2.xaxis.set_visible(False)
# There should be a method for this, but there isn't... Pull request?
ax2._r_label_position._t = (22.5 + 180, 0.0)
ax2._r_label_position.invalidate()
# Bit of a hack to ensure that the original axes tick labels are on top of
# whatever is plotted in the twinned axes. Tick labels will be drawn twice.
for label in ax.get_yticklabels():
ax.figure.texts.append(label)
return ax2
if __name__ == '__main__':
main()
Just to add onto #JoeKington 's (great) answer, I found that the "hack to ensure that the original axes tick labels are on top of whatever is plotted in the twinned axes" didn't work for me so as an alternative I've used:
from matplotlib.ticker import MaxNLocator
#Match the tick point locations by setting the same number of ticks in the
# 2nd axis as the first
ax2.yaxis.set_major_locator(MaxNLocator(nbins=len(ax1.get_yticks())))
#Set the last tick as the plot limit
ax2.set_ylim(0, ax2.get_yticks()[-1])
#Remove the tick label at zero
ax2.yaxis.get_major_ticks()[0].label1.set_visible(False)