def plot_gain_patterns(gain_func, sup_title, label_psi_plane, label_theta_plane): gf = gain_func psi = np.linspace(-np.pi, np.pi, 50) theta_front = np.linspace(0, np.pi, 50) theta_back = theta_front[::-1] * -1.0 fig = pl.figure() fig.suptitle(sup_title) ax1 = fig.add_subplot(121, polar=True) ax1.plot(psi, np.array([gf(np.pi / 2.0, p) for p in psi]), 'bo-') ax1.set_title(label_psi_plane) # Hacky to get rotated polar... ax2 = fig.add_subplot(122, polar=True) ax2.hold(True) ax2.plot(theta_front - np.pi / 2, np.array([gf(t, 0.0) for t in theta_front]), 'ro-') ax2.plot(theta_back - np.pi / 2, np.array([gf(-1.0 * t, -np.pi) for t in theta_back]), 'ro-') # the negative goes to psi pl.thetagrids([0, 45, 90, 135, 180, 225, 270, 315], [90, 45, 0, -45, -90, -135, 180, 135]) ax2.set_title(label_theta_plane) pl.show()
def _drawAnglesFromMountNormalToAxis(self, ax, xy_angles_from_12_o_clock, angles_from_mount_normal, mount_angles, label, color, lw=3, ls='-'): xy_angles_from_12_o_clock = np.array(xy_angles_from_12_o_clock) angles_from_mount_normal = np.array(angles_from_mount_normal)*60 for idx, angle in enumerate(mount_angles): ax.annotate(int(round(360*angle/(2*np.pi))), xy=(xy_angles_from_12_o_clock[idx], angles_from_mount_normal[idx]), xytext=(-2, 1), textcoords='offset points', ha='right', va='bottom', color=color, fontsize=14) ax.plot(xy_angles_from_12_o_clock, angles_from_mount_normal, linewidth=lw, color=color, ls=ls, label=label) ax.set_theta_offset(np.pi) ax.set_theta_direction(-1) plt.thetagrids(range(0,360,60), range(0,360,60)) ax.set_title("Angle between axis and mount frame\nnormal [0, 0, 1] (arcmin)\n")
def _drawResidualsToAxis(self, ax, xy_angles_from_12_o_clock, residuals, mount_angles, label, color, lw=3, ls='-'): residuals = np.array(residuals) if self.inMicron: residuals *= 10**3 unit = 'micron' else: unit = 'mm' for idx, angle in enumerate(mount_angles): ax.annotate(int(round(360*angle/(2*np.pi))), xy=(xy_angles_from_12_o_clock[idx], residuals[idx]), xytext=(-2, 1), textcoords='offset points', ha='right', va='bottom', color=color, fontsize=14) ax.plot(xy_angles_from_12_o_clock, residuals, linewidth=lw, color=color, ls=ls, label=label) plt.thetagrids(range(0,360,60), range(0,360,60)) ax.set_theta_offset(np.pi) ax.set_theta_direction(-1) ax.set_title("Residual from circular fit (" + unit + ")\n")
def _drawRadialDisplacementsToAxis(self, ax, xy_angles_from_12_o_clock, xy, mount_angles, label, color, lw=3, ls='-'): xy = np.array(xy) if self.inMicron: xy *= 10**3 unit = 'micron' else: unit = 'mm' x = xy[0] y = xy[1] radial_displacements = np.sqrt((x**2) + (y**2)) for idx, angle in enumerate(mount_angles): ax.annotate(int(round(360*angle/(2*np.pi))), xy=(xy_angles_from_12_o_clock[idx], radial_displacements[idx]), xytext=(-2, 1), textcoords='offset points', ha='right', va='bottom', color=color, fontsize=14) ax.plot(xy_angles_from_12_o_clock, radial_displacements, linewidth=lw, color=color, ls=ls, label=label) plt.thetagrids(range(0,360,60), range(0,360,60)) ax.set_theta_offset(np.pi) ax.set_theta_direction(-1) ax.set_title("Radial decentre from circular fit\n as a function of the angle between the fit centre\n and vector [0, 1], clockwise (" + unit + ")\n")
def plot_gain_patterns( gain_func, sup_title, label_psi_plane, label_theta_plane ): gf = gain_func psi = np.linspace( -np.pi, np.pi, 50 ) theta_front = np.linspace( 0, np.pi, 50 ) theta_back = theta_front[::-1] * -1.0 fig = pl.figure() fig.suptitle( sup_title ) ax1 = fig.add_subplot( 121, polar=True) ax1.plot( psi, np.array([ gf( np.pi / 2.0, p ) for p in psi ]), 'bo-' ) ax1.set_title( label_psi_plane ) # Hacky to get rotated polar... ax2 = fig.add_subplot( 122, polar=True ) ax2.hold( True ) ax2.plot( theta_front - np.pi / 2, np.array([ gf( t, 0.0 ) for t in theta_front ]), 'ro-' ) ax2.plot( theta_back - np.pi / 2, np.array([ gf( -1.0*t, -np.pi ) for t in theta_back ]), 'ro-' ) # the negative goes to psi pl.thetagrids( [0, 45, 90, 135, 180, 225, 270, 315], [90, 45, 0, -45, -90, -135, 180, 135 ] ) ax2.set_title( label_theta_plane ) pl.show()
# -*- coding: utf-8 -*- """ Created on Sat May 2 02:27:05 2015 @author: alumno """ import pylab as m m.polar(m.arange(360) * m.pi / 180., m.rand(360)) m.thetagrids(m.arange(0, 90, 10), labels=None, fmt='%d', frac=1.1) m.rgrids([1, 2, 3], labels=None, angle=22.5) m.show()
theta = numpy.radians(data[:, 0]) w = data[:, 1] wc = data[:, 2] wulff = data[:, 3] fig = pylab.figure(figsize=(3, 3)) #inches ax = pylab.subplot(111, polar=True) ax.set_theta_zero_location('N') ax.set_yticklabels([]) ax.grid(color='gray', linestyle=':', linewidth=0.25) # theta labels ranging from [-180 to 180] pylab.thetagrids(angles % 360, [tex(r'' + str(a) + r'^\circ') for a in angles], frac=1.2) # plot unrelaxed, relaxed shapes pylab.polar(theta, w, color='blue', linewidth=0.5) pylab.polar(numpy.pi + theta, w, color='blue', linewidth=0.5) pylab.polar(theta, wc, color='blue', linewidth=1.5) pylab.polar(numpy.pi + theta, wc, color='blue', linewidth=1.5) pylab.fill(theta, wulff, color='navajowhite') pylab.fill(numpy.pi + theta, wulff, color='navajowhite') pylab.polar(theta, wulff, color='darkorange') pylab.polar(numpy.pi + theta, wulff, color='darkorange') #pylab.polar([0,0],[0,w[0]],color='black') #pylab.Polygon([[0,0],[1.5,.5],[0,1]],closed=True,edgecolor='blue') #ax.fill([0,numpy.pi/2,numpy.pi],[0,0.1,0.1])
def QuiverPlotDict(longitude, colatitude, scalars, vectors, plotOpts1=None, plotOpts2=None, longTicks=None, longLabels=None, colatTicks=None, colatLabels=None, northPOV=True, useMesh=False, plotColorBar=True, plotQuiverKey=True, userAxes=None): """ Wrapper to produce well-labeled polar plot of a vector field overlaid on a color-coded scalar field. Inputs - first is a dictionary holding a 2D meshgrid of longitudes (azimuth) - second is a dictionary holding a 2D meshgrid of colatitudes (radius) - third is dictionary holding a 2D array of a scalar field whose elements are located at coordinates specified in the first and second arguments; as per MIX convention: dim1 = longitudes, dim2=colatitudes - fourth is a 2-tuple of 2D arrays of local {long,colat} direction vector components whose elements are located at coordinates specified in the first and second arguments; dim1 = longitudes, dim2=colatitudes Keywords - plotOpts1 - dictionary holding various optional parameters for tweaking the scalar field appearance 'colormap': string specifying a colormap for surface or contourf plots FIXME: this should be an actual colormap object, NOT just the name of a standard colormap 'min': minimum data value mapped to colormap 'max': maximum data value mapped to colormap 'format_str': format string for min/max labels 'numContours': number of contours between min and max 'numTicks': number of ticks in colorbar - plotOpts2 - dictionary holding various optional parameters for tweaking the vector field appearance 'scale': floating point number specifying how many data unit vector arrows will fit across the width of the plot; default is for max. vector amplitude to be 1/10th plot width 'width': floating point number specifying the width of an arrow shaft as a fraction of plot width 'pivot': should be one of: 'tail' - pivot about tail (default) 'middle' - pivot about middle 'tip' - pivot about tip 'color': color of arrow - longTicks - where to place 'longitude' ticks in radians - longLabels - labels to place at longTicks - colatTicks - where to place 'colatiude' ticks in 'colatitude' units - colatLabels- labels to place at colatTicks - northPOV - if True, assume a POV above north pole, otherwise assume POV below south pole (this does NOT transform data, but only corrects the plot axes to match the POV; it is up to the user to ensure proper inputs) - useMesh - if True, plot scalar field as surface plot, not filled contours (should be quicker in theory, but seems to be much slower) - plotColorBar- if True, plot a colorbar - plotQuiverKey- if True, plot and label a scaled arrow outside the plot - userAxes - FIXME: it currently does nothing, although if None (default), this function creates a new figure...this is almost contrary to normal Matplotlib behavior. Outputs Reference to a matplotlib.axes.PolarAxesSubplot object """ # if user supplies axes use them other wise create our own polar axes if userAxes == None: ax = p.axes(polar=True) else: ax = userAxes # use longitude for azimuthal dimension # NOTE: polar plots have methods to set the 0 direction, BUT they do not # handle vector fields properly (or rather, the vector field plot # routines are not written correctly for polar projections)...we # simply add pi/2, and make all the proper transformations below theta = longitude['data'].copy() + p.pi / 2 # use colatitude for radial dimension r = colatitude['data'].copy() # adjust r so colatitudes increase from 0->pi/2, then decrease from pi/2->pi; # a corresponding adjustment is made to the vrs vector field below gt90 = r > p.pi / 2 r[gt90] = p.pi - r[gt90] # Draw Polar grids with 0 longitude pointing up, correcting for POV if longTicks == None: longTicks = [elem * p.pi / 180 for elem in [0, 90, 180, 270]] # if longTicks was not passed, quietly ignore any longLabels passed longLabels = [ r'0' + '\xb0', r'90' + '\xb0', r'180' + '\xb0', r'270' + '\xb0' ] else: longTicks = [elem for elem in longTicks] if northPOV: longTicks = [elem + p.pi / 2 for elem in longTicks] else: longTicks = [ p.mod(p.arctan2(p.sin(elem), -p.cos(elem)) - p.pi / 2, 2 * p.pi) for elem in longTicks ] if longLabels == None: thetaLines, thetaLabels = p.thetagrids( [elem * 180 / p.pi for elem in longTicks]) else: thetaLines, thetaLabels = p.thetagrids( [elem * 180 / p.pi for elem in longTicks], longLabels) p.setp(thetaLabels, fontsize=10, color='0.4') if colatTicks == None: # let Matplotlib determine colatTicks # if longTicks was not passed, quietly ignore any longLabels passed colatLabels = None if colatLabels == None: rhoLines, rhoLabels = p.rgrids() else: rhoLines, rhoLabels = p.rgrids(colatTicks, colatLabels) p.setp(rhoLabels, fontsize=10, color='gray') p.axis([0, 2.0 * n.pi, 0, r.max()], 'tight') # if scalars is False (or None, or empty, etc.), skip and proceed to quiver plot if scalars: # PROCESS PLOT OPTIONS FOR FIRST (SCALAR) INPUT if plotOpts1 == None: plotOpts1 = {} # if colormap is supplied use it if 'colormap' in plotOpts1: cmap1 = eval('p.cm.' + plotOpts1['colormap']) else: cmap1 = None # default is used # if limits are given use them, if not use the variables min/max values if 'min' in plotOpts1: lower1 = plotOpts1['min'] else: lower1 = scalars['data'].min() if 'max' in plotOpts1: upper1 = plotOpts1['max'] else: upper1 = scalars['data'].max() # if format string for max/min is given use otherwise do default if 'format_str' in plotOpts1: format_str1 = plotOpts1['format_str'] else: format_str1 = '%.2f' # if number of contours is given use it otherwise do 51 if 'numContours' in plotOpts1: ncontours1 = plotOpts1['numContours'] else: ncontours1 = 51 # if number of ticks is given use it otherwise do 51 if 'numTicks' in plotOpts1: nticks1 = plotOpts1['numTicks'] else: nticks1 = 11 contours1 = n.linspace(lower1, upper1, ncontours1) ticks1 = n.linspace(lower1, upper1, nticks1) var1 = scalars['data'] if (useMesh): p.pcolor(theta, r, var1, cmap=cmap1, vmin=lower1, vmax=upper1) else: p.contourf(theta, r, var1, contours1, extend='both', cmap=cmap1) if (plotColorBar): cb1 = p.colorbar(pad=0.075, ticks=ticks1) cb1.set_label(scalars['name'] + ' [' + scalars['units'] + ']') cb1.solids.set_rasterized(True) p.annotate(('min: ' + format_str1 + '\nmax: ' + format_str1) % (var1.min(), var1.max()), (0.65, -.05), textcoords='axes fraction', annotation_clip=False) # if vectors is False (or None, or empty, etc.), skip quiver plot if vectors: # copy 'longitudinal' component of directional vector to polar theta component vts = vectors[0]['data'].copy() # copy 'colatitudinal' component of directional vector to polar r component vrs = vectors[1]['data'].copy() # adjust vrs to accomodate vectors positioned at r > pi/2 vrs[gt90] = -vrs[gt90] # PROCESS PLOT OPTIONS FOR SECOND (VECTOR) INPUT if plotOpts2 == None: plotOpts2 = {} # use scale if given, otherwise max. magnitude generates arrow 1/10 plot-width if 'scale' in plotOpts2: scale = plotOpts2['scale'] else: scale = p.sqrt(vrs**2 + vts**2).max() / 0.1 # use width if given, otherwise default is .0025 plot-width if 'width' in plotOpts2: width = plotOpts2['width'] else: width = .0025 # use pivot if given, otherwise default is 'tail' if 'pivot' in plotOpts2: pivot = plotOpts2['pivot'] else: pivot = 'tail' # use color if given, otherwise default is black if 'color' in plotOpts2: color2 = plotOpts2['color'] else: color2 = 'k' # consider adding options to control spacing of vector field arrows # interpolate to a polar grid that alleviates some of the so-called # "pole problem" described by Randall (2011 Online notes) -EJR 12/2013 for j in range(r[0, :].size): if r[0, j] == 0: # if radius==0, there should be only one unique vector, so just # use the first element r_tmp = 0 theta_tmp = theta[0, j] vr_tmp = vrs[0, j] vt_tmp = vts[0, j] else: # OK, it took the better part of a day to determine that a "bug" # was nothing more than me neglecting to ensure that the known x's # were monotonically increasing in calls to p.interp()...ARGH!!! # # Now, this whole business of allowing colatitudes that are greater # than pi/2 needs to be re-visited, since it is likely to lead to # even bigger problems if/when someone attempts to pass colatitudes # from both hemispheres simultaneously..then again, I would like to # eventually supercede this function with one based on the BASEMAP # extension to Matplotlib, so maybe this is a moot point. -EJR 2/2014 theta_tmp = p.linspace(theta[:, 0].min(), theta[:, 0].max(), 3 * j + 1) r_tmp = theta_tmp * 0 + r[0, j] # all r's are the same for each j # currently only 1D interpolation along columns of constant theta; # 2D interpolation could be use to achieve more uniform distribution # of quiver positions, however this is already easily obtained if # the BASEMAP extenstion to Matplotlib is used, so leave as-is. -EJR 2/2014 minc = theta[:, j].argsort() vr_tmp = p.interp(theta_tmp, theta[minc, j], vrs[minc, j]) vt_tmp = p.interp(theta_tmp, theta[minc, j], vts[minc, j]) # call pylab's quiver, correcting for local polar coordinates # NOTE: if pylab/matplotlib ever fixes quiver to properly handle polar # plots (or projections in general), the rotation below will need # to be removed -EJR 11/2013 ...once again, this is probably a # moot point if/when BASEMAP is incoporated into our plot functions. units = 'width' # use plot width as basic unit for all quiver attributes qh = p.quiver( theta_tmp, r_tmp, vr_tmp * p.cos(theta_tmp) - vt_tmp * p.sin(theta_tmp), vr_tmp * p.sin(theta_tmp) + vt_tmp * p.cos(theta_tmp), units=units, scale=scale, width=width, pivot=pivot, color=color2) ## uncomment for debugging #print 'Number of longitude gridpoints: ',vr_tmp.size - 1 #blah = raw_input('Hit key for next plot:') if plotQuiverKey: # Since we forced units to be 'width', a scale keyword equal to 1 implies # that an arrow whose length is the full width of the plot will be equal # to 1 data unit, a scale keyword of 2 implies that an arrow whose length # is the full width of the plot will be equal to two data units, etc. # Here we draw a "Quiver Key" that is 1/10th the width of the plot, and # properly scale its value...much pained thought went into convincing my- # self that this is correct, but feel free to verify -EJR 12/2013 p.quiverkey(qh, .3, 0, .1 * scale, ('%3.1e ' + '%s') % (.1 * scale, vectors[0]['units']), coordinates='axes', labelpos='S') return p.gca()
import pylab as m m.polar(m.arange(360)*m.pi/180., m.rand(360)) m.thetagrids(angles, labels=None, fmt='%d', frac = 1.1) m.rgrids(radii, labels=None, angle=22.5)