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


------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with 
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Reply via email to