11import numpy as np
22
33import matplotlib .pyplot as plt
4- from matplotlib .projections .polar import PolarAxes
5- from matplotlib .projections import register_projection
4+ from matplotlib .path import Path
5+ from matplotlib .spines import Spine
6+ from matplotlib .projections .polar import PolarAxes
7+ from matplotlib .projections import register_projection
68
7- def radar_factory (num_vars , frame = 'circle' ):
8- """Create a radar chart with `num_vars` axes."""
9- # calculate evenly-spaced axis angles
10- theta = 2 * np .pi * np .linspace (0 , 1 - 1. / num_vars , num_vars )
11- # rotate theta such that the first axis is at the top
12- theta += np .pi / 2
139
14- def draw_poly_frame (self , x0 , y0 , r ):
10+ def radar_factory (num_vars , frame = 'circle' ):
11+ """Create a radar chart with `num_vars` axes."""
12+ # calculate evenly-spaced axis angles
13+ theta = 2 * np .pi * np .linspace (0 , 1 - 1. / num_vars , num_vars )
14+ # rotate theta such that the first axis is at the top
15+ theta += np .pi / 2
16+
17+ def poly_verts (x0 , y0 , r ):
1518 # TODO: use transforms to convert (x, y) to (r, theta)
16- verts = [(r * np .cos (t ) + x0 , r * np .sin (t ) + y0 ) for t in theta ]
17- return plt .Polygon (verts , closed = True , edgecolor = 'k' )
18-
19- def draw_circle_frame (self , x0 , y0 , r ):
20- return plt .Circle ((x0 , y0 ), r )
21-
22- frame_dict = {'polygon' : draw_poly_frame , 'circle' : draw_circle_frame }
23- if frame not in frame_dict :
24- raise ValueError , 'unknown value for `frame`: %s' % frame
25-
26- class RadarAxes (PolarAxes ):
27- """Class for creating a radar chart (a.k.a. a spider or star chart)
28-
29- http://en.wikipedia.org/wiki/Radar_chart
30- """
31- name = 'radar'
32- # use 1 line segment to connect specified points
33- RESOLUTION = 1
34- # define draw_frame method
35- draw_frame = frame_dict [frame ]
36-
37- def fill (self , * args , ** kwargs ):
38- """Override fill so that line is closed by default"""
39- closed = kwargs .pop ('closed' , True )
40- return super (RadarAxes , self ).fill (closed = closed , * args , ** kwargs )
41-
42- def plot (self , * args , ** kwargs ):
43- """Override plot so that line is closed by default"""
44- lines = super (RadarAxes , self ).plot (* args , ** kwargs )
45- for line in lines :
46- self ._close_line (line )
47-
48- def _close_line (self , line ):
49- x , y = line .get_data ()
50- # FIXME: markers at x[0], y[0] get doubled-up
51- if x [0 ] != x [- 1 ]:
52- x = np .concatenate ((x , [x [0 ]]))
53- y = np .concatenate ((y , [y [0 ]]))
54- line .set_data (x , y )
55-
56- def set_varlabels (self , labels ):
57- self .set_thetagrids (theta * 180 / np .pi , labels )
58-
59- def _gen_axes_patch (self ):
60- x0 , y0 = (0.5 , 0.5 )
61- r = 0.5
19+ verts = [(r * np .cos (t ) + x0 , r * np .sin (t ) + y0 ) for t in theta ]
20+ return verts
21+
22+ def draw_poly_frame (self , x0 , y0 , r ):
23+ verts = poly_verts (x0 , y0 , r )
24+ return plt .Polygon (verts , closed = True , edgecolor = 'k' )
25+
26+ def draw_circle_frame (self , x0 , y0 , r ):
27+ return plt .Circle ((x0 , y0 ), r )
28+
29+ frame_dict = {'polygon' : draw_poly_frame , 'circle' : draw_circle_frame }
30+ if frame not in frame_dict :
31+ raise ValueError , 'unknown value for `frame`: %s' % frame
32+
33+ class RadarAxes (PolarAxes ):
34+ """Class for creating a radar chart (a.k.a. a spider or star chart)
35+
36+ http://en.wikipedia.org/wiki/Radar_chart
37+ """
38+ name = 'radar'
39+ # use 1 line segment to connect specified points
40+ RESOLUTION = 1
41+ # define draw_frame method
42+ draw_frame = frame_dict [frame ]
43+
44+ def fill (self , * args , ** kwargs ):
45+ """Override fill so that line is closed by default"""
46+ closed = kwargs .pop ('closed' , True )
47+ return super (RadarAxes , self ).fill (closed = closed , * args , ** kwargs )
48+
49+ def plot (self , * args , ** kwargs ):
50+ """Override plot so that line is closed by default"""
51+ lines = super (RadarAxes , self ).plot (* args , ** kwargs )
52+ for line in lines :
53+ self ._close_line (line )
54+
55+ def _close_line (self , line ):
56+ x , y = line .get_data ()
57+ # FIXME: markers at x[0], y[0] get doubled-up
58+ if x [0 ] != x [- 1 ]:
59+ x = np .concatenate ((x , [x [0 ]]))
60+ y = np .concatenate ((y , [y [0 ]]))
61+ line .set_data (x , y )
62+
63+ def set_varlabels (self , labels ):
64+ self .set_thetagrids (theta * 180 / np .pi , labels )
65+
66+ def _gen_axes_patch (self ):
67+ x0 , y0 = (0.5 , 0.5 )
68+ r = 0.5
6269 return self .draw_frame (x0 , y0 , r )
63-
64- register_projection (RadarAxes )
65- return theta
70+
71+ def _gen_axes_spines (self ):
72+ if frame == 'circle' :
73+ return PolarAxes ._gen_axes_spines (self )
74+ # The following is a hack to get the spines (i.e. the axes frame)
75+ # to draw correctly for a polygon frame.
76+
77+ # spine_type must be 'left', 'right', 'top', 'bottom', or `circle`.
78+ spine_type = 'circle'
79+ r = 0.5
80+ x0 , y0 = (0.5 , 0.5 )
81+ #verts = [(t, r) for t in theta]
82+ verts = poly_verts (x0 , y0 , r )
83+ # close off polygon by repeating first vertex
84+ verts .append (verts [0 ])
85+ path = Path (verts )
86+
87+ spine = Spine (self , spine_type , path )
88+ spine .set_transform (self .transAxes )
89+ return {'polar' : spine }
90+
91+ register_projection (RadarAxes )
92+ return theta
6693
6794
68- if __name__ == '__main__' :
69- #The following data is from the Denver Aerosol Sources and Health study.
70- #See doi:10.1016/j.atmosenv.2008.12.017
95+ if __name__ == '__main__' :
96+ #The following data is from the Denver Aerosol Sources and Health study.
97+ #See doi:10.1016/j.atmosenv.2008.12.017
7198 #
7299 #The data are pollution source profile estimates for five modeled pollution
73100 #sources (e.g., cars, wood-burning, etc) that emit 7-9 chemical species.
74- #The radar charts are experimented with here to see if we can nicely
101+ #The radar charts are experimented with here to see if we can nicely
75102 #visualize how the modeled source profiles change across four scenarios:
76103 # 1) No gas-phase species present, just seven particulate counts on
77104 # Sulfate
@@ -81,64 +108,68 @@ def _gen_axes_patch(self):
81108 # Organic Carbon fraction 2 (OC2)
82109 # Organic Carbon fraction 3 (OC3)
83110 # Pyrolized Organic Carbon (OP)
84- # 2)Inclusion of gas-phase specie carbon monoxide (CO)
85- # 3)Inclusion of gas-phase specie ozone (O3).
111+ # 2)Inclusion of gas-phase specie carbon monoxide (CO)
112+ # 3)Inclusion of gas-phase specie ozone (O3).
86113 # 4)Inclusion of both gas-phase speciesis present...
87114 N = 9
88- theta = radar_factory (N )
89- spoke_labels = ['Sulfate' , 'Nitrate' , 'EC' , 'OC1' , 'OC2' , 'OC3' , 'OP' , 'CO' ,
115+ theta = radar_factory (N , frame = 'polygon' )
116+ spoke_labels = ['Sulfate' , 'Nitrate' , 'EC' , 'OC1' , 'OC2' , 'OC3' , 'OP' , 'CO' ,
90117 'O3' ]
91118 f1_base = [0.88 , 0.01 , 0.03 , 0.03 , 0.00 , 0.06 , 0.01 , 0.00 , 0.00 ]
92- f1_CO = [0.88 , 0.02 , 0.02 , 0.02 , 0.00 , 0.05 , 0.00 , 0.05 , 0.00 ]
93- f1_O3 = [0.89 , 0.01 , 0.07 , 0.00 , 0.00 , 0.05 , 0.00 , 0.00 , 0.03 ]
94- f1_both = [0.87 , 0.01 , 0.08 , 0.00 , 0.00 , 0.04 , 0.00 , 0.00 , 0.01 ]
119+ f1_CO = [0.88 , 0.02 , 0.02 , 0.02 , 0.00 , 0.05 , 0.00 , 0.05 , 0.00 ]
120+ f1_O3 = [0.89 , 0.01 , 0.07 , 0.00 , 0.00 , 0.05 , 0.00 , 0.00 , 0.03 ]
121+ f1_both = [0.87 , 0.01 , 0.08 , 0.00 , 0.00 , 0.04 , 0.00 , 0.00 , 0.01 ]
95122
96123 f2_base = [0.07 , 0.95 , 0.04 , 0.05 , 0.00 , 0.02 , 0.01 , 0.00 , 0.00 ]
97- f2_CO = [0.08 , 0.94 , 0.04 , 0.02 , 0.00 , 0.01 , 0.12 , 0.04 , 0.00 ]
98- f2_O3 = [0.07 , 0.95 , 0.05 , 0.04 , 0.00 , 0.02 , 0.12 , 0.00 , 0.00 ]
99- f2_both = [0.09 , 0.95 , 0.02 , 0.03 , 0.00 , 0.01 , 0.13 , 0.06 , 0.00 ]
124+ f2_CO = [0.08 , 0.94 , 0.04 , 0.02 , 0.00 , 0.01 , 0.12 , 0.04 , 0.00 ]
125+ f2_O3 = [0.07 , 0.95 , 0.05 , 0.04 , 0.00 , 0.02 , 0.12 , 0.00 , 0.00 ]
126+ f2_both = [0.09 , 0.95 , 0.02 , 0.03 , 0.00 , 0.01 , 0.13 , 0.06 , 0.00 ]
100127
101128 f3_base = [0.01 , 0.02 , 0.85 , 0.19 , 0.05 , 0.10 , 0.00 , 0.00 , 0.00 ]
102- f3_CO = [0.01 , 0.01 , 0.79 , 0.10 , 0.00 , 0.05 , 0.00 , 0.31 , 0.00 ]
103- f3_O3 = [0.01 , 0.02 , 0.86 , 0.27 , 0.16 , 0.19 , 0.00 , 0.00 , 0.00 ]
104- f3_both = [0.01 , 0.02 , 0.71 , 0.24 , 0.13 , 0.16 , 0.00 , 0.50 , 0.00 ]
105-
129+ f3_CO = [0.01 , 0.01 , 0.79 , 0.10 , 0.00 , 0.05 , 0.00 , 0.31 , 0.00 ]
130+ f3_O3 = [0.01 , 0.02 , 0.86 , 0.27 , 0.16 , 0.19 , 0.00 , 0.00 , 0.00 ]
131+ f3_both = [0.01 , 0.02 , 0.71 , 0.24 , 0.13 , 0.16 , 0.00 , 0.50 , 0.00 ]
132+
106133 f4_base = [0.02 , 0.01 , 0.07 , 0.01 , 0.21 , 0.12 , 0.98 , 0.00 , 0.00 ]
107- f4_CO = [0.00 , 0.02 , 0.03 , 0.38 , 0.31 , 0.31 , 0.00 , 0.59 , 0.00 ]
108- f4_O3 = [0.01 , 0.03 , 0.00 , 0.32 , 0.29 , 0.27 , 0.00 , 0.00 , 0.95 ]
109- f4_both = [0.01 , 0.03 , 0.00 , 0.28 , 0.24 , 0.23 , 0.00 , 0.44 , 0.88 ]
134+ f4_CO = [0.00 , 0.02 , 0.03 , 0.38 , 0.31 , 0.31 , 0.00 , 0.59 , 0.00 ]
135+ f4_O3 = [0.01 , 0.03 , 0.00 , 0.32 , 0.29 , 0.27 , 0.00 , 0.00 , 0.95 ]
136+ f4_both = [0.01 , 0.03 , 0.00 , 0.28 , 0.24 , 0.23 , 0.00 , 0.44 , 0.88 ]
110137
111138 f5_base = [0.01 , 0.01 , 0.02 , 0.71 , 0.74 , 0.70 , 0.00 , 0.00 , 0.00 ]
112- f5_CO = [0.02 , 0.02 , 0.11 , 0.47 , 0.69 , 0.58 , 0.88 , 0.00 , 0.00 ]
113- f5_O3 = [0.02 , 0.00 , 0.03 , 0.37 , 0.56 , 0.47 , 0.87 , 0.00 , 0.00 ]
114- f5_both = [0.02 , 0.00 , 0.18 , 0.45 , 0.64 , 0.55 , 0.86 , 0.00 , 0.16 ]
139+ f5_CO = [0.02 , 0.02 , 0.11 , 0.47 , 0.69 , 0.58 , 0.88 , 0.00 , 0.00 ]
140+ f5_O3 = [0.02 , 0.00 , 0.03 , 0.37 , 0.56 , 0.47 , 0.87 , 0.00 , 0.00 ]
141+ f5_both = [0.02 , 0.00 , 0.18 , 0.45 , 0.64 , 0.55 , 0.86 , 0.00 , 0.16 ]
115142
116143 fig = plt .figure (figsize = (9 ,9 ))
117144 # adjust spacing around the subplots
118145 fig .subplots_adjust (wspace = 0.25 , hspace = 0.20 , top = 0.85 , bottom = 0.05 )
119146 title_list = ['Basecase' , 'With CO' , 'With O3' , 'CO & O3' ]
120147 data = {'Basecase' : [f1_base , f2_base , f3_base , f4_base , f5_base ],
121148 'With CO' : [f1_CO , f2_CO , f3_CO , f4_CO , f5_CO ],
122- 'With O3' : [f1_O3 , f2_O3 , f3_O3 , f4_O3 , f5_O3 ],
149+ 'With O3' : [f1_O3 , f2_O3 , f3_O3 , f4_O3 , f5_O3 ],
123150 'CO & O3' : [f1_both , f2_both , f3_both , f4_both , f5_both ]}
124151 colors = ['b' , 'r' , 'g' , 'm' , 'y' ]
125152 # chemicals range from 0 to 1
126153 radial_grid = [0.2 , 0.4 , 0.6 , 0.8 ]
154+
127155 # If you don't care about the order, you can loop over data_dict.items()
128156 for n , title in enumerate (title_list ):
129157 ax = fig .add_subplot (2 , 2 , n + 1 , projection = 'radar' )
130158 plt .rgrids (radial_grid )
131159 ax .set_title (title , weight = 'bold' , size = 'medium' , position = (0.5 , 1.1 ),
132160 horizontalalignment = 'center' , verticalalignment = 'center' )
133161 for d , color in zip (data [title ], colors ):
134- ax .plot (theta , d , color = color )
135- ax .fill (theta , d , facecolor = color , alpha = 0.25 )
162+ ax .plot (theta , d , color = color )
163+ ax .fill (theta , d , facecolor = color , alpha = 0.25 )
136164 ax .set_varlabels (spoke_labels )
165+
137166 # add legend relative to top-left plot
138167 plt .subplot (2 ,2 ,1 )
139168 labels = ('Factor 1' , 'Factor 2' , 'Factor 3' , 'Factor 4' , 'Factor 5' )
140169 legend = plt .legend (labels , loc = (0.9 , .95 ), labelspacing = 0.1 )
141170 plt .setp (legend .get_texts (), fontsize = 'small' )
142- plt .figtext (0.5 , 0.965 , '5-Factor Solution Profiles Across Four Scenarios' ,
143- ha = 'center' , color = 'black' , weight = 'bold' , size = 'large' )
171+
172+ plt .figtext (0.5 , 0.965 , '5-Factor Solution Profiles Across Four Scenarios' ,
173+ ha = 'center' , color = 'black' , weight = 'bold' , size = 'large' )
144174 plt .show ()
175+
0 commit comments