## Re: [Matplotlib-users] Radar / Spider Chars

 Re: [Matplotlib-users] Radar / Spider Chars From: Michael Droettboom - 2009-07-28 16:57:36 ```Would you (Josh and Tony) be amenable to us including this in the set of examples? It would make it easier for users to find it. Eventually, it might be nice to include this as a core plotting command, but in the meantime, I think it would still be useful as-is. Mike Josh Hemann wrote: > Tony, > > I know this is a year later but your code was hugely helpful to me last > week, so thank you. I needed to make a few modifications to get exactly what > I needed, so I thought I'd add to the post for posterity... > > First, here is the graphic that the sample code generates (>> > execfile('radarPlotExample.py'): > > http://www.nabble.com/file/p24688050/profileComparisonPub.png > profileComparisonPub.png > > Suffice it to say, I think this looks way better than what you get out of R > or MATLAB (e.g. > http://addictedtor.free.fr/graphiques/RGraphGallery.php?graph=123). > > Here is the code. I have tried to add comments in places that differ from > Tony's code. I am a relatively new Python and matplotlib convert, so please > feel free to comment on better ways to do this. But, like Tony said, > hopefully this will help someone. > > Josh > > > --------------------------------------------------------------------------------------- > radarPlotExample.py > --------------------------------------------------------------------------------------- > from matplotlib.projections.polar import PolarAxes > from matplotlib.projections import register_projection > from pylab import * > > def radar_factory(num_vars, frame='polygon'): > """Create a radar chart with `num_vars` axes. > """ > # calculate evenly-spaced axis angles > theta = 2*pi * linspace(0, 1-1/float(num_vars), num_vars) > #print theta > #print > # rotate theta such that the first axis is at the top > theta += pi/2 > > def draw_poly_frame(self, x0, y0, r): > # TODO: should use transforms to convert (x, y) to (r, theta) > verts = [(r*cos(t) + x0, r*sin(t) + y0) for t in theta] > return Polygon(verts, closed=True) > > def draw_circle_frame(self, x0, y0, r): > return Circle((x0, y0), r) > > frame_dict = {'polygon': draw_poly_frame, 'circle': draw_circle_frame} > if frame not in frame_dict: > raise ValueError, 'unknown value for `frame`: %s' % frame > > class RadarAxes(PolarAxes): > """Class for creating a radar chart (a.k.a. a spider or star chart) > > http://en.wikipedia.org/wiki/Radar_chart > """ > name = 'radar' > # use 1 line segment to connect specified points > RESOLUTION = 1 > # define draw_frame method > draw_frame = frame_dict[frame] > > def fill(self, *args, **kwargs): > """Override fill so that line is closed by default""" > closed = kwargs.pop('closed', True) > return super(RadarAxes, self).fill(closed=closed, *args, > **kwargs) > > def plot(self, *args, **kwargs): > """Override plot so that line is closed by default""" > 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() > # FIXME: markers at x[0], y[0] get doubled-up > if x[0] != x[-1]: > x = concatenate((x, [x[0]])) > y = concatenate((y, [y[0]])) > line.set_data(x, y) > > def set_varlabels(self, labels, rvals, rlabels): > self.set_thetagrids(theta * 180/pi, labels) > #Josh says: The rvals and rlabels parameters were added to > support > #the call to the set_rgrid method so you can control the > position > #and labelling of the circular grid lines. Make the radii labels > #smaller than the default size... > self.set_rgrids(rvals, labels=rlabels, size='small') > > def get_axes_patch(self): > x0, y0 = (0.5, 0.5) > r = 0.5 > return self.draw_frame(x0, y0, r) > > register_projection(RadarAxes) > return theta > > > if __name__ == '__main__': > #The following data is from the Denver Aerosol Sources and Health study. > #See doi:10.1016/j.atmosenv.2008.12.017 > # > #The data are pollution source profile estimates for five modeled > pollution > #sources (e.g., cars, wood-burning, etc) that emit 7-9 chemical species. > #The radar charts are experimented with here to see if we can nicely > #visualize how the modeled source profiles change across four scenarios: > # 1) No gas-phase species present, just seven particulate counts on > # Sulfate > # Nitrate > # Elemental Carbon (EC) > # Organic Carbon fraction 1 (OC) > # Organic Carbon fraction 2 (OC2) > # Organic Carbon fraction 3 (OC3) > # Pyrolized Organic Carbon (OP) > # 2)Inclusion of gas-phase specie carbon monoxide (CO) > # 3)Inclusion of gas-phase specie ozone (O3). > # 4)Inclusion of both gas-phase speciesis present... > > N = 9 > theta = radar_factory(N) > > f1_base = [0.88, 0.01, 0.03, 0.03, 0.00, 0.06, 0.01, 0.00, 0.00] > f1_CO = [0.88, 0.02, 0.02, 0.02, 0.00, 0.05, 0.00, 0.05, 0.00] > f1_O3 = [0.89, 0.01, 0.07, 0.00, 0.00, 0.05, 0.00, 0.00, 0.03] > f1_both = [0.87, 0.01, 0.08, 0.00, 0.00, 0.04, 0.00, 0.00, 0.01] > > f2_base = [0.07, 0.95, 0.04, 0.05, 0.00, 0.02, 0.01, 0.00, 0.00] > f2_CO = [0.08, 0.94, 0.04, 0.02, 0.00, 0.01, 0.12, 0.04, 0.00] > f2_O3 = [0.07, 0.95, 0.05, 0.04, 0.00, 0.02, 0.12, 0.00, 0.00] > f2_both = [0.09, 0.95, 0.02, 0.03, 0.00, 0.01, 0.13, 0.06, 0.00] > > f3_base = [0.01, 0.02, 0.85, 0.19, 0.05, 0.10, 0.00, 0.00, 0.00] > f3_CO = [0.01, 0.01, 0.79, 0.10, 0.00, 0.05, 0.00, 0.31, 0.00] > f3_O3 = [0.01, 0.02, 0.86, 0.27, 0.16, 0.19, 0.00, 0.00, 0.00] > f3_both = [0.01, 0.02, 0.71, 0.24, 0.13, 0.16, 0.00, 0.50, 0.00] > > f4_base = [0.01, 0.01, 0.02, 0.71, 0.74, 0.70, 0.00, 0.00, 0.00] > f4_CO = [0.00, 0.02, 0.03, 0.38, 0.31, 0.31, 0.00, 0.59, 0.00] > f4_O3 = [0.01, 0.03, 0.00, 0.32, 0.29, 0.27, 0.00, 0.00, 0.95] > f4_both = [0.01, 0.03, 0.00, 0.28, 0.24, 0.23, 0.00, 0.44, 0.88] > > f5_base = [0.02, 0.01, 0.07, 0.01, 0.21, 0.12, 0.98, 0.00, 0.00] > f5_CO = [0.02, 0.02, 0.11, 0.47, 0.69, 0.58, 0.88, 0.00, 0.00] > f5_O3 = [0.02, 0.00, 0.03, 0.37, 0.56, 0.47, 0.87, 0.00, 0.00] > f5_both = [0.02, 0.00, 0.18, 0.45, 0.64, 0.55, 0.86, 0.00, 0.16] > > fig = figure(figsize=(9,9)) > fig.subplots_adjust(wspace=0.25, hspace=0.20) > axlist = [] > axisNum = 0 > #The base vs with-gas ordering of the modeled profiles is swapped for > #factors 4/5, so we'll swap their ordering in the basecase list just to > keep > #the coloring consistent across the four plots... > bases = [f1_base, f2_base, f3_base, f5_base, f4_base] > COs = [f1_CO, f2_CO, f3_CO, f4_CO, f5_CO] > O3s = [f1_O3, f2_O3, f3_O3, f4_O3, f5_O3] > boths = [f1_both, f2_both, f3_both, f4_both, f5_both] > everything = [bases, COs, O3s, boths] > titles = ['Basecase', 'With CO', 'With O3', 'CO & O3'] > colors = ['b', 'r', 'g', 'm', 'y'] > for row in range(2): > for col in range(2): > axisNum += 1 > if axisNum == 2: > #Unfortunately, it looks like the loc keyword to legend() is > #relative to a specific subplot, rather than the figure > itself. > #So, the positioning seen looks good, but if you resize the > #figure to be larger the legend becomes obviously bound to a > #specific subplot. This is in contrast to how the position > works > #in something like figtext(). Had trouble using figlegend(), > but > #need to try some more... > legend(('Factor 1', 'Factor 2', 'Factor 3', 'Factor 4', > 'Factor 5'), loc=(0.95, 0.895), borderpad=0.01, > shadow=False, prop=matplotlib.font_manager > .FontProperties(size='smaller'), markerscale=0.4) > > data = everything[axisNum-1] > ax = fig.add_subplot(2, 2, axisNum, projection='radar') > ax.set_title(titles[axisNum-1], weight='bold', size='medium', > horizontalalignment='center', > verticalalignment='center', > backgroundcolor='white', > position=(0.5, 1.1)) > p1 = ax.plot(theta, data[0], color=colors[0]) > p2 = ax.plot(theta, data[1], color=colors[1]) > p3 = ax.plot(theta, data[2], color=colors[2]) > p4 = ax.plot(theta, data[3], color=colors[3]) > p5 = ax.plot(theta, data[4], color=colors[4]) > ax.fill(theta, data[0], facecolor=colors[0]) > ax.fill(theta, data[1], facecolor=colors[1]) > ax.fill(theta, data[2], facecolor=colors[2]) > ax.fill(theta, data[3], facecolor=colors[3]) > ax.fill(theta, data[4], facecolor=colors[4]) > #axlist.extend(ax) #This does not work because ax is a > #RadarAxesSubplot object, which is not > iterable > axlist.append(ax) #append() works because it simply tacks on to > #the list, as opposed to merging items from > two > #lists > for patch in ax.patches: > patch.set_alpha(0.25) > > > figtext(0.5, 0.965, '5-Factor Solution Profiles Across Four Scenarios > ', > ha='center', color='black', weight='bold', size='large') > > #Crudely plot the grid lines I want to see: normalized concentrations of > #chemicals range from 0 to 1... > radiiGrid = [0.2, 0.4, 0.6, 0.8] > theta_rgrid = radar_factory(100) > for ax in axlist: > for r in radiiGrid: > radii = repeat(r, 100) > ax.plot(theta_rgrid, radii, color='lightgrey') > > # FIXME: legend doesn't work when fill is called > spokeLabels = ['Sulfate', 'Nitrate', 'EC', 'OC1', 'OC2', 'OC3', 'OP', > 'CO', > 'O3'] > radiiLabels = [str(rg) for rg in radiiGrid] > for ax in axlist: > ax.set_varlabels(spokeLabels, radiiGrid, radiiLabels) > > show() > -- Michael Droettboom Science Software Branch Operations and Engineering Division Space Telescope Science Institute Operated by AURA for NASA ```