Add second axis to polar plot
Asked Answered
H

2

11

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

Result till now

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. provisional result That's almost everything i wanted to achieve. But there are some points i wasn't able to figure out.

  1. 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?

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

Habitforming answered 25/10, 2013 at 12:38 Comment(3)
Please fix your indentation. It looks like you are using a heavily customized set of classes which makes sorting out what is going on difficult. Can you demonstrate what you want using only standard matplotlib object?Titanomachy
Thank you for including the image. I had too less reputation scores to do it for my own.Habitforming
As for your second question about how to get the last radius label to display, it's just because you're setting the ticks using np.arange. arange stops before the endpoint (e.g. np.arange(0, 0.5, 0.1) yields array([0.0, 0.1, 0.2, 0.3, 0.4])). If you want to include the endpoint, use end + dx (where dx is your interval) instead.Freemason
F
14

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

enter image description here

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

enter image description here

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

enter image description here

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

enter image description here

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()
Freemason answered 27/10, 2013 at 17:9 Comment(4)
This answer was so nice, that I wanted to give it more than one upvote, but I cant :( I'll try and make my own answers as good as this one.Picco
Thank you very much for your detailed and very comprehensible answer, it worked fine for me. There are still some issues, mentioned in my first post.Habitforming
@Habitforming - Thanks! I'll add an example of an automatic locator for the second axes' ticks, but I may not be able to get to it until this weekend. (If anyone else wants to take a crack at it, feel free!)Freemason
@JoeKington It might be because of a new version of the libraries, but this hack does not works for me regarding the positioning of the second ylabels... they are drawn in the same direction as the original axes, overplotting them, ignoring whatever is specified as ax2._r_label_position._t. Any help will be greatly appreciated!Helicon
L
2

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)
Lester answered 8/3, 2015 at 17:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.