Bring radial axes labels in front of lines of polar plot matplotlib - python

I am trying to get the radial (or y-axis) labels on a polar plot to go on top of the lines that are plotted on the graph. Right now they are underneath the lines and are covered up.
Here is a simplified version of the code for just one city and one line:
fig, ax = plt.subplots(figsize=(10,6) , nrows=1, ncols=1,subplot_kw=dict(projection='polar'))
rmax = 15
rticks = np.arange(9,rmax,1.5)
rticklabel = np.arange(18,rmax*2,3).astype(int)
theta = np.arange(0,6.3, 0.17) #plots a circle
r = np.ones(len(theta))*(21/2)
ax.plot(theta, r,c='r', linestyle='-',linewidth = 4,zorder=1)
ax.set_rmax(rmax)
ax.set_rticks(rticks) # less radial ticks
ax.set_xticklabels([])
ax.set_rlabel_position(285) # get radial labels away from plotted line
ax.grid(True)
ax.set_facecolor('white')
ax.yaxis.grid(color='silver', linestyle=':',linewidth = 1.5,zorder=10)
ax.set_yticklabels(rticklabel,fontsize=12,zorder=10) #this zorder does nothing
I have already tried this:
plt.rcParams["axes.axisbelow"] = False
This brings the text to the front as I wish, however, it also brings the grid lines. I would like those to stay behind the colored lines.
I have also tried changing the zorder of the yaxis grid, but that does not work.
Most solutions for this are not for the polar axis. Any suggestions?

Unfortunately it seems that the zorder of the grid and labes is tied to that of the axes: https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.grid.html
One possible solution even if not elegant is to draw the gridlines yourself
fig, ax = plt.subplots(figsize=(10,6) , nrows=1, ncols=1,subplot_kw=dict(projection='polar'))
rmax = 15
rticks = np.arange(9,rmax,1.5)
rticklabel = np.arange(18,rmax*2,3).astype(int)
theta = np.arange(0,6.3, 0.17) #plots a circle
r = np.ones(len(theta))*(21/2)
ax.plot(theta, r,c='r', linestyle='-',linewidth = 4,zorder=2)
ax.set_rticks(rticks) # less radial ticks
ax.set_xticklabels([])
ax.set_rlabel_position(285) # get radial labels away from plotted line
ax.xaxis.grid(True)
ax.yaxis.grid(False)
ax.set_facecolor('white')
ax.set_yticklabels(rticklabel,fontsize=12,zorder=10) #this zorder does nothing
ax.yaxis.set_zorder(10)
#ax.yaxis.grid(color='silver', linestyle=':',linewidth = 1.5,zorder=10)
x = np.arange(0,2*np.pi,0.05)
y = np.outer( np.ones(x.shape), rticks)
ax.plot( x,y, zorder=1, color='silver', linestyle=':')
ax.set_ylim(0,rmax)

Related

Adding a 2d histogram to a python cartopy plot causes the extent to be reset

I am writing a program that displays a heat map of lightning strikes over a cartopy plot. Using a scatter plot work; however when I try to implement the 2d histogram, the map becomes zoomed out all the way.
Plot before adding histogram
Plot after adding histogram
Here is the code
lon_small and lat_small are the coordinate arrays.
proj = ccrs.LambertConformal()
fig, ax = plt.subplots(figsize=(10,10),subplot_kw=dict(projection=proj))
#FEATURES
#state_borders = cfeat.NaturalEarthFeature(category='cultural', name="admin_1_states_provinces_lakes", scale = '10m', facecolor = 'none')
extent = ([-99,-92, 27, 31.3])
xynps = ax2.projection.transform_points(ccrs.PlateCarree(), lon_array, lat_array)
ax.set_extent(extent)
ax.set_extent(extent)
ax.add_feature(USCOUNTIES.with_scale('5m'), edgecolor = "gray")
ax.add_feature(state_borders, linestyle='solid', edgecolor='black')
ax.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False)
#Adding scatter plots
ax.scatter(-95.36, 29.76, transform = ccrs.PlateCarree(), marker='*', s = 400, color = "orange") #Houston
ax.scatter(lon_small, lat_small, transform = ccrs.PlateCarree(), marker='.', s=0.001, c = "red")
#Adding Histogram
h = ax.hist2d(xynps[:,0], xynps[:,1], bins=100, zorder=10, alpha=0.75, cmin=120)
plt.show()
I have checked online for people with a similar problem, but I can't find anything like this problem.
There's a note in the docstring of hist2d stating:
Currently hist2d calculates its own axis limits, and any limits previously set are ignored.
... so i guess the easiest way to maintain initial limits is to do something like this:
...
extent = ax.get_extent(crs=ax.projection)
ax.hist2d(...)
ax.set_extent(extent, crs=ax.projection)
...

scatter plot color bar does not look right

I have written my code to create a scatter plot with a color bar on the right. But the color bar does not look right, in the sense that the color is too light to be mapped to the actual color used in the plot. I am not sure what is missing or wrong here. But I am hoping to get something similar to what's shown here: https://medium.com/#juliansteam/what-bert-topic-modelling-reveal-about-the-2021-unrest-in-south-africa-d0d15629a9b4 (about in the middle of the page)
df = .... # data loading
df["topic"] = topics
# Plot parameters
top_n = topn
fontsize = 15
# some data preparation
to_plot = df.copy()
to_plot[df.topic >= top_n] = -1
outliers = to_plot.loc[to_plot.topic == -1]
non_outliers = to_plot.loc[to_plot.topic != -1]
#the actual plot
fig, ax = plt.subplots(figsize=(15, 15))
scatter_outliers = ax.scatter(outliers['x'], outliers['y'], color="#E0E0E0", s=1, alpha=.3)
scatter = ax.scatter(non_outliers['x'], non_outliers['y'], c=non_outliers['topic'], s=1, alpha=.3, cmap='hsv_r')
ax.text(0.99, 0.01, f"BERTopic - Top {top_n} topics", transform=ax.transAxes, horizontalalignment="right", color="black")
plt.xticks([], [])
plt.yticks([], [])
plt.colorbar(scatter)
plt.savefig(outfile+"_1.png", format='png', dpi=300)
plt.clf()
plt.close()
As you can see, an example plot looks like this. The color bar is created, but compared to that shown in the link above, the color is very light and does not seem to map to those on the scatter plot. Any suggestions?
The colorbar uses the given alpha=.3. In the scatterplot, many dots with the same color are superimposed, causing them to look brighter than a single dot.
One way to tackle this, is to create a ScalarMappable object to be used by the colorbar, taking the colormap and the norm of the scatter plot (but not its alpha). Note that simply changing the alpha of the scatter object (scatter.set_alpha(1)) would also change the plot itself.
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable
import numpy as np
x = np.random.normal(np.repeat(np.random.uniform(0, 20, 10), 1000))
y = np.random.normal(np.repeat(np.random.uniform(0, 10, 10), 1000))
c = np.repeat(np.arange(10), 1000)
scatter = plt.scatter(x, y, c=c, cmap='hsv_r', alpha=.3, s=3)
plt.colorbar(ScalarMappable(cmap=scatter.get_cmap(), norm=scatter.norm))
plt.tight_layout()
plt.show()

Align twinx with second axis with non linear scale

I'm facing some problems in the alignment of the ticks of two different y-axes with the first characterized by a linear range and the second by a non linear range as depicted in the following picture.
HS, TMN = np.meshgrid(hs, period)
r = function(HS, TMN)
cax = plt.contourf(HS, TMN, np.log10(HS), cmap=plt.cm.RdYlGn_r)
ax = plt.gca()
ax2 = ax.twinx()
ticks2 = get_y2values(ax.get_yticks()) # Non linear function
ax2.yaxis.set_major_locator(mpl.ticker.FixedLocator(ticks))
ax2.set_ylim([0, 700])
ax.grid()
ax.set_ylabel('Y1', fontsize=14)
ax2.set_ylabel('Y2', fontsize=14)
plt.show()
More precisely, the right axis requires a different scale from the one on the left. And as final outcome, the idea is to have ticks values on the left aligned with the ticks values on the right (due to the non-linear function depicted below). E.g.: the value 8.08 from Y1 aligned with 101.5; 16.07 aligned with 309.5...
The new scale is required in order to insert new plot in the new scale.
As suggested in the comments the definition of a new scale works perfectly.
Referring to the SegmentedScale defined at the following link, the code that worked for me is the following:
hs = np.linspace(0.1, 15, 1000) # [meters]
period = np.linspace(0.1, 35, 1000) # [seconds]
HS, TMN = np.meshgrid(hs, period)
cax = plt.contourf(HS, TMN, np.log10(HS), cmap=plt.cm.RdYlGn_r)
ax1 = plt.gca()
ax2 = ax.twinx()
ticks = get_y2values(ax1.get_yticks()) # Non linear function
ax2.set_yscale('segmented', points=ticks)
ax1.grid()
ax1.set_yticks(ax1.get_yticks())
ax2.set_yticks(ticks)
ax1.set_ylabel('Y1', fontsize=14)
ax2.set_ylabel('Y2', fontsize=14)
plt.show()
If it is necessary to add new plots on the ax2 axis, it is required to do the plot before the application of the new custom scale.

Colormap entire subplot

I'm having some trouble with color maps. Basically, what I would like to produce is similar to the image below.
On the bottom subplot I would like to be able to plot the relevant colour, but spanning the entire background of the subplot.i.e it would just look like a colourmap over the entire plot, with no lines or points plotted. It should still correspond to the colours shown in the scatter plot.
Is it possible to do this? what I would ideally like to do is put this background under the top subplot. ( the y scales are in diferent units)
Thanks for and help.
code for bottom scatter subplot:
x = np.arange(len(wind))
y = wind
t = y
plt.scatter(x, y, c=t)
where wind is a 1D array
You can use imshow to display your wind array. It needs to be reshaped to a 2D array, but the 'height' dimensions can be length 1. Setting the extent to the dimensions of the top axes makes it align with it.
wind = np.random.randn(100) + np.random.randn(100).cumsum() * 0.5
x = np.arange(len(wind))
y = wind
t = y
fig, ax = plt.subplots(2,1,figsize=(10,6))
ax[0].plot(x,y)
ax[1].plot(x, 100- y * 10, lw=2, c='black')
ymin, ymax = ax[1].get_ybound()
xmin, xmax = ax[1].get_xbound()
im = ax[1].imshow(y.reshape(1, y.size), extent=[xmin,xmax,ymin,ymax], interpolation='none', alpha=.5, cmap=plt.cm.RdYlGn_r)
ax[1].set_aspect(ax[0].get_aspect())
cax = fig.add_axes([.95,0.3,0.01,0.4])
cb = plt.colorbar(im, cax=cax)
cb.set_label('Y parameter [-]')
If you want to use it as a 'background' you should first plot whatever you want. Then grab the extent of the bottom plot and set it as an extent to imshow. You can also provide any colormap you want to imshow by using cmap=.

Add second axis to polar plot

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)

Categories