Search code examples
pythonmatplotliblineradar-chart

Matplotlib Radar Plot fill between vertical lines


I am plotting data in a radar plot and adding two vertical lines to mark an event +/- the standard deviation of that event. I would really like to shade between these lines covering the whole of the y axis. So far I have tried ax.fill_between and ax.span but I'm not having any luck. Does anyone know how to do this? Basic code below.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.spines import Spine
from matplotlib.projections.polar import PolarAxes
from matplotlib.projections import register_projection
from math import pi

data = [1, 5, 10, 11, 15]
data = np.asarray(data)
labels = [10, 20, 30, 40, 50]
event1 = 3
st_dev_event1 = 1

def _radar_factory(num_vars):

                theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)

                theta += np.pi/2

                def unit_poly_verts(theta):

                    x0, y0, r = [0.5] * 3

                    verts = [(r*np.cos(t) + x0, r*np.sin(t) + y0) for t in theta]

                    return verts

                class RadarAxes(PolarAxes):

                    name = 'radar'

                    RESOLUTION = 1

                    def fill(self, *args, **kwargs):

                        closed = kwargs.pop('closed', True)

                        return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)

                    def plot(self, *args, **kwargs):

                        lines = super(RadarAxes, self).plot(*args, **kwargs)

                        for line in lines:

                            self._close_line(line)

                    def _close_line(self, line):

                        x, y = line.get_data()

                        if x[0] != x[-1]:

                            x = np.concatenate((x, [x[0]]))

                            y = np.concatenate((y, [y[0]]))

                            line.set_data(x, y)

                    def set_varlabels(self, labels):

                            self.set_thetagrids(theta * 180/np.pi, labels)

                    def _gen_axes_patch(self):

                        verts = unit_poly_verts(theta)

                        return plt.Polygon(verts, closed=True, edgecolor='k')

                    def _gen_axes_spines(self):

                        spine_type = 'circle'

                        verts = unit_poly_verts(theta)

                        verts.append(verts[0])

                        path = Path(verts)

                        spine = Spine(self, spine_type, path)

                        spine.set_transform(self.transAxes)

                        return {'polar': spine}

                register_projection(RadarAxes)

                return theta

N = len(labels)
theta = _radar_factory(N)

fig = plt.figure()
plt.clf()
ax = fig.add_subplot(1, 1, 1, projection='radar')
ax.plot(theta+(data<0.0)*np.pi, np.abs(data), color='#660066')
ax.set_theta_offset(pi / 1)
ax.set_theta_direction(-1)
ax.set_rlabel_position(90)
ax.set_varlabels(labels)
for label in ax.get_xticklabels()[::2]:
    label.set_visible(False)
plt.axvline(theta[event1], color='#660066', linewidth=2, linestyle='dashed')
plt.axvline(theta[event1+st_dev_event1], color='b', linewidth=0.8, linestyle='dashed')  
plt.axvline(theta[event1-st_dev_event1], color='b', linewidth=0.8, linestyle='dashed')
plt.show()
fig.clf()

Solution

  • I used filling twice: first, between theta[event1] and the left bound and second, between theta[event1] and the right bound using the following. Using theta[event1+st_dev_event1] and theta[event1-st_dev_event1] as x1 and x2 was not filling the whole y-axis. Let me know if this is not what you wanted:

    ax.fill_betweenx(data, x1=theta[event1+st_dev_event1], x2=theta[event1],color='lightgreen')
    ax.fill_betweenx(data, x1=theta[event1-st_dev_event1], x2=theta[event1],color='lightgreen')
    

    Output

    enter image description here