#!/usr/bin/python -tt #======================================================================= # General Documentation """Single-procedure module. See procedure docstring for description. """ #----------------------------------------------------------------------- # Additional Documentation # # RCS Revision Code: # $Id: plot.py,v 1.1 2004/02/20 21:49:49 jlin Exp $ # # Modification History: # - 12 Mar 2003: Orig. by Johnny Lin, Computation Institute, # University of Chicago. Passed passably reasonable tests. # - 16 Dec 2003: VCS and gracePlot versions added. Passed passably # reasonable tests. # - 24 Dec 2003: All VCS object names made "unique". Passed passably # reasonable tests. # # Notes: # - Written for Python 2.2. # - There are a variety of different packages available to do x-y plots # in Python. This method supports 3 ways: VCS, gracePlot, and # Scientific/Tkinter. It searches, in that order, for whether the # package is installed. If so, the plot is done using that package. # The Scientific/Tkinter method is the most limited, but is probably # the most widely available, so it is the default version. GracePlot # gives nice plots, but it also requires Grace (successor to ACE/gr) # and plots in a Grace window. VCS is the default because it is the # package used for IaGraph.contour and has a lot of flexibility. # - Note re the Sci/Tk method: In essentially all the examples I've # seen of establishing a GUI using Tkinter, the line "root.mainloop()" # is included at the end of the code to make the plot display. # However, this has the negative effect of not returning the control # prompt in the Python interactive interpreter until the window # closes. Additionally, if you run the code in IDLE, it causes IDLE # to freeze. In tests though using Python 2.1.3 [GCC 2.95.4 20011002 # (Debian prerelease)] on linux2, I found that the mainloop call # wasn't necessary, and that without it the function is able to # simultaneously spawn multiple windows. # - For list of dependencies see import statement placed throughout # the file. # # Copyright (c) 2003 by Johnny Lin. For licensing, distribution # conditions, contact information, and additional documentation see # the URL http://www.johnny-lin.com/py_pkgs/IaGraph/. #======================================================================= #---------------- Module General Import and Declarations --------------- #- Set package version number: import IaGraph_version __version__ = IaGraph_version.version del IaGraph_version #------------------------ Procedure Declaration ------------------------ def plot( x_in, y_in \ , annot=None \ , linestyle=None \ , position=None \ , psym=None \ , symht=7 \ , title=None \ , xrange=None, yrange=None \ , xtitle=None, ytitle=None \ ): """Interactively make x-y plots. Plots an x-y line plot onto the active VCS canvas. If there is a pre-existing plot on the active canvas, that plot is overwritten. Syntax of method call, and symbol and linestyle codes, are based on IDL conventions. However, defaults for input parameters are set to my personal preferences, not IDL defaults, such that a call of plot(x,y) should give a plot I would (subjectively) find generally suitable for publication. Default plot has no overall title and no axis titles, and square plot symbols connected by a solid line (unless the system variables have settings different than their default). Input Arguments: * x_in: Vector of independent (x-axis) variable values. Numeric, MA, or MV array. * y_in: Vector of dependent (y-axis) variable values. Numeric, MA, or MV array. NB: If an input argument is MV, only the MA portion of the variable is used; this procedure does not read the additional attributes of the MV class. Also, if the input arguments have any missing values (as expressed in an MA or MV mask), this procedure will accomodate that only if VCS is used as the plot- ting package. Keyword Inputs: * annot: Extra text to annotate the graph with, placed in the very upper-left corner in small-sized text. String scalar. Different lines of text are separated by the os.linesep string. * linestyle: Line style code for connecting lines. Default set to IaGraph.Sysvar.__class__.p_linestyle. Integer. Key to values: 0: Solid line 1: Dotted 2: Dashed 3: Dash dot 5: Long dashes Note: linestyle cannot equal 4; in IDL that corresponds to dash-dot-dot, which is not a default defined linetype in VCS. * position: 4-element list of [bbllx, bblly, bburx, bbury] of the bounding box (x,y) coordinates in normalized values. The data origin is the lower-left corner, and the upper-right cor- ner of the data box is the upper-right corner of position. If keyword not defined, IaGraph.Sysvar.__class__.p_position is used. If that system variable is not defined, this procedure uses value [0.25, 0.2, 0.85, 0.7] (if keyword annot is defined) and [0.25, 0.25, 0.85, 0.75] (if keyword annot is not passed in). * psym: Symbol code. Default set to IaGraph.Sysvar.__class__. p_psym. Integer. Negative values plot both symbol and line. Key to values: 0: No symbol 1: Plus sign 2: Asterisk 3: Period, circle, or dot 4: Diamond 5: Triangle 6: Square 7: X If psym is less than 0, the symbol and connecting lines are plotted. If psym is greater than 0, the symbol without connec- ting lines are plotted. If psym is 0, lines without symbols are plotted. * [xy]range: The range of the [xy]-axis used to calculate "neat" tickmarks, from which the actual axis range is set. Two-element list, where the first element is the minimum in the axis range and the second element is the maximum in the axis range. If keyword not defined, IaGraph.Sysvar.__class__.[xy]_range is used. If that system variable is not defined, this procedure uses the the minimum and maximum of each input vector. * symht: Plot symbol height. Integer scalar. Arbitrary units, with range of 1 to 300. Default is 7 (which gives a square with an on screen size of approximately 2 mm). * title: The overall plot title. String scalar. Only a single line of input is accepted. * [xy]title: The x-axis and y-axis title. String scalar. Only a single line of input is accepted. Output: * x-y plot of data on screen. * If VCS is the graphics package used, the active_canvas system variable in IaGraph.Sysvar is set (overwritten if a previous canvas exists) to the canvas that is drawn by this procedure. This allows other procedures in IaGraph to operate on the canvas outside of the IaGraph.plot procedure. Notes: * There are a variety of different packages available to do x-y plots in Python. This procedure supports 3: VCS, gracePlot, and Scientific/Tkinter. It searches, in that decreasing order of preference, for whether the package is installed. If that package is found, the plot is made using that package. VCS is the most preferred of the 3 because it is the most flexible and allows writing to file via IaGraph.active2ps, etc. Many of the keywords above only work when VCS is used. * Annotation via annot is set to 1/2 the height as the title font. The font for the annot keyword text is set to be the overall title font. (For VCS only.) * Font heights for the axes labels are set to be the same as the axes titles. (For VCS only.) Example to plot a plus sign for symbol, no connecting lines: import Numeric from plot import plot x = Numeric.arange(100.)/Numeric.pi y = Numeric.sin(x) plot(x, y, psym=1) """ #-------- Global Imports Based On Which Graphics Package To Use -------- # # Graphics: We first try to import VCS to do our plot. If that does # not work we try gracePlot. If that doesn't work we try Scientific # with Tkinter. If that doesn't work, an exception is raised. We # also import MA, Numeric as N, and time. import MA import Numeric as N import time try: import cdms, vcs plotpkg = 'cdat' except ImportError: try: from gracePlot import gracePlot plotpkg = 'grace' except ImportError: try: import Tkinter import Scientific.TkWidgets.TkPlotCanvas as SciTk plotpkg = 'scitk' except ImportError: raise ValueError, "plot: No supported packages available" #---------- Create x and y from Input and Establish Settings ----------- # # Note: Many of these settings are just ignored if gracePlot or Sci/Tk # is the graphics package used, since many of these settings are not # customizable in those packages. #- Vectors x and y are equivalent to the input vectors: x = MA.masked_array(x_in) y = MA.masked_array(y_in) #- Set system variable object and set line style and plot symbol: import IaGraph sysvar = IaGraph.Sysvar() if linestyle == None: linestyle = sysvar.__class__.p_linestyle if psym == None: psym = sysvar.__class__.p_psym #- Set coordinates (normalized units) for lower-left and upper- # right bounding box (depending on whether there is annotation): if position == None: position = sysvar.__class__.p_position if sysvar.__class__.p_position == None: if annot != None: position = [0.25, 0.2, 0.85, 0.7] else: position = [0.25, 0.25, 0.85, 0.75] bbllx = position[0] bblly = position[1] bburx = position[2] bbury = position[3] #- Set height of font for the axis labels and the title, and set # the tick length (font height is in arbitary units and is # converted to normalized units by multiplying by fontht2norm). # Set maximum number of ticks for axis: title_fontht = sysvar.__class__.p_fontht xtitle_fontht = sysvar.__class__.x_fontht ytitle_fontht = sysvar.__class__.y_fontht xticklabel_fontht = sysvar.__class__.x_fontht yticklabel_fontht = sysvar.__class__.y_fontht xticklength = sysvar.__class__.x_ticklen yticklength = sysvar.__class__.y_ticklen xtickmaxnum = sysvar.__class__.x_tickmaxnum ytickmaxnum = sysvar.__class__.y_tickmaxnum #- Set conversion factor to multiply VCS font height coordinates # by to obtain the value in normalized coordinates: fontht2norm = sysvar.__class__.p_fontht2norm[0] #- Set fonts for title and axis ticks and titles: title_font = sysvar.__class__.p_font xaxis_font = sysvar.__class__.x_font yaxis_font = sysvar.__class__.y_font #- Set range of axis, if not defined in keywords: if xrange == None: xrange = sysvar.__class__.x_range if sysvar.__class__.x_range == None: xrange = [min(x), max(x)] if yrange == None: yrange = sysvar.__class__.y_range if sysvar.__class__.y_range == None: yrange = [min(y), max(y)] if len(xrange) != 2: raise ValueError, "plot: Bad x-axis ranges" if len(yrange) != 2: raise ValueError, "plot: Bad y-axis ranges" #------------------------- Global Error Check -------------------------- if len(x) != len(y): raise ValueError, "plot: Bad input vector lengths" #--------------------------- Plot Using CDAT --------------------------- if plotpkg == 'cdat': #- Set plot symbols and linestyles (key:value corresponds # to IDL code:VCS code): symbols = sysvar.__class__.symbols linetypes = sysvar.__class__.linetypes #- Open VCS canvas window (first closing and reseting any # pre-existing active canvas): if sysvar.__class__.active_canvas != []: sysvar.__class__.active_canvas.clear() v = sysvar.__class__.active_canvas else: v = vcs.init() #- Make unique string based on system time. This provides # a way of giving a unique name to VCS objects that is # guaranteed to be unique if the procedure is called again # no sooner than 1 sec and no later than ~100 days from # the current call: uniq_str = ('%.2f' % time.time())[-11:].replace('.','') #- Create x vs. y plot graphics method object and template: xy_gm = v.createxvsy('xygm'+uniq_str, 'default') my_tpl = v.createtemplate('mytpl'+uniq_str, 'default') #- Set plot symbol, symbol size, and linestyle: try: xy_gm.marker = symbols[abs(psym)] except KeyError: print "plot: VCS lacks chosen psym - using square" xy_gm.marker = symbols[6] try: xy_gm.line = linetypes[linestyle] except KeyError: print "plot: VCS lacks chosen linestyle - using solid" xy_gm.line = linetypes[0] xy_gm.markersize = symht #- Create text-table and text-orientation objects for: title, # axis titles, tick labels. Create a text-combined object # for annotation text and set annotation font height to # fraction of the title height (with a minimum value of 20): ttab_title = v.createtexttable('tttitl'+uniq_str, 'default') ttab_xtitle = v.createtexttable('ttxtit'+uniq_str, 'default') ttab_ytitle = v.createtexttable('ttytit'+uniq_str, 'default') ttab_title.font = title_font ttab_xtitle.font = xaxis_font ttab_ytitle.font = yaxis_font tori_title = v.createtextorientation('totit'+uniq_str, 'defcenter') tori_xtitle = v.createtextorientation('toxtit'+uniq_str, 'defcenter') tori_ytitle = v.createtextorientation('toytit'+uniq_str, 'defcentup') tori_title.height = title_fontht tori_xtitle.height = xtitle_fontht tori_ytitle.height = ytitle_fontht ttab_xticklabel = v.createtexttable('ttxtic'+uniq_str, 'default') ttab_yticklabel = v.createtexttable('ttytic'+uniq_str, 'default') ttab_xticklabel.font = xaxis_font ttab_yticklabel.font = yaxis_font tori_xticklabel = \ v.createtextorientation('toxtic'+uniq_str, 'defcenter') tori_yticklabel = \ v.createtextorientation('toytic'+uniq_str, 'default') tori_xticklabel.height = xticklabel_fontht tori_yticklabel.height = yticklabel_fontht text_annot = \ v.createtext( 'ttanno'+uniq_str, 'default' \ , 'toanno'+uniq_str, '7left' ) text_annot.font = title_font text_annot.height = title_fontht * 0.5 #- Turn off some default titling fields and right-upper set # of tick labels: my_tpl.mean.priority = 0 my_tpl.max.priority = 0 my_tpl.min.priority = 0 my_tpl.xtic2.priority = 0 my_tpl.ytic2.priority = 0 #- Set out data field and plot box: my_tpl.data.x1 = my_tpl.box1.x1 = bbllx my_tpl.data.y1 = my_tpl.box1.y1 = bblly my_tpl.data.x2 = my_tpl.box1.x2 = bburx my_tpl.data.y2 = my_tpl.box1.y2 = bbury #- If the system variable x_tickvalues is not defined, calculate # nice x-axis labels (if these labels have a maximum length >= 5, # redo the x-axis labels to have no more than 5 ticks). Set x- # axis labels into graphics method. Set x-axis bottom ticks and # labels: if sysvar.__class__.x_tickvalues == None: xlabels = \ vcs.mklabels( vcs.mkscale(xrange[0], xrange[1], xtickmaxnum) ) longest_xlabels = \ max([ len(xlabels.values()[i]) for i in range(len(xlabels))] ) if longest_xlabels >= 5: xlabels = vcs.mklabels( vcs.mkscale(xrange[0], xrange[1], 5) ) else: xlabels = sysvar.__class__.x_tickvalues xy_gm.xticlabels1 = xlabels xy_gm.datawc_x1 = min(xlabels.keys()) xy_gm.datawc_x2 = max(xlabels.keys()) my_tpl.xtic1.y1 = bblly - xticklength my_tpl.xtic1.y2 = bblly my_tpl.xlabel1.y = bblly - (3.0 * xticklength) my_tpl.xlabel1.texttable = ttab_xticklabel my_tpl.xlabel1.textorientation = tori_xticklabel #- If the system variable y_tickvalues is not defined, calculate # nice y-axis labels. Set y-axis labels in the graphics method. # Set y-axis ticks and tick label locations based on longest tick # label length: if sysvar.__class__.y_tickvalues == None: ylabels = \ vcs.mklabels( vcs.mkscale(yrange[0], yrange[1], ytickmaxnum) ) else: ylabels = sysvar.__class__.y_tickvalues xy_gm.yticlabels1 = ylabels xy_gm.datawc_y1 = min(ylabels.keys()) xy_gm.datawc_y2 = max(ylabels.keys()) longest_ylabels = \ max([ len(ylabels.values()[i]) for i in range(len(ylabels))] ) my_tpl.ytic1.x1 = bbllx - yticklength my_tpl.ytic1.x2 = bbllx my_tpl.ylabel1.x = bbllx - yticklength \ - ( longest_ylabels \ * yticklabel_fontht * fontht2norm ) my_tpl.ylabel1.texttable = ttab_yticklabel my_tpl.ylabel1.textorientation = tori_yticklabel #- Set overall title and x- and y-axis titles. Note for # some reason, the yname element for the xvsy graphics # method will not display; thus I duplicate yname settings # as comment1 and pass in the title via the comment1 keyword # in v.plot. I do this so in other routines where I want # to inquire about yname settings I don't have to remember # that plot uses comment1: my_tpl.dataname.priority = 0 #+ Default values of title my_tpl.xname.priority = 0 # and axis title is off my_tpl.yname.priority = 0 if title != None: my_tpl.dataname.priority = 1 my_tpl.dataname.x = (bburx-bbllx)/2.0 + bbllx my_tpl.dataname.y = bbury \ + (1.7 * title_fontht * fontht2norm) my_tpl.dataname.texttable = ttab_title my_tpl.dataname.textorientation = tori_title if xtitle != None: my_tpl.xname.priority = 1 my_tpl.xname.x = (bburx-bbllx)/2.0 + bbllx my_tpl.xname.y = my_tpl.xlabel1.y \ - (1.7 * xtitle_fontht * fontht2norm) my_tpl.xname.texttable = ttab_xtitle my_tpl.xname.textorientation = tori_xtitle if ytitle != None: my_tpl.yname.priority = 1 my_tpl.yname.x = my_tpl.ylabel1.x \ - ( 1.6 * ytitle_fontht * fontht2norm ) my_tpl.yname.y = (bbury-bblly)/2.0 + bblly my_tpl.yname.texttable = ttab_ytitle my_tpl.yname.textorientation = tori_ytitle my_tpl.comment1.priority = my_tpl.yname.priority #+ Duplicate yname my_tpl.comment1.x = my_tpl.yname.x # into comment1 my_tpl.comment1.y = my_tpl.yname.y my_tpl.comment1.texttable = my_tpl.yname.texttable my_tpl.comment1.textorientation = my_tpl.yname.textorientation #- Render plot (turn off lines by setting line color to white # if psym is > 0): if psym > 0: xy_gm.linecolor = 240 v.plot( x, y, xy_gm, my_tpl \ , xname=xtitle, comment1=ytitle, name=title ) #- Add annotation in upper-left corner (if selected): if annot != None: annot_aslist = annot.splitlines() xval = N.zeros(len(annot_aslist)) + 0.03 yval = 0.97 - ( N.arange(len(annot_aslist)) \ * 1.6 * text_annot.height * fontht2norm ) text_annot.x = xval.tolist() text_annot.y = yval.tolist() text_annot.string = annot_aslist v.plot(text_annot) #- Update active_canvas in system variables object: sysvar.__class__.active_canvas_base_gm = 'xygm' + uniq_str sysvar.__class__.active_canvas_base_tpl = 'mytpl' + uniq_str sysvar.__class__.active_canvas = v del v #------------------------- Plot Using GracePlot ------------------------ if plotpkg == 'grace': #- Open plot session: v = gracePlot() #- Set plot symbols and linestyles. GracePlot only allows you # to turn on or off; you can't choose exactly what you get: if psym == 0: symbols = None linetypes = 1 elif psym > 0: print "plot: gracePlot always plots a line" print "plot: gracePlot only allows symbols be on or off" symbols = 1 linetypes = 1 else: print "plot: gracePlot only allows symbols be on or off" print "plot: gracePlot only allows lines be on or off" symbols = 1 linetypes = 1 #- Plot and add titling: v.plot(x, y, symbols=symbols, styles=linetypes) if title != None: v.title(title) if xtitle != None: v.xlabel(xtitle) if ytitle != None: v.ylabel(ytitle) #--------------------------- Plot Using SciTK -------------------------- if plotpkg == 'scitk': #- Establish graphics window and data variable: if (xtitle != None) or (ytitle != None) or (title != None) or \ (annot != None): print "plot: Titles and annot have no effect in Sci/Tk" root = Tkinter.Tk() data = N.zeros([len(x),2], typecode=N.Float) data[:,0] = x[:] data[:,1] = y[:] #- Set plot symbols and linestyles (key:value corresponds # to IDL code:Scientific code). Objects pts and line are # the plot symbols and line type, respectively: symbols = { 1: 'plus', 3: 'dot', 5: 'triangle', 6: 'square' } try: pts = SciTk.PolyMarker( data, marker=symbols[abs(psym)] ) except KeyError: if psym != 0: print "plot: Sci/Tk lacks chosen psym - using square" pts = SciTk.PolyMarker( data, marker=symbols[6] ) linetypes = { 0: None } try: line = SciTk.PolyLine( data, width=1, color='black' \ , stipple=linetypes[linestyle] ) except KeyError: if psym <= 0: print "plot: Sci/Tk lacks chosen linestyle - using solid" line = SciTk.PolyLine( data, width=1, color='black' \ , stipple=linetypes[0] ) #- Establish plot: xsize=500; ysize=386 plot = SciTk.PlotCanvas( root, xsize, ysize ) if psym < 0: plot.draw( line, xaxis='automatic', yaxis='automatic' ) plot.draw( pts , xaxis='automatic', yaxis='automatic' ) elif psym == 0: plot.draw( line, xaxis='automatic', yaxis='automatic' ) else: plot.draw( pts , xaxis='automatic', yaxis='automatic' ) plot.pack() # ===== end file =====