Search code examples
pythonmatplotlibpolar-coordinates

Matplotlib polar plot radial axis offset


I was wondering, is it possible to offset the start of the radial axis or move it outside of the graph.

This is what I'm hoping to achieve:

Goal

And this is what I have for now.

CurRes

I have read the documentation and different topics on SO, but I couldn't find anything helpful. Does that mean that it is not even possible if it is not mentioned anywhere.

Thank you in advance.

EDIT (added snippet of a code used to create the plot):

ax = fig.add_subplot(111, projection='polar')
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)      
ax.plot(X,lines[li]*yScalingFactor,label=linelabels[li],color=color,linestyle=ls)

Solution

  • To offset the start of the radial axis:

    EDIT: As of Matplotlib 2.2.3 there's a new Axes method called set_rorigin which does exactly that. You call it with the theoretical radial coordinate of the origin. So if you call ax.set_ylim(0, 2) and ax.set_rorigin(-1), the radius of the center circle will be a third of the radius of the plot.

    A quick and dirty workaround for Matplotlib < 2.2.3 is to set the lower radial axis limit to a negative value and hide the inner part of the plot behind a circle:

    import numpy as np
    import matplotlib.pyplot as plt
    
    CIRCLE_RES = 36 # resolution of circle inside
    def offset_radial_axis(ax):
        x_circle = np.linspace(0, 2*np.pi, CIRCLE_RES)
        y_circle = np.zeros_like(x_circle)
        ax.fill(x_circle, y_circle, fc='white', ec='black', zorder=2) # circle
        ax.set_rmin(-1) # needs to be after ax.fill. No idea why.
        ax.set_rticks([tick for tick in ax.get_yticks() if tick >= 0])
        # or set the ticks manually (simple)
        # or define a custom TickLocator (very flexible)
        # or leave out this line if the ticks are fully behind the circle
    

    To add a scale outside the plot:

    You can add an extra axes object in the upper half of the other axes and use its yaxis:

    X_OFFSET = 0 # to control how far the scale is from the plot (axes coordinates)
    def add_scale(ax):
        # add extra axes for the scale
        rect = ax.get_position()
        rect = (rect.xmin-X_OFFSET, rect.ymin+rect.height/2, # x, y
                rect.width, rect.height/2) # width, height
        scale_ax = ax.figure.add_axes(rect)
        # hide most elements of the new axes
        for loc in ['right', 'top', 'bottom']:
            scale_ax.spines[loc].set_visible(False)
        scale_ax.tick_params(bottom=False, labelbottom=False)
        scale_ax.patch.set_visible(False) # hide white background
        # adjust the scale
        scale_ax.spines['left'].set_bounds(*ax.get_ylim())
        # scale_ax.spines['left'].set_bounds(0, ax.get_rmax()) # mpl < 2.2.3
        scale_ax.set_yticks(ax.get_yticks())
        scale_ax.set_ylim(ax.get_rorigin(), ax.get_rmax())
        # scale_ax.set_ylim(ax.get_ylim()) # Matplotlib < 2.2.3
    

    Putting it all together:

    (The example is taken from the Matplotlib polar plot demo)

    r = np.arange(0, 2, 0.01)
    theta = 2 * np.pi * r
    
    ax = plt.subplot(111, projection='polar')
    ax.plot(theta, r)
    ax.grid(True)
    
    ax.set_rorigin(-1)
    # offset_radial_axis(ax) # Matplotlib < 2.2.3
    add_scale(ax)
    
    ax.set_title("A line plot on an offset polar axis", va='bottom')
    plt.show()
    

    enter image description here