Top

madrigalWebPlot.madrigalPlot module

madrigalPlot is the module that produces plots of Madrigal data.

It is meant to be included as part of the Remote Python Madrigal API.

Presently based on madplotlib: http://matplotlib.sourceforge.net/

$Id: madrigalPlot.py 3317 2011-01-27 21:56:15Z brideout $

"""madrigalPlot is the module that produces plots of Madrigal data.

It is meant to be included as part of the Remote Python Madrigal API.

Presently based on madplotlib: http://matplotlib.sourceforge.net/


$Id: madrigalPlot.py 3317 2011-01-27 21:56:15Z brideout $

"""
import sys,os
import os.path
import traceback
import types
import datetime
import bisect
import math

import numpy

import madrigalWeb.madrigalWeb


def defineHomeEnvVariable():
    """defineHomeEnvVariable makes sure HOME env variable is defined, as required by matplotlib.

    If not defined, sets HOME to PWD
    """
    try:
        os.environ['HOME']
        return
    except KeyError:
        os.environ['HOME'] = os.getcwd()


defineHomeEnvVariable()
try:
    import matplotlib
except:
    print 'You need to install the numpy and matplotlib python module first.'
    print '    For numpy, go to http://numpy.scipy.org/.'
    print '    For matplotlib, go to http://matplotlib.sourceforge.net/.'
    sys.exit(-1)
# set rendering
matplotlib.use('Agg')
import matplotlib.pylab
import matplotlib.colors
import matplotlib.cm



def convertToAbsoluteTimeStr(xticks, noTime=False):
    """convertToAbsoluteTimeStr converts a list of strings containing seconds since 1/1/1950 to datetime string.
    
    Input: xticks - a list of strings containing seconds since 1/1/1950
    
    Returns: a list of strings formated as YYYY-MM-DD HH-MM-SS.  If noTime, format as YYYY-MM-DD
    """
    datetime1950 = datetime.datetime(1950,1,1,0,0,0)
    
    newList = []
    
    for item in xticks:
        seconds = long(item)
        newDatetime = datetime1950 + datetime.timedelta(0, seconds)
        if noTime:
            newList.append(newDatetime.strftime('%Y-%m-%d'))
        else:
            newList.append(newDatetime.strftime('%Y-%m-%d %H:%M:%S'))
    
    return newList


def get_vo_cmap():
    """get_vo_cmap is a function to return a colormap optimized to show sign changes in the middle of the range.
    """
    LUTSIZE = matplotlib.rcParams['image.lut']

    _vo_cm_data =     {'red':   ((0, 0.75, 0.75), (0.4, 0.0, 0.0), (0.5, 0.0, 0.0),   (0.6, 1.0, 1.0),   (1.0, 1.0, 1.0)),
                       'green': ((0, 1.0, 1.0), (0.4, 0.5, 0.5), (0.5, 0.25, 0.25), (0.6, 0.25, 0.25), (1.0, 1.0, 1.0)),
                       'blue':  ((0, 1.0, 1.0), (0.4, 1.0, 1.0), (0.5, 0.5, 0.5),   (0.6, 0.0, 0.0),   (1.0, 0.5, 0.5))}


    vo_cm = matplotlib.colors.LinearSegmentedColormap('vo_cm',_vo_cm_data, LUTSIZE)

    return vo_cm



class madScatterPlot:
    """madScatterPlot is the class the produces two dimensional scatter plots of x versus y.


    """
    def __init__(self, isprintText,
                 titleStr,
                 xLabelStr,
                 yLabelStr,
                 fullFilename,
                 useAbsoluteTime = False,
                 startTime = None,
                 endTime = None,
                 maxNumPoints = None,
                 yMin=None,
                 yMax=None):
        """__init__ writes a madScatter plot to a file.

        Inputs:

            isprintText - a string giving isprint output without headers. First parameter
                          must be UTH or UT1, depending on whether time scale should be relative
            			  to the experiment beginning (UTH) or absolute (UT1).
            			  The second must be the parameter to be plotted. Any
                          missing data should be written as "missing" or other string that
                          cannot be converted to a float. 
            
            titleStr - plot title (string) - should describe parameter being plotted
            
            xLabelStr - x label string
	    
	        yLabelStr - ylabel string

            fullFilename - full path of file containing pcolor plot to be saved.  Extension must
                           be jpeg or png, or exception thrown.
			    
	        useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
	                      since beginning of experiment (UTH).  If useAbsoluteTime is true, first
			              parameter in isprintText must be UT1, if false, it must be UTH.

    	    startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
    	                in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
    	                startTime must be in units of UTH (hours since midnight UT of first day of
    	                experiment). Default is None, which means start at lowest time found.
    
    	    endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
    	              in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
    	              endTime must be in units of UTH (hours since midnight UT of first day of
    	              experiment). Default is None, which means end at largest time found.
    
    	    maxNumPoints - maximum number of points to plot.  If not None, truncate isprintText if
    	                   needed to have at most maxNumPoints lines.
        
        
        Returns: void

        Affects: None
        """
        self.__missing = 1.0E30 # special value, since Numeric doesn't handle NaN
        self.__parameter_count = 2

        if maxNumPoints != None:
            isprintText = self.__truncateIsprint(isprintText, maxNumPoints)
               
        # convert the input data into numeric arrays of float assuming no headers and filter the missing values.

        try:
            split_data = isprintText.split()
            float_data = map(self.__filter_missing, split_data)
            array_data = numpy.asarray(float_data)
            array_data = numpy.reshape(array_data,(-1,self.__parameter_count))
        except:
            traceback.print_exc()
            raise ValueError, 'input text is not parseable'
        
        # find min, max of x (time)
        if startTime == None:
            xMin = array_data[0,0]
        else:
            xMin = startTime
        if endTime == None:
            xMax = array_data[-1,0]
        else:
            xMax = endTime
        
	    
        # make sure there's valid data
        isValidData = False
        for y in array_data[:,1]:
            if y == self.__missing:
                continue
            isValidData = True
            break
		
        if not isValidData:
            raise ValueError, 'No valid y data found'

      
        matplotlib.pylab.scatter(array_data[:,0],array_data[:,1])

        matplotlib.pylab.xlabel(xLabelStr)
        matplotlib.pylab.ylabel(yLabelStr)
        matplotlib.pylab.yticks()
        matplotlib.pylab.xlim(xMin, xMax)
        if yMin != None:
            matplotlib.pylab.ylim(ymin=yMin)
        if yMax != None:
            matplotlib.pylab.ylim(ymax=yMax)
        matplotlib.pylab.title(titleStr)

        if useAbsoluteTime:
            locs, labels = matplotlib.pylab.xticks()
            if len(locs) > 5:
                # truncate by len(locs) / 5
                scale = 1 + int(len(locs) / 5)
                new_locs = []
                for i in range(len(locs)):
                    if i % scale == 0:
                        new_locs.append(locs[i])
                locs = new_locs
            newXTicks = convertToAbsoluteTimeStr(locs)
            matplotlib.pylab.xticks(locs, newXTicks, rotation=15)
	    
        matplotlib.pylab.xticks()

        # get the handle to the figure now that it has been created so we can manipulate on subsequent calls.
        
        #self.__figure = matplotlib.pylab.gcf()
          
        matplotlib.pylab.savefig(fullFilename)
        #matplotlib.pylab.show()

        matplotlib.pylab.clf()


    def __truncateIsprint(self, isprintText, maxLines):
        """__truncateIsprint truncates isprintText to have maxLines at most.
        """
        isprintList = isprintText.split('\n')
        if len(isprintList) < maxLines:
            return isprintText
        else:
            dropNumber = int(1 + len(isprintList)/maxLines)
            newline = '\n'
            newIsprintText = newline.join(isprintList[::dropNumber])

            return newIsprintText
        

    def __filter_missing(self,x):
        try:
            return float(x)
        except:
            return self.__missing

        


        


class madPcolorPlot:
    """madPcolorPlot is the class that produces pcolor plots of x versus y with z intensity.

    Assumes the x axis is time.

    Usage example::

    	obj = madPcolorPlot(isprintText,
                        'Nel (log(m^-3)) - Millstone Hill - Oct. 30, 2003 - Alt code',
                        'Hours since midnight UT Oct. 30, 2003',
                        'Altitude (km)',
                        './isprint.png',
                        minColormap = 9,
                        maxColormap = 12,
			smoothAltitude = False)    

    Non-standard Python modules used:
    matplotlib

    Change history:

    Written by "Bill Rideout":mailto:wrideout@haystack.mit.edu  Mar. 31, 2005
    """


    def __init__(self, isprintText,
                 titleStr,
                 xLabelStr,
                 yLabelStr,
                 fullFilename,
                 minColormap = None,
                 maxColormap = None,
                 smoothAltitude = True, 
                 insertDataGap = 5,
                 useAbsoluteTime = False,
                 startTime = None,
                 endTime = None,
                 sortTimeFlag = False,
                 maxNumTimes = None,
                 maxNumAlt = None,
                 truncateIsprint = False,
                 colorMap = matplotlib.cm.jet,
                 yMinimum = None,
                 yMaximum = None,
                 altYTitle = None,
                 altYLabels = None):
        """__init__ writes a madPColorPlot to a file.

        Inputs:

            isprintText - a string giving isprint output without headers. First parameter
                          must be UTH or UT1, depending on whether time scale should be relative
            			  to the experiment beginning (UTH) or absolute (UT1).
            			  The second must be gdalt, and third parameter to be plotted.
            
            titleStr - plot title (string) - should describe parameter being plotted
	    
	        xLabelStr - x label string
	    
	        yLabelStr - ylabel string

            fullFilename - full path of file containing pcolor plot to be saved.  Extension must
                           be jpeg or png, or exception thrown.
            
            minColormap - minimum parameter value (defaults to lowest parameter value)

            maxColormap - maximum parameter value (defaults to highest parameter value).  However, if
                          both minColormap and maxColormap == None, autoscaling applied.
	    
	        smoothAltitude - if True, extrapolate between existing data between altitudes to fill
	                         in missing data; if False, leave missing
			     
	        insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
	                    being plotted are ordered, and the time gap larger than 90% of the rest is determined.
			            Any time interval more than insertDataGap times bigger is then considered missing
			            data.  Defaults to five.  If None, no gaps are ever inserted.  For data with close
			            to uniform time intervals, no gaps will be inserted.
			    
    	    useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
    	                      since beginning of experiment (UTH).  If useAbsoluteTime is true, first
			                  parameter in isprintText must be UT1, if false, it must be UTH.
        
            startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
	                in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
	                startTime must be in units of UTH (hours since midnight UT of first day of
	                experiment). Default is None, which means start at lowest time found.

    	    endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
    	              in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
    	              endTime must be in units of UTH (hours since midnight UT of first day of
    	              experiment). Default is None, which means end at largest time found.
    
    	    sortTimeFlag - if true, check that time is correctly sorted.  If false (the default),
    	                   assume time already sorted
    
    	    maxNumTimes - if not None, decimate the number of times in the isprint string to
    	                  maxNumTimes.  If None (the default), plot all times.
    
    	    maxNumAlt - if not None, reduce the number of altitudes to maxNumAlt.  If None (the default),
    	                plot all altitudes.
    
    	    truncateIsprint - if True, and both maxNumTimes and maxNumAlt not = None, then truncate
    	                       the number of isprint lines to be maxNumTimes * maxNumAlt
    
    	    colorMap - sets colormap.  It not given, defaults to matplotlib.cm.jet
    
    	    yMinimum - minumum y value.  If None (default), set by data y minimum.
    
    	    yMaximum - maximum y value.  If None (default), set by data y maximum.
    
    	    altYTitle - title of right (second) y axis.  If None (the default),
    	                 no Y axis on the right side.
    
    	    altYLabels - a list of Y labels (strings) for the right axis.  If None (the default),
    	                 no Y labels on the right axis.

	    
	              
        Returns: void

        Affects: None
        """
        self.__missing = 1.0E30 # special value, used to create a masked array
        self.__parameter_count = 3
        
        isprintText = self._removeMissing(isprintText)

        if truncateIsprint and maxNumTimes != None and maxNumAlt != None:
            isprintText = self.__truncateIsprint(isprintText, maxNumTimes * maxNumAlt)

        # since matplotlib pcolor wants a regular grid, we create a grid with all altitudes

        # convert the input data into numeric arrays of float assuming no headers and filter the missing values.

        try:
            split_data = isprintText.split()
            float_data = map(self.__filter_missing, split_data)
            array_data = numpy.asarray(float_data)
            array_data = numpy.reshape(array_data,(-1,self.__parameter_count))
        except:
            traceback.print_exc()
            raise ValueError, 'input text is not parseable'

        if sortTimeFlag:
            array_data = self.sortArrayInTime(array_data)

        if maxNumTimes != None and maxNumTimes != 0 and not truncateIsprint:
            array_data = self.decimateTimes(array_data, int(maxNumTimes), insertDataGap)
        
        # The first pass through is to obtain the number and range of the  x and y variables
        
        xList = []
        yList = []
        zList = []
        zMin = None
        zMax = None

        # loop over all the lines of data in the array
        for j in range(len(array_data)):
            try:
                
                x = array_data[j][0]
                y = array_data[j][1]
                z = array_data[j][2]

                if x not in xList:
                    xList.append(x)

                if y not in yList:
                    yList.append(y)

                if z != self.__missing:
                    zList.append(z)

                if zMin != None:
                    if z < zMin and z != self.__missing:
                        zMin = z
                elif z != self.__missing:
                    zMin = z

                if zMax != None:
                    if z > zMax and z != self.__missing:
                        zMax = z
                elif z != self.__missing:
                    zMax = z

            except:
                continue
		
        if zMin == None:
            raise ValueError, 'No valid z data found'

        # if both minColormap and maxColormap == None, use autoscaling
        if minColormap == None and maxColormap == None:
            zList.sort()
            d10 = zList[int(len(zList)*0.10)]
            d90 = zList[int(len(zList)*0.90)]

            zMin = d10 - (d90-d10) * 0.75
            zMax = d90 + (d90-d10) * 0.75

        # now sort the X and Y axis lists and pull their length
        
        xList.sort()
        if startTime == None:
            xMin = xList[0]
        else:
            xMin = startTime
        if endTime == None:
            xMax = xList[-1]
        else:
            xMax = endTime
        yList.sort()
        max_x_dimension = len(xList)
        max_y_dimension = len(yList)

        if yMinimum == None:
            yMinimum = yList[0]
        if yMaximum == None:
            yMaximum = yList[-1]

        self.truncateAlt = False
        if maxNumAlt != None:
            if max_y_dimension > maxNumAlt:
                self.truncateAlt = True

        # build dictonary of indexes into xList
        self.xListDict = {}
        for i in range(len(xList)):
            self.xListDict[xList[i]] = i

        # if self.truncateAlt == False, build dictonary of indexes into yList,
        # else truncate y values by builing a list of maxNumAlt ranges
        if self.truncateAlt == False:
            self.yListDict = {}
            for i in range(len(yList)):
                self.yListDict[yList[i]] = i
        else:
            self.yListRanges = []
            for i in range(maxNumAlt):
                self.yListRanges.append(yList[int(i*(len(yList)/maxNumAlt))])
            max_y_dimension = maxNumAlt

        # now build arrays to handle the X axis label, Y axis label, and the Z data

        X = numpy.zeros((max_x_dimension, max_y_dimension), numpy.float32)
        Y = numpy.zeros((max_x_dimension, max_y_dimension), numpy.float32)
        Z = numpy.ones((max_x_dimension, max_y_dimension), numpy.float32)

        # all parameter values default to missing
        Z = Z * self.__missing

        # fill the X and Y arrays

        for i in range(max_x_dimension):
            for j in range(max_y_dimension):
                X[i][j] = float(xList[i])
                if self.truncateAlt:
                    Y[i][j] = float(yList[int(j*(len(yList)/maxNumAlt))])
                else:
                    Y[i][j] = float(yList[j])

    
        # Now load up the data array Z with the array_data measurements as a function of x and y
        previousIndex = None
        previousValue = None
        presentTime = None
        newTimeFound = True

        for k in range(len(array_data)):
            try:

                xdata = array_data[k][0]
                ydata = array_data[k][1]
                zdata = array_data[k][2]

                if zdata == self.__missing:
                    continue

                if xdata != presentTime:
                    newTimeFound = True
                else:
                    newTimeFound = False
                presentTime = xdata

                # now find the right place in the array for this data point
                i = self.xListDict[xdata]
                j = self.__getYIndex(ydata)

                Z[i][j] = zdata
		
		# now see if we need to fill in any gaps
                if (not newTimeFound) and smoothAltitude:
                    if previousIndex < j - 1:
                        # fill in all missing points
                        for k in range(previousIndex + 1, j):
                            # simply average between the points based on index
                            thisValue = previousValue + (zdata - previousValue)*(float(k-previousIndex)/float(j-previousIndex))
                            Z[i][k] = thisValue
			   
                previousIndex = j
                previousValue = zdata
		
                
            except:
                continue
		
        # insert missing data to represent gaps if needed
        if insertDataGap != None:
            # first find the time interval greater than 90% of others
            timeIntervalList = []
            for i in range(len(xList) - 1):
                timeIntervalList.append(xList[i+1] - xList[i])
            timeIntervalList.sort()
            index = int(len(timeIntervalList)*0.9)
            maxInterval = timeIntervalList[index]
	    
            for i in range(len(xList) - 1):
                if xList[i+1] - xList[i] > maxInterval * insertDataGap:
                    Z[i,:] = self.__missing


        # set up plotting parameters
        if minColormap == None:
            minColormap = zMin
        if maxColormap == None:
            maxColormap = zMax

        matplotlib.pylab.pcolor(X,Y,Z, shading='flat', vmin=minColormap, vmax=maxColormap, cmap = colorMap, norm = matplotlib.pylab.normalize())
        matplotlib.pylab.colorbar()

        matplotlib.pylab.xlabel(xLabelStr)
        matplotlib.pylab.ylabel(yLabelStr)
        matplotlib.pylab.xlim(xMin, xMax)
        matplotlib.pylab.ylim(yMinimum, yMaximum)
        matplotlib.pylab.yticks()
        matplotlib.pylab.title(titleStr)

        if useAbsoluteTime:
            locs, labels = matplotlib.pylab.xticks()
            if len(locs) > 5:
                # truncate by len(locs) / 5
                scale = 1 + int(len(locs) / 5)
                new_locs = []
                for i in range(len(locs)):
                    if i % scale == 0:
                        new_locs.append(locs[i])
                locs = new_locs
            newXTicks = convertToAbsoluteTimeStr(locs)
            matplotlib.pylab.xticks(locs, newXTicks, rotation=15)

        matplotlib.pylab.xticks()

        # add second y-axis if desired
        if altYTitle != None and altYLabels != None:
            ax2 = matplotlib.pylab.twinx()
            matplotlib.pylab.ylabel(altYTitle)
            ax2.yaxis.tick_right()
            matplotlib.pylab.yticks(range(len(altYLabels)), altYLabels)
            matplotlib.pylab.yticks()

        # get the handle to the figure now that it has been created so we can manipulate on subsequent calls.
        
        #self.__figure = matplotlib.pylab.gcf()
          
        matplotlib.pylab.savefig(fullFilename)
        #matplotlib.pylab.show()

        matplotlib.pylab.clf()



    def __filter_missing(self,x):
        try:
            return float(x)
        except:
            return self.__missing

    def __getYIndex(self, yvalue):
        """__getYIndex returns the correct index into the y dimension for a given y value.

        Input: yvalue - value of y parameter

        Returns: the correct index into the y dimension

        Algorithm: if self.truncateAlt == False, simple use the dictionary self.yListDict.  Else
        loop through self.yListRanges and return the first greater than the requested value
        """
        if self.truncateAlt == False:
            return self.yListDict[yvalue]
        else:
            i = bisect.bisect_left(self.yListRanges, yvalue)
            if i >= len(self.yListRanges):
                i = len(self.yListRanges) - 1
            return i
        
            
    def __truncateIsprint(self, isprintText, maxLines):
        """__truncateIsprint truncates isprintText to have maxLines at most.
        """
        isprintList = isprintText.split('\n')
        if len(isprintList) < maxLines:
            return isprintText
        else:
            dropNumber = int(1 + len(isprintList)/maxLines)
            newIsprintText = ''
            for i in range(0,len(isprintList),dropNumber):
                newIsprintText += isprintList[i] + '\n'

            return newIsprintText


        
    def displayToScreen(self):
        " to implement this takes a reworking away from pylab to use the underlying matplotlib code "
        pass 

    def getFigureHandle(self):
        return self.__figure


    def getAverage(self, X):
        """returns the average of items in a float array.  Does not including missing data.
        If all data missing, returns self.__missing
        """
        count = 0
        total = 0.0
        for i in range(X.shape[0]):
            if X[i] != self.__missing:
                count += 1
                total += X[i]

        if count == 0:
            return self.__missing
        else:
            return total / float(count)


    def sortArrayInTime(self, array_data):
        """sortArrayInTime sorts a two-dimensional array so that the first element in each row (time) is in ascending order.

        Input: array_data - two-dimensional array to be sorted by rearranging rows so
               that the first element in each row (time) is in ascending order

        Returns: new_array
        """

        sortIndex = numpy.argsort(array_data[:,0])

        # if already sorted, just return original array
        if sortIndex == numpy.sort(sortIndex):
            return array_data
        
        new_array = numpy.zeros(array_data.shape, array_data.typecode())


        for i in range(len(sortIndex)):
            new_array[sortIndex[i],:] = array_data[i,:]


        return new_array


    def decimateTimes(self, array_data, maxNumTimes, insertDataGap):
        """decimateTimes decimates array_data to have at most maxNumTimes times.

        Input: array_data - two-dimensional array to be decimated by deleting times and missing data.

               maxNumTimes: int representing the maximum number of times to keep in array_data

               insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
	                    being plotted are ordered, and the time gap larger than 90% of the rest is determined.
			    Note that this parameter is used here to stop the truncation of isprint lines that
			    will eventually be considered edge lines.

        Returns: new array built from decimated array_data

        """

        # get the number of times in array_data, and make a list of all unique times
        numTimes = 0
        uniqueTimes = []
        time_array = array_data[:, 0]
        for i in range(len(time_array)):
            if i == 0:
               numTimes =  1
               uniqueTimes.append(time_array[i])
            elif time_array[i-1] != time_array[i]:
                uniqueTimes.append(time_array[i])
                numTimes +=  1

        if numTimes <= maxNumTimes:
            return array_data
        

        # insert missing data to represent gaps if needed
        gapTimes = []
        if insertDataGap != None:
            # first find the time interval greater than 90% of others
            timeIntervalList = []
            for i in range(len(time_array) - 1):
                if time_array[i+1] == time_array[i]:
                    continue
                timeIntervalList.append(time_array[i+1] - time_array[i])
            timeIntervalList.sort()
            index = int(len(timeIntervalList)*0.9)
            maxInterval = timeIntervalList[index]
	    
            for i in range(len(time_array) - 1):
                if time_array[i+1] - time_array[i] > maxInterval * insertDataGap:
                    gapTimes.append(time_array[i+1])
                    gapTimes.append(time_array[i])

        # get the number of times to skip each time
        numSkip = numTimes/maxNumTimes

        # get the number of rows in the new_array
        numRows = 0
        numTimes = 0
        useThisTime = False
        for i in range(len(time_array)):
            if i == 0:
                numTimes =  1
            elif time_array[i-1] != time_array[i]:
                numTimes +=  1
                if numTimes % (numSkip + 1) == 0 or time_array[i] in gapTimes:
                    useThisTime = True
                    if array_data[i, -1] != self.__missing:
                        numRows += 1
                else:
                    useThisTime = False
            else:
                if useThisTime:
                    if array_data[i, -1] != self.__missing:
                        numRows += 1

        # create new_array
        new_array = numpy.zeros((numRows, array_data.shape[1]), array_data.typecode())

        # copy selected rows to new_array
        numRows = 0
        numTimes = 0
        useThisTime = False
        for i in range(len(time_array)):
            if i == 0:
                numTimes =  1
            elif time_array[i-1] != time_array[i]:
                numTimes +=  1
                if numTimes % (numSkip + 1) == 0 or time_array[i] in gapTimes:
                    useThisTime = True
                    if array_data[i, -1] != self.__missing:
                        new_array[numRows,:] = array_data[i,:]
                        numRows += 1
                else:
                    useThisTime = False
            else:
                if useThisTime:
                    if array_data[i, -1] != self.__missing:
                        new_array[numRows,:] = array_data[i,:]
                        numRows += 1

        return new_array
    
    def _removeMissing(self, isprintText):
        """ returns modified isprintText with all missing data removed """
        lines = isprintText.split('\n')
        newIsprintText = ''
        for line in lines:
            items = line.split()
            if len(items) < 3:
                continue
            try:
                for item in items:
                    float(item)
                newIsprintText += line
            except:
                continue
            
        return(newIsprintText)

Functions

def convertToAbsoluteTimeStr(

xticks, noTime=False)

convertToAbsoluteTimeStr converts a list of strings containing seconds since 1/1/1950 to datetime string.

Input: xticks - a list of strings containing seconds since 1/1/1950

Returns: a list of strings formated as YYYY-MM-DD HH-MM-SS. If noTime, format as YYYY-MM-DD

def convertToAbsoluteTimeStr(xticks, noTime=False):
    """convertToAbsoluteTimeStr converts a list of strings containing seconds since 1/1/1950 to datetime string.
    
    Input: xticks - a list of strings containing seconds since 1/1/1950
    
    Returns: a list of strings formated as YYYY-MM-DD HH-MM-SS.  If noTime, format as YYYY-MM-DD
    """
    datetime1950 = datetime.datetime(1950,1,1,0,0,0)
    
    newList = []
    
    for item in xticks:
        seconds = long(item)
        newDatetime = datetime1950 + datetime.timedelta(0, seconds)
        if noTime:
            newList.append(newDatetime.strftime('%Y-%m-%d'))
        else:
            newList.append(newDatetime.strftime('%Y-%m-%d %H:%M:%S'))
    
    return newList

def defineHomeEnvVariable(

)

defineHomeEnvVariable makes sure HOME env variable is defined, as required by matplotlib.

If not defined, sets HOME to PWD

def defineHomeEnvVariable():
    """defineHomeEnvVariable makes sure HOME env variable is defined, as required by matplotlib.

    If not defined, sets HOME to PWD
    """
    try:
        os.environ['HOME']
        return
    except KeyError:
        os.environ['HOME'] = os.getcwd()

def get_vo_cmap(

)

get_vo_cmap is a function to return a colormap optimized to show sign changes in the middle of the range.

def get_vo_cmap():
    """get_vo_cmap is a function to return a colormap optimized to show sign changes in the middle of the range.
    """
    LUTSIZE = matplotlib.rcParams['image.lut']

    _vo_cm_data =     {'red':   ((0, 0.75, 0.75), (0.4, 0.0, 0.0), (0.5, 0.0, 0.0),   (0.6, 1.0, 1.0),   (1.0, 1.0, 1.0)),
                       'green': ((0, 1.0, 1.0), (0.4, 0.5, 0.5), (0.5, 0.25, 0.25), (0.6, 0.25, 0.25), (1.0, 1.0, 1.0)),
                       'blue':  ((0, 1.0, 1.0), (0.4, 1.0, 1.0), (0.5, 0.5, 0.5),   (0.6, 0.0, 0.0),   (1.0, 0.5, 0.5))}


    vo_cm = matplotlib.colors.LinearSegmentedColormap('vo_cm',_vo_cm_data, LUTSIZE)

    return vo_cm

Classes

class madPcolorPlot

madPcolorPlot is the class that produces pcolor plots of x versus y with z intensity.

Assumes the x axis is time.

Usage example::

obj = madPcolorPlot(isprintText,
                'Nel (log(m^-3)) - Millstone Hill - Oct. 30, 2003 - Alt code',
                'Hours since midnight UT Oct. 30, 2003',
                'Altitude (km)',
                './isprint.png',
                minColormap = 9,
                maxColormap = 12,
                smoothAltitude = False)

Non-standard Python modules used: matplotlib

Change history:

Written by "Bill Rideout":mailto:wrideout@haystack.mit.edu Mar. 31, 2005

class madPcolorPlot:
    """madPcolorPlot is the class that produces pcolor plots of x versus y with z intensity.

    Assumes the x axis is time.

    Usage example::

    	obj = madPcolorPlot(isprintText,
                        'Nel (log(m^-3)) - Millstone Hill - Oct. 30, 2003 - Alt code',
                        'Hours since midnight UT Oct. 30, 2003',
                        'Altitude (km)',
                        './isprint.png',
                        minColormap = 9,
                        maxColormap = 12,
			smoothAltitude = False)    

    Non-standard Python modules used:
    matplotlib

    Change history:

    Written by "Bill Rideout":mailto:wrideout@haystack.mit.edu  Mar. 31, 2005
    """


    def __init__(self, isprintText,
                 titleStr,
                 xLabelStr,
                 yLabelStr,
                 fullFilename,
                 minColormap = None,
                 maxColormap = None,
                 smoothAltitude = True, 
                 insertDataGap = 5,
                 useAbsoluteTime = False,
                 startTime = None,
                 endTime = None,
                 sortTimeFlag = False,
                 maxNumTimes = None,
                 maxNumAlt = None,
                 truncateIsprint = False,
                 colorMap = matplotlib.cm.jet,
                 yMinimum = None,
                 yMaximum = None,
                 altYTitle = None,
                 altYLabels = None):
        """__init__ writes a madPColorPlot to a file.

        Inputs:

            isprintText - a string giving isprint output without headers. First parameter
                          must be UTH or UT1, depending on whether time scale should be relative
            			  to the experiment beginning (UTH) or absolute (UT1).
            			  The second must be gdalt, and third parameter to be plotted.
            
            titleStr - plot title (string) - should describe parameter being plotted
	    
	        xLabelStr - x label string
	    
	        yLabelStr - ylabel string

            fullFilename - full path of file containing pcolor plot to be saved.  Extension must
                           be jpeg or png, or exception thrown.
            
            minColormap - minimum parameter value (defaults to lowest parameter value)

            maxColormap - maximum parameter value (defaults to highest parameter value).  However, if
                          both minColormap and maxColormap == None, autoscaling applied.
	    
	        smoothAltitude - if True, extrapolate between existing data between altitudes to fill
	                         in missing data; if False, leave missing
			     
	        insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
	                    being plotted are ordered, and the time gap larger than 90% of the rest is determined.
			            Any time interval more than insertDataGap times bigger is then considered missing
			            data.  Defaults to five.  If None, no gaps are ever inserted.  For data with close
			            to uniform time intervals, no gaps will be inserted.
			    
    	    useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
    	                      since beginning of experiment (UTH).  If useAbsoluteTime is true, first
			                  parameter in isprintText must be UT1, if false, it must be UTH.
        
            startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
	                in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
	                startTime must be in units of UTH (hours since midnight UT of first day of
	                experiment). Default is None, which means start at lowest time found.

    	    endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
    	              in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
    	              endTime must be in units of UTH (hours since midnight UT of first day of
    	              experiment). Default is None, which means end at largest time found.
    
    	    sortTimeFlag - if true, check that time is correctly sorted.  If false (the default),
    	                   assume time already sorted
    
    	    maxNumTimes - if not None, decimate the number of times in the isprint string to
    	                  maxNumTimes.  If None (the default), plot all times.
    
    	    maxNumAlt - if not None, reduce the number of altitudes to maxNumAlt.  If None (the default),
    	                plot all altitudes.
    
    	    truncateIsprint - if True, and both maxNumTimes and maxNumAlt not = None, then truncate
    	                       the number of isprint lines to be maxNumTimes * maxNumAlt
    
    	    colorMap - sets colormap.  It not given, defaults to matplotlib.cm.jet
    
    	    yMinimum - minumum y value.  If None (default), set by data y minimum.
    
    	    yMaximum - maximum y value.  If None (default), set by data y maximum.
    
    	    altYTitle - title of right (second) y axis.  If None (the default),
    	                 no Y axis on the right side.
    
    	    altYLabels - a list of Y labels (strings) for the right axis.  If None (the default),
    	                 no Y labels on the right axis.

	    
	              
        Returns: void

        Affects: None
        """
        self.__missing = 1.0E30 # special value, used to create a masked array
        self.__parameter_count = 3
        
        isprintText = self._removeMissing(isprintText)

        if truncateIsprint and maxNumTimes != None and maxNumAlt != None:
            isprintText = self.__truncateIsprint(isprintText, maxNumTimes * maxNumAlt)

        # since matplotlib pcolor wants a regular grid, we create a grid with all altitudes

        # convert the input data into numeric arrays of float assuming no headers and filter the missing values.

        try:
            split_data = isprintText.split()
            float_data = map(self.__filter_missing, split_data)
            array_data = numpy.asarray(float_data)
            array_data = numpy.reshape(array_data,(-1,self.__parameter_count))
        except:
            traceback.print_exc()
            raise ValueError, 'input text is not parseable'

        if sortTimeFlag:
            array_data = self.sortArrayInTime(array_data)

        if maxNumTimes != None and maxNumTimes != 0 and not truncateIsprint:
            array_data = self.decimateTimes(array_data, int(maxNumTimes), insertDataGap)
        
        # The first pass through is to obtain the number and range of the  x and y variables
        
        xList = []
        yList = []
        zList = []
        zMin = None
        zMax = None

        # loop over all the lines of data in the array
        for j in range(len(array_data)):
            try:
                
                x = array_data[j][0]
                y = array_data[j][1]
                z = array_data[j][2]

                if x not in xList:
                    xList.append(x)

                if y not in yList:
                    yList.append(y)

                if z != self.__missing:
                    zList.append(z)

                if zMin != None:
                    if z < zMin and z != self.__missing:
                        zMin = z
                elif z != self.__missing:
                    zMin = z

                if zMax != None:
                    if z > zMax and z != self.__missing:
                        zMax = z
                elif z != self.__missing:
                    zMax = z

            except:
                continue
		
        if zMin == None:
            raise ValueError, 'No valid z data found'

        # if both minColormap and maxColormap == None, use autoscaling
        if minColormap == None and maxColormap == None:
            zList.sort()
            d10 = zList[int(len(zList)*0.10)]
            d90 = zList[int(len(zList)*0.90)]

            zMin = d10 - (d90-d10) * 0.75
            zMax = d90 + (d90-d10) * 0.75

        # now sort the X and Y axis lists and pull their length
        
        xList.sort()
        if startTime == None:
            xMin = xList[0]
        else:
            xMin = startTime
        if endTime == None:
            xMax = xList[-1]
        else:
            xMax = endTime
        yList.sort()
        max_x_dimension = len(xList)
        max_y_dimension = len(yList)

        if yMinimum == None:
            yMinimum = yList[0]
        if yMaximum == None:
            yMaximum = yList[-1]

        self.truncateAlt = False
        if maxNumAlt != None:
            if max_y_dimension > maxNumAlt:
                self.truncateAlt = True

        # build dictonary of indexes into xList
        self.xListDict = {}
        for i in range(len(xList)):
            self.xListDict[xList[i]] = i

        # if self.truncateAlt == False, build dictonary of indexes into yList,
        # else truncate y values by builing a list of maxNumAlt ranges
        if self.truncateAlt == False:
            self.yListDict = {}
            for i in range(len(yList)):
                self.yListDict[yList[i]] = i
        else:
            self.yListRanges = []
            for i in range(maxNumAlt):
                self.yListRanges.append(yList[int(i*(len(yList)/maxNumAlt))])
            max_y_dimension = maxNumAlt

        # now build arrays to handle the X axis label, Y axis label, and the Z data

        X = numpy.zeros((max_x_dimension, max_y_dimension), numpy.float32)
        Y = numpy.zeros((max_x_dimension, max_y_dimension), numpy.float32)
        Z = numpy.ones((max_x_dimension, max_y_dimension), numpy.float32)

        # all parameter values default to missing
        Z = Z * self.__missing

        # fill the X and Y arrays

        for i in range(max_x_dimension):
            for j in range(max_y_dimension):
                X[i][j] = float(xList[i])
                if self.truncateAlt:
                    Y[i][j] = float(yList[int(j*(len(yList)/maxNumAlt))])
                else:
                    Y[i][j] = float(yList[j])

    
        # Now load up the data array Z with the array_data measurements as a function of x and y
        previousIndex = None
        previousValue = None
        presentTime = None
        newTimeFound = True

        for k in range(len(array_data)):
            try:

                xdata = array_data[k][0]
                ydata = array_data[k][1]
                zdata = array_data[k][2]

                if zdata == self.__missing:
                    continue

                if xdata != presentTime:
                    newTimeFound = True
                else:
                    newTimeFound = False
                presentTime = xdata

                # now find the right place in the array for this data point
                i = self.xListDict[xdata]
                j = self.__getYIndex(ydata)

                Z[i][j] = zdata
		
		# now see if we need to fill in any gaps
                if (not newTimeFound) and smoothAltitude:
                    if previousIndex < j - 1:
                        # fill in all missing points
                        for k in range(previousIndex + 1, j):
                            # simply average between the points based on index
                            thisValue = previousValue + (zdata - previousValue)*(float(k-previousIndex)/float(j-previousIndex))
                            Z[i][k] = thisValue
			   
                previousIndex = j
                previousValue = zdata
		
                
            except:
                continue
		
        # insert missing data to represent gaps if needed
        if insertDataGap != None:
            # first find the time interval greater than 90% of others
            timeIntervalList = []
            for i in range(len(xList) - 1):
                timeIntervalList.append(xList[i+1] - xList[i])
            timeIntervalList.sort()
            index = int(len(timeIntervalList)*0.9)
            maxInterval = timeIntervalList[index]
	    
            for i in range(len(xList) - 1):
                if xList[i+1] - xList[i] > maxInterval * insertDataGap:
                    Z[i,:] = self.__missing


        # set up plotting parameters
        if minColormap == None:
            minColormap = zMin
        if maxColormap == None:
            maxColormap = zMax

        matplotlib.pylab.pcolor(X,Y,Z, shading='flat', vmin=minColormap, vmax=maxColormap, cmap = colorMap, norm = matplotlib.pylab.normalize())
        matplotlib.pylab.colorbar()

        matplotlib.pylab.xlabel(xLabelStr)
        matplotlib.pylab.ylabel(yLabelStr)
        matplotlib.pylab.xlim(xMin, xMax)
        matplotlib.pylab.ylim(yMinimum, yMaximum)
        matplotlib.pylab.yticks()
        matplotlib.pylab.title(titleStr)

        if useAbsoluteTime:
            locs, labels = matplotlib.pylab.xticks()
            if len(locs) > 5:
                # truncate by len(locs) / 5
                scale = 1 + int(len(locs) / 5)
                new_locs = []
                for i in range(len(locs)):
                    if i % scale == 0:
                        new_locs.append(locs[i])
                locs = new_locs
            newXTicks = convertToAbsoluteTimeStr(locs)
            matplotlib.pylab.xticks(locs, newXTicks, rotation=15)

        matplotlib.pylab.xticks()

        # add second y-axis if desired
        if altYTitle != None and altYLabels != None:
            ax2 = matplotlib.pylab.twinx()
            matplotlib.pylab.ylabel(altYTitle)
            ax2.yaxis.tick_right()
            matplotlib.pylab.yticks(range(len(altYLabels)), altYLabels)
            matplotlib.pylab.yticks()

        # get the handle to the figure now that it has been created so we can manipulate on subsequent calls.
        
        #self.__figure = matplotlib.pylab.gcf()
          
        matplotlib.pylab.savefig(fullFilename)
        #matplotlib.pylab.show()

        matplotlib.pylab.clf()



    def __filter_missing(self,x):
        try:
            return float(x)
        except:
            return self.__missing

    def __getYIndex(self, yvalue):
        """__getYIndex returns the correct index into the y dimension for a given y value.

        Input: yvalue - value of y parameter

        Returns: the correct index into the y dimension

        Algorithm: if self.truncateAlt == False, simple use the dictionary self.yListDict.  Else
        loop through self.yListRanges and return the first greater than the requested value
        """
        if self.truncateAlt == False:
            return self.yListDict[yvalue]
        else:
            i = bisect.bisect_left(self.yListRanges, yvalue)
            if i >= len(self.yListRanges):
                i = len(self.yListRanges) - 1
            return i
        
            
    def __truncateIsprint(self, isprintText, maxLines):
        """__truncateIsprint truncates isprintText to have maxLines at most.
        """
        isprintList = isprintText.split('\n')
        if len(isprintList) < maxLines:
            return isprintText
        else:
            dropNumber = int(1 + len(isprintList)/maxLines)
            newIsprintText = ''
            for i in range(0,len(isprintList),dropNumber):
                newIsprintText += isprintList[i] + '\n'

            return newIsprintText


        
    def displayToScreen(self):
        " to implement this takes a reworking away from pylab to use the underlying matplotlib code "
        pass 

    def getFigureHandle(self):
        return self.__figure


    def getAverage(self, X):
        """returns the average of items in a float array.  Does not including missing data.
        If all data missing, returns self.__missing
        """
        count = 0
        total = 0.0
        for i in range(X.shape[0]):
            if X[i] != self.__missing:
                count += 1
                total += X[i]

        if count == 0:
            return self.__missing
        else:
            return total / float(count)


    def sortArrayInTime(self, array_data):
        """sortArrayInTime sorts a two-dimensional array so that the first element in each row (time) is in ascending order.

        Input: array_data - two-dimensional array to be sorted by rearranging rows so
               that the first element in each row (time) is in ascending order

        Returns: new_array
        """

        sortIndex = numpy.argsort(array_data[:,0])

        # if already sorted, just return original array
        if sortIndex == numpy.sort(sortIndex):
            return array_data
        
        new_array = numpy.zeros(array_data.shape, array_data.typecode())


        for i in range(len(sortIndex)):
            new_array[sortIndex[i],:] = array_data[i,:]


        return new_array


    def decimateTimes(self, array_data, maxNumTimes, insertDataGap):
        """decimateTimes decimates array_data to have at most maxNumTimes times.

        Input: array_data - two-dimensional array to be decimated by deleting times and missing data.

               maxNumTimes: int representing the maximum number of times to keep in array_data

               insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
	                    being plotted are ordered, and the time gap larger than 90% of the rest is determined.
			    Note that this parameter is used here to stop the truncation of isprint lines that
			    will eventually be considered edge lines.

        Returns: new array built from decimated array_data

        """

        # get the number of times in array_data, and make a list of all unique times
        numTimes = 0
        uniqueTimes = []
        time_array = array_data[:, 0]
        for i in range(len(time_array)):
            if i == 0:
               numTimes =  1
               uniqueTimes.append(time_array[i])
            elif time_array[i-1] != time_array[i]:
                uniqueTimes.append(time_array[i])
                numTimes +=  1

        if numTimes <= maxNumTimes:
            return array_data
        

        # insert missing data to represent gaps if needed
        gapTimes = []
        if insertDataGap != None:
            # first find the time interval greater than 90% of others
            timeIntervalList = []
            for i in range(len(time_array) - 1):
                if time_array[i+1] == time_array[i]:
                    continue
                timeIntervalList.append(time_array[i+1] - time_array[i])
            timeIntervalList.sort()
            index = int(len(timeIntervalList)*0.9)
            maxInterval = timeIntervalList[index]
	    
            for i in range(len(time_array) - 1):
                if time_array[i+1] - time_array[i] > maxInterval * insertDataGap:
                    gapTimes.append(time_array[i+1])
                    gapTimes.append(time_array[i])

        # get the number of times to skip each time
        numSkip = numTimes/maxNumTimes

        # get the number of rows in the new_array
        numRows = 0
        numTimes = 0
        useThisTime = False
        for i in range(len(time_array)):
            if i == 0:
                numTimes =  1
            elif time_array[i-1] != time_array[i]:
                numTimes +=  1
                if numTimes % (numSkip + 1) == 0 or time_array[i] in gapTimes:
                    useThisTime = True
                    if array_data[i, -1] != self.__missing:
                        numRows += 1
                else:
                    useThisTime = False
            else:
                if useThisTime:
                    if array_data[i, -1] != self.__missing:
                        numRows += 1

        # create new_array
        new_array = numpy.zeros((numRows, array_data.shape[1]), array_data.typecode())

        # copy selected rows to new_array
        numRows = 0
        numTimes = 0
        useThisTime = False
        for i in range(len(time_array)):
            if i == 0:
                numTimes =  1
            elif time_array[i-1] != time_array[i]:
                numTimes +=  1
                if numTimes % (numSkip + 1) == 0 or time_array[i] in gapTimes:
                    useThisTime = True
                    if array_data[i, -1] != self.__missing:
                        new_array[numRows,:] = array_data[i,:]
                        numRows += 1
                else:
                    useThisTime = False
            else:
                if useThisTime:
                    if array_data[i, -1] != self.__missing:
                        new_array[numRows,:] = array_data[i,:]
                        numRows += 1

        return new_array
    
    def _removeMissing(self, isprintText):
        """ returns modified isprintText with all missing data removed """
        lines = isprintText.split('\n')
        newIsprintText = ''
        for line in lines:
            items = line.split()
            if len(items) < 3:
                continue
            try:
                for item in items:
                    float(item)
                newIsprintText += line
            except:
                continue
            
        return(newIsprintText)

Ancestors (in MRO)

Instance variables

var truncateAlt

var xListDict

Methods

def __init__(

self, isprintText, titleStr, xLabelStr, yLabelStr, fullFilename, minColormap=None, maxColormap=None, smoothAltitude=True, insertDataGap=5, useAbsoluteTime=False, startTime=None, endTime=None, sortTimeFlag=False, maxNumTimes=None, maxNumAlt=None, truncateIsprint=False, colorMap=<matplotlib.colors.LinearSegmentedColormap object at 0x104d40790>, yMinimum=None, yMaximum=None, altYTitle=None, altYLabels=None)

init writes a madPColorPlot to a file.

Inputs:

isprintText - a string giving isprint output without headers. First parameter
              must be UTH or UT1, depending on whether time scale should be relative
                      to the experiment beginning (UTH) or absolute (UT1).
                      The second must be gdalt, and third parameter to be plotted.

titleStr - plot title (string) - should describe parameter being plotted

    xLabelStr - x label string

    yLabelStr - ylabel string

fullFilename - full path of file containing pcolor plot to be saved.  Extension must
               be jpeg or png, or exception thrown.

minColormap - minimum parameter value (defaults to lowest parameter value)

maxColormap - maximum parameter value (defaults to highest parameter value).  However, if
              both minColormap and maxColormap == None, autoscaling applied.

    smoothAltitude - if True, extrapolate between existing data between altitudes to fill
                     in missing data; if False, leave missing

    insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
                being plotted are ordered, and the time gap larger than 90% of the rest is determined.
                        Any time interval more than insertDataGap times bigger is then considered missing
                        data.  Defaults to five.  If None, no gaps are ever inserted.  For data with close
                        to uniform time intervals, no gaps will be inserted.

useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
                  since beginning of experiment (UTH).  If useAbsoluteTime is true, first
                              parameter in isprintText must be UT1, if false, it must be UTH.

startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
            in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
            startTime must be in units of UTH (hours since midnight UT of first day of
            experiment). Default is None, which means start at lowest time found.

endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
          in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
          endTime must be in units of UTH (hours since midnight UT of first day of
          experiment). Default is None, which means end at largest time found.

sortTimeFlag - if true, check that time is correctly sorted.  If false (the default),
               assume time already sorted

maxNumTimes - if not None, decimate the number of times in the isprint string to
              maxNumTimes.  If None (the default), plot all times.

maxNumAlt - if not None, reduce the number of altitudes to maxNumAlt.  If None (the default),
            plot all altitudes.

truncateIsprint - if True, and both maxNumTimes and maxNumAlt not = None, then truncate
                   the number of isprint lines to be maxNumTimes * maxNumAlt

colorMap - sets colormap.  It not given, defaults to matplotlib.cm.jet

yMinimum - minumum y value.  If None (default), set by data y minimum.

yMaximum - maximum y value.  If None (default), set by data y maximum.

altYTitle - title of right (second) y axis.  If None (the default),
             no Y axis on the right side.

altYLabels - a list of Y labels (strings) for the right axis.  If None (the default),
             no Y labels on the right axis.

Returns: void

Affects: None

def __init__(self, isprintText,
             titleStr,
             xLabelStr,
             yLabelStr,
             fullFilename,
             minColormap = None,
             maxColormap = None,
             smoothAltitude = True, 
             insertDataGap = 5,
             useAbsoluteTime = False,
             startTime = None,
             endTime = None,
             sortTimeFlag = False,
             maxNumTimes = None,
             maxNumAlt = None,
             truncateIsprint = False,
             colorMap = matplotlib.cm.jet,
             yMinimum = None,
             yMaximum = None,
             altYTitle = None,
             altYLabels = None):
    """__init__ writes a madPColorPlot to a file.
    Inputs:
        isprintText - a string giving isprint output without headers. First parameter
                      must be UTH or UT1, depending on whether time scale should be relative
        			  to the experiment beginning (UTH) or absolute (UT1).
        			  The second must be gdalt, and third parameter to be plotted.
        
        titleStr - plot title (string) - should describe parameter being plotted
 
     xLabelStr - x label string
 
     yLabelStr - ylabel string
        fullFilename - full path of file containing pcolor plot to be saved.  Extension must
                       be jpeg or png, or exception thrown.
        
        minColormap - minimum parameter value (defaults to lowest parameter value)
        maxColormap - maximum parameter value (defaults to highest parameter value).  However, if
                      both minColormap and maxColormap == None, autoscaling applied.
 
     smoothAltitude - if True, extrapolate between existing data between altitudes to fill
                      in missing data; if False, leave missing
    
     insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
                 being plotted are ordered, and the time gap larger than 90% of the rest is determined.
           Any time interval more than insertDataGap times bigger is then considered missing
           data.  Defaults to five.  If None, no gaps are ever inserted.  For data with close
           to uniform time intervals, no gaps will be inserted.
   
	    useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
	                      since beginning of experiment (UTH).  If useAbsoluteTime is true, first
                 parameter in isprintText must be UT1, if false, it must be UTH.
    
        startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
             in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
             startTime must be in units of UTH (hours since midnight UT of first day of
             experiment). Default is None, which means start at lowest time found.
	    endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
	              in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
	              endTime must be in units of UTH (hours since midnight UT of first day of
	              experiment). Default is None, which means end at largest time found.

	    sortTimeFlag - if true, check that time is correctly sorted.  If false (the default),
	                   assume time already sorted

	    maxNumTimes - if not None, decimate the number of times in the isprint string to
	                  maxNumTimes.  If None (the default), plot all times.

	    maxNumAlt - if not None, reduce the number of altitudes to maxNumAlt.  If None (the default),
	                plot all altitudes.

	    truncateIsprint - if True, and both maxNumTimes and maxNumAlt not = None, then truncate
	                       the number of isprint lines to be maxNumTimes * maxNumAlt

	    colorMap - sets colormap.  It not given, defaults to matplotlib.cm.jet

	    yMinimum - minumum y value.  If None (default), set by data y minimum.

	    yMaximum - maximum y value.  If None (default), set by data y maximum.

	    altYTitle - title of right (second) y axis.  If None (the default),
	                 no Y axis on the right side.

	    altYLabels - a list of Y labels (strings) for the right axis.  If None (the default),
	                 no Y labels on the right axis.
 
           
    Returns: void
    Affects: None
    """
    self.__missing = 1.0E30 # special value, used to create a masked array
    self.__parameter_count = 3
    
    isprintText = self._removeMissing(isprintText)
    if truncateIsprint and maxNumTimes != None and maxNumAlt != None:
        isprintText = self.__truncateIsprint(isprintText, maxNumTimes * maxNumAlt)
    # since matplotlib pcolor wants a regular grid, we create a grid with all altitudes
    # convert the input data into numeric arrays of float assuming no headers and filter the missing values.
    try:
        split_data = isprintText.split()
        float_data = map(self.__filter_missing, split_data)
        array_data = numpy.asarray(float_data)
        array_data = numpy.reshape(array_data,(-1,self.__parameter_count))
    except:
        traceback.print_exc()
        raise ValueError, 'input text is not parseable'
    if sortTimeFlag:
        array_data = self.sortArrayInTime(array_data)
    if maxNumTimes != None and maxNumTimes != 0 and not truncateIsprint:
        array_data = self.decimateTimes(array_data, int(maxNumTimes), insertDataGap)
    
    # The first pass through is to obtain the number and range of the  x and y variables
    
    xList = []
    yList = []
    zList = []
    zMin = None
    zMax = None
    # loop over all the lines of data in the array
    for j in range(len(array_data)):
        try:
            
            x = array_data[j][0]
            y = array_data[j][1]
            z = array_data[j][2]
            if x not in xList:
                xList.append(x)
            if y not in yList:
                yList.append(y)
            if z != self.__missing:
                zList.append(z)
            if zMin != None:
                if z < zMin and z != self.__missing:
                    zMin = z
            elif z != self.__missing:
                zMin = z
            if zMax != None:
                if z > zMax and z != self.__missing:
                    zMax = z
            elif z != self.__missing:
                zMax = z
        except:
            continue
    if zMin == None:
        raise ValueError, 'No valid z data found'
    # if both minColormap and maxColormap == None, use autoscaling
    if minColormap == None and maxColormap == None:
        zList.sort()
        d10 = zList[int(len(zList)*0.10)]
        d90 = zList[int(len(zList)*0.90)]
        zMin = d10 - (d90-d10) * 0.75
        zMax = d90 + (d90-d10) * 0.75
    # now sort the X and Y axis lists and pull their length
    
    xList.sort()
    if startTime == None:
        xMin = xList[0]
    else:
        xMin = startTime
    if endTime == None:
        xMax = xList[-1]
    else:
        xMax = endTime
    yList.sort()
    max_x_dimension = len(xList)
    max_y_dimension = len(yList)
    if yMinimum == None:
        yMinimum = yList[0]
    if yMaximum == None:
        yMaximum = yList[-1]
    self.truncateAlt = False
    if maxNumAlt != None:
        if max_y_dimension > maxNumAlt:
            self.truncateAlt = True
    # build dictonary of indexes into xList
    self.xListDict = {}
    for i in range(len(xList)):
        self.xListDict[xList[i]] = i
    # if self.truncateAlt == False, build dictonary of indexes into yList,
    # else truncate y values by builing a list of maxNumAlt ranges
    if self.truncateAlt == False:
        self.yListDict = {}
        for i in range(len(yList)):
            self.yListDict[yList[i]] = i
    else:
        self.yListRanges = []
        for i in range(maxNumAlt):
            self.yListRanges.append(yList[int(i*(len(yList)/maxNumAlt))])
        max_y_dimension = maxNumAlt
    # now build arrays to handle the X axis label, Y axis label, and the Z data
    X = numpy.zeros((max_x_dimension, max_y_dimension), numpy.float32)
    Y = numpy.zeros((max_x_dimension, max_y_dimension), numpy.float32)
    Z = numpy.ones((max_x_dimension, max_y_dimension), numpy.float32)
    # all parameter values default to missing
    Z = Z * self.__missing
    # fill the X and Y arrays
    for i in range(max_x_dimension):
        for j in range(max_y_dimension):
            X[i][j] = float(xList[i])
            if self.truncateAlt:
                Y[i][j] = float(yList[int(j*(len(yList)/maxNumAlt))])
            else:
                Y[i][j] = float(yList[j])

    # Now load up the data array Z with the array_data measurements as a function of x and y
    previousIndex = None
    previousValue = None
    presentTime = None
    newTimeFound = True
    for k in range(len(array_data)):
        try:
            xdata = array_data[k][0]
            ydata = array_data[k][1]
            zdata = array_data[k][2]
            if zdata == self.__missing:
                continue
            if xdata != presentTime:
                newTimeFound = True
            else:
                newTimeFound = False
            presentTime = xdata
            # now find the right place in the array for this data point
            i = self.xListDict[xdata]
            j = self.__getYIndex(ydata)
            Z[i][j] = zdata
now see if we need to fill in any gaps
            if (not newTimeFound) and smoothAltitude:
                if previousIndex < j - 1:
                    # fill in all missing points
                    for k in range(previousIndex + 1, j):
                        # simply average between the points based on index
                        thisValue = previousValue + (zdata - previousValue)*(float(k-previousIndex)/float(j-previousIndex))
                        Z[i][k] = thisValue
  
            previousIndex = j
            previousValue = zdata
            
        except:
            continue
    # insert missing data to represent gaps if needed
    if insertDataGap != None:
        # first find the time interval greater than 90% of others
        timeIntervalList = []
        for i in range(len(xList) - 1):
            timeIntervalList.append(xList[i+1] - xList[i])
        timeIntervalList.sort()
        index = int(len(timeIntervalList)*0.9)
        maxInterval = timeIntervalList[index]
 
        for i in range(len(xList) - 1):
            if xList[i+1] - xList[i] > maxInterval * insertDataGap:
                Z[i,:] = self.__missing
    # set up plotting parameters
    if minColormap == None:
        minColormap = zMin
    if maxColormap == None:
        maxColormap = zMax
    matplotlib.pylab.pcolor(X,Y,Z, shading='flat', vmin=minColormap, vmax=maxColormap, cmap = colorMap, norm = matplotlib.pylab.normalize())
    matplotlib.pylab.colorbar()
    matplotlib.pylab.xlabel(xLabelStr)
    matplotlib.pylab.ylabel(yLabelStr)
    matplotlib.pylab.xlim(xMin, xMax)
    matplotlib.pylab.ylim(yMinimum, yMaximum)
    matplotlib.pylab.yticks()
    matplotlib.pylab.title(titleStr)
    if useAbsoluteTime:
        locs, labels = matplotlib.pylab.xticks()
        if len(locs) > 5:
            # truncate by len(locs) / 5
            scale = 1 + int(len(locs) / 5)
            new_locs = []
            for i in range(len(locs)):
                if i % scale == 0:
                    new_locs.append(locs[i])
            locs = new_locs
        newXTicks = convertToAbsoluteTimeStr(locs)
        matplotlib.pylab.xticks(locs, newXTicks, rotation=15)
    matplotlib.pylab.xticks()
    # add second y-axis if desired
    if altYTitle != None and altYLabels != None:
        ax2 = matplotlib.pylab.twinx()
        matplotlib.pylab.ylabel(altYTitle)
        ax2.yaxis.tick_right()
        matplotlib.pylab.yticks(range(len(altYLabels)), altYLabels)
        matplotlib.pylab.yticks()
    # get the handle to the figure now that it has been created so we can manipulate on subsequent calls.
    
    #self.__figure = matplotlib.pylab.gcf()
      
    matplotlib.pylab.savefig(fullFilename)
    #matplotlib.pylab.show()
    matplotlib.pylab.clf()

def decimateTimes(

self, array_data, maxNumTimes, insertDataGap)

decimateTimes decimates array_data to have at most maxNumTimes times.

Input: array_data - two-dimensional array to be decimated by deleting times and missing data.

   maxNumTimes: int representing the maximum number of times to keep in array_data

   insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
                being plotted are ordered, and the time gap larger than 90% of the rest is determined.
                Note that this parameter is used here to stop the truncation of isprint lines that
                will eventually be considered edge lines.

Returns: new array built from decimated array_data

def decimateTimes(self, array_data, maxNumTimes, insertDataGap):
    """decimateTimes decimates array_data to have at most maxNumTimes times.
    Input: array_data - two-dimensional array to be decimated by deleting times and missing data.
           maxNumTimes: int representing the maximum number of times to keep in array_data
           insertDataGap - this parameter sets the threshold for inserting a data gap.  The time intervals
                 being plotted are ordered, and the time gap larger than 90% of the rest is determined.
   Note that this parameter is used here to stop the truncation of isprint lines that
   will eventually be considered edge lines.
    Returns: new array built from decimated array_data
    """
    # get the number of times in array_data, and make a list of all unique times
    numTimes = 0
    uniqueTimes = []
    time_array = array_data[:, 0]
    for i in range(len(time_array)):
        if i == 0:
           numTimes =  1
           uniqueTimes.append(time_array[i])
        elif time_array[i-1] != time_array[i]:
            uniqueTimes.append(time_array[i])
            numTimes +=  1
    if numTimes <= maxNumTimes:
        return array_data
    
    # insert missing data to represent gaps if needed
    gapTimes = []
    if insertDataGap != None:
        # first find the time interval greater than 90% of others
        timeIntervalList = []
        for i in range(len(time_array) - 1):
            if time_array[i+1] == time_array[i]:
                continue
            timeIntervalList.append(time_array[i+1] - time_array[i])
        timeIntervalList.sort()
        index = int(len(timeIntervalList)*0.9)
        maxInterval = timeIntervalList[index]
 
        for i in range(len(time_array) - 1):
            if time_array[i+1] - time_array[i] > maxInterval * insertDataGap:
                gapTimes.append(time_array[i+1])
                gapTimes.append(time_array[i])
    # get the number of times to skip each time
    numSkip = numTimes/maxNumTimes
    # get the number of rows in the new_array
    numRows = 0
    numTimes = 0
    useThisTime = False
    for i in range(len(time_array)):
        if i == 0:
            numTimes =  1
        elif time_array[i-1] != time_array[i]:
            numTimes +=  1
            if numTimes % (numSkip + 1) == 0 or time_array[i] in gapTimes:
                useThisTime = True
                if array_data[i, -1] != self.__missing:
                    numRows += 1
            else:
                useThisTime = False
        else:
            if useThisTime:
                if array_data[i, -1] != self.__missing:
                    numRows += 1
    # create new_array
    new_array = numpy.zeros((numRows, array_data.shape[1]), array_data.typecode())
    # copy selected rows to new_array
    numRows = 0
    numTimes = 0
    useThisTime = False
    for i in range(len(time_array)):
        if i == 0:
            numTimes =  1
        elif time_array[i-1] != time_array[i]:
            numTimes +=  1
            if numTimes % (numSkip + 1) == 0 or time_array[i] in gapTimes:
                useThisTime = True
                if array_data[i, -1] != self.__missing:
                    new_array[numRows,:] = array_data[i,:]
                    numRows += 1
            else:
                useThisTime = False
        else:
            if useThisTime:
                if array_data[i, -1] != self.__missing:
                    new_array[numRows,:] = array_data[i,:]
                    numRows += 1
    return new_array

def displayToScreen(

self)

to implement this takes a reworking away from pylab to use the underlying matplotlib code

def displayToScreen(self):
    " to implement this takes a reworking away from pylab to use the underlying matplotlib code "
    pass 

def getAverage(

self, X)

returns the average of items in a float array. Does not including missing data. If all data missing, returns self.__missing

def getAverage(self, X):
    """returns the average of items in a float array.  Does not including missing data.
    If all data missing, returns self.__missing
    """
    count = 0
    total = 0.0
    for i in range(X.shape[0]):
        if X[i] != self.__missing:
            count += 1
            total += X[i]
    if count == 0:
        return self.__missing
    else:
        return total / float(count)

def getFigureHandle(

self)

def getFigureHandle(self):
    return self.__figure

def sortArrayInTime(

self, array_data)

sortArrayInTime sorts a two-dimensional array so that the first element in each row (time) is in ascending order.

Input: array_data - two-dimensional array to be sorted by rearranging rows so that the first element in each row (time) is in ascending order

Returns: new_array

def sortArrayInTime(self, array_data):
    """sortArrayInTime sorts a two-dimensional array so that the first element in each row (time) is in ascending order.
    Input: array_data - two-dimensional array to be sorted by rearranging rows so
           that the first element in each row (time) is in ascending order
    Returns: new_array
    """
    sortIndex = numpy.argsort(array_data[:,0])
    # if already sorted, just return original array
    if sortIndex == numpy.sort(sortIndex):
        return array_data
    
    new_array = numpy.zeros(array_data.shape, array_data.typecode())
    for i in range(len(sortIndex)):
        new_array[sortIndex[i],:] = array_data[i,:]
    return new_array

class madScatterPlot

madScatterPlot is the class the produces two dimensional scatter plots of x versus y.

class madScatterPlot:
    """madScatterPlot is the class the produces two dimensional scatter plots of x versus y.


    """
    def __init__(self, isprintText,
                 titleStr,
                 xLabelStr,
                 yLabelStr,
                 fullFilename,
                 useAbsoluteTime = False,
                 startTime = None,
                 endTime = None,
                 maxNumPoints = None,
                 yMin=None,
                 yMax=None):
        """__init__ writes a madScatter plot to a file.

        Inputs:

            isprintText - a string giving isprint output without headers. First parameter
                          must be UTH or UT1, depending on whether time scale should be relative
            			  to the experiment beginning (UTH) or absolute (UT1).
            			  The second must be the parameter to be plotted. Any
                          missing data should be written as "missing" or other string that
                          cannot be converted to a float. 
            
            titleStr - plot title (string) - should describe parameter being plotted
            
            xLabelStr - x label string
	    
	        yLabelStr - ylabel string

            fullFilename - full path of file containing pcolor plot to be saved.  Extension must
                           be jpeg or png, or exception thrown.
			    
	        useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
	                      since beginning of experiment (UTH).  If useAbsoluteTime is true, first
			              parameter in isprintText must be UT1, if false, it must be UTH.

    	    startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
    	                in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
    	                startTime must be in units of UTH (hours since midnight UT of first day of
    	                experiment). Default is None, which means start at lowest time found.
    
    	    endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
    	              in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
    	              endTime must be in units of UTH (hours since midnight UT of first day of
    	              experiment). Default is None, which means end at largest time found.
    
    	    maxNumPoints - maximum number of points to plot.  If not None, truncate isprintText if
    	                   needed to have at most maxNumPoints lines.
        
        
        Returns: void

        Affects: None
        """
        self.__missing = 1.0E30 # special value, since Numeric doesn't handle NaN
        self.__parameter_count = 2

        if maxNumPoints != None:
            isprintText = self.__truncateIsprint(isprintText, maxNumPoints)
               
        # convert the input data into numeric arrays of float assuming no headers and filter the missing values.

        try:
            split_data = isprintText.split()
            float_data = map(self.__filter_missing, split_data)
            array_data = numpy.asarray(float_data)
            array_data = numpy.reshape(array_data,(-1,self.__parameter_count))
        except:
            traceback.print_exc()
            raise ValueError, 'input text is not parseable'
        
        # find min, max of x (time)
        if startTime == None:
            xMin = array_data[0,0]
        else:
            xMin = startTime
        if endTime == None:
            xMax = array_data[-1,0]
        else:
            xMax = endTime
        
	    
        # make sure there's valid data
        isValidData = False
        for y in array_data[:,1]:
            if y == self.__missing:
                continue
            isValidData = True
            break
		
        if not isValidData:
            raise ValueError, 'No valid y data found'

      
        matplotlib.pylab.scatter(array_data[:,0],array_data[:,1])

        matplotlib.pylab.xlabel(xLabelStr)
        matplotlib.pylab.ylabel(yLabelStr)
        matplotlib.pylab.yticks()
        matplotlib.pylab.xlim(xMin, xMax)
        if yMin != None:
            matplotlib.pylab.ylim(ymin=yMin)
        if yMax != None:
            matplotlib.pylab.ylim(ymax=yMax)
        matplotlib.pylab.title(titleStr)

        if useAbsoluteTime:
            locs, labels = matplotlib.pylab.xticks()
            if len(locs) > 5:
                # truncate by len(locs) / 5
                scale = 1 + int(len(locs) / 5)
                new_locs = []
                for i in range(len(locs)):
                    if i % scale == 0:
                        new_locs.append(locs[i])
                locs = new_locs
            newXTicks = convertToAbsoluteTimeStr(locs)
            matplotlib.pylab.xticks(locs, newXTicks, rotation=15)
	    
        matplotlib.pylab.xticks()

        # get the handle to the figure now that it has been created so we can manipulate on subsequent calls.
        
        #self.__figure = matplotlib.pylab.gcf()
          
        matplotlib.pylab.savefig(fullFilename)
        #matplotlib.pylab.show()

        matplotlib.pylab.clf()


    def __truncateIsprint(self, isprintText, maxLines):
        """__truncateIsprint truncates isprintText to have maxLines at most.
        """
        isprintList = isprintText.split('\n')
        if len(isprintList) < maxLines:
            return isprintText
        else:
            dropNumber = int(1 + len(isprintList)/maxLines)
            newline = '\n'
            newIsprintText = newline.join(isprintList[::dropNumber])

            return newIsprintText
        

    def __filter_missing(self,x):
        try:
            return float(x)
        except:
            return self.__missing

Ancestors (in MRO)

Methods

def __init__(

self, isprintText, titleStr, xLabelStr, yLabelStr, fullFilename, useAbsoluteTime=False, startTime=None, endTime=None, maxNumPoints=None, yMin=None, yMax=None)

init writes a madScatter plot to a file.

Inputs:

isprintText - a string giving isprint output without headers. First parameter
              must be UTH or UT1, depending on whether time scale should be relative
                      to the experiment beginning (UTH) or absolute (UT1).
                      The second must be the parameter to be plotted. Any
              missing data should be written as "missing" or other string that
              cannot be converted to a float.

titleStr - plot title (string) - should describe parameter being plotted

xLabelStr - x label string

    yLabelStr - ylabel string

fullFilename - full path of file containing pcolor plot to be saved.  Extension must
               be jpeg or png, or exception thrown.

    useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
                  since beginning of experiment (UTH).  If useAbsoluteTime is true, first
                          parameter in isprintText must be UT1, if false, it must be UTH.

startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
            in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
            startTime must be in units of UTH (hours since midnight UT of first day of
            experiment). Default is None, which means start at lowest time found.

endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
          in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
          endTime must be in units of UTH (hours since midnight UT of first day of
          experiment). Default is None, which means end at largest time found.

maxNumPoints - maximum number of points to plot.  If not None, truncate isprintText if
               needed to have at most maxNumPoints lines.

Returns: void

Affects: None

def __init__(self, isprintText,
             titleStr,
             xLabelStr,
             yLabelStr,
             fullFilename,
             useAbsoluteTime = False,
             startTime = None,
             endTime = None,
             maxNumPoints = None,
             yMin=None,
             yMax=None):
    """__init__ writes a madScatter plot to a file.
    Inputs:
        isprintText - a string giving isprint output without headers. First parameter
                      must be UTH or UT1, depending on whether time scale should be relative
        			  to the experiment beginning (UTH) or absolute (UT1).
        			  The second must be the parameter to be plotted. Any
                      missing data should be written as "missing" or other string that
                      cannot be converted to a float. 
        
        titleStr - plot title (string) - should describe parameter being plotted
        
        xLabelStr - x label string
 
     yLabelStr - ylabel string
        fullFilename - full path of file containing pcolor plot to be saved.  Extension must
                       be jpeg or png, or exception thrown.
   
     useAbsoluteTime - if true, print time as YYYY-MM-DD HH:MM:SS.  If false (default), print time as hour
                   since beginning of experiment (UTH).  If useAbsoluteTime is true, first
             parameter in isprintText must be UT1, if false, it must be UTH.
	    startTime - start plot at given time.  If useAbsoluteTime == True, then startTime must be
	                in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
	                startTime must be in units of UTH (hours since midnight UT of first day of
	                experiment). Default is None, which means start at lowest time found.

	    endTime - end plot at given time.  If useAbsoluteTime == True, then endTime must be
	              in units of seconds since 1/1/1950.  If useAbsoluteTime == False, then
	              endTime must be in units of UTH (hours since midnight UT of first day of
	              experiment). Default is None, which means end at largest time found.

	    maxNumPoints - maximum number of points to plot.  If not None, truncate isprintText if
	                   needed to have at most maxNumPoints lines.
    
    
    Returns: void
    Affects: None
    """
    self.__missing = 1.0E30 # special value, since Numeric doesn't handle NaN
    self.__parameter_count = 2
    if maxNumPoints != None:
        isprintText = self.__truncateIsprint(isprintText, maxNumPoints)
           
    # convert the input data into numeric arrays of float assuming no headers and filter the missing values.
    try:
        split_data = isprintText.split()
        float_data = map(self.__filter_missing, split_data)
        array_data = numpy.asarray(float_data)
        array_data = numpy.reshape(array_data,(-1,self.__parameter_count))
    except:
        traceback.print_exc()
        raise ValueError, 'input text is not parseable'
    
    # find min, max of x (time)
    if startTime == None:
        xMin = array_data[0,0]
    else:
        xMin = startTime
    if endTime == None:
        xMax = array_data[-1,0]
    else:
        xMax = endTime
    
 
    # make sure there's valid data
    isValidData = False
    for y in array_data[:,1]:
        if y == self.__missing:
            continue
        isValidData = True
        break
    if not isValidData:
        raise ValueError, 'No valid y data found'
  
    matplotlib.pylab.scatter(array_data[:,0],array_data[:,1])
    matplotlib.pylab.xlabel(xLabelStr)
    matplotlib.pylab.ylabel(yLabelStr)
    matplotlib.pylab.yticks()
    matplotlib.pylab.xlim(xMin, xMax)
    if yMin != None:
        matplotlib.pylab.ylim(ymin=yMin)
    if yMax != None:
        matplotlib.pylab.ylim(ymax=yMax)
    matplotlib.pylab.title(titleStr)
    if useAbsoluteTime:
        locs, labels = matplotlib.pylab.xticks()
        if len(locs) > 5:
            # truncate by len(locs) / 5
            scale = 1 + int(len(locs) / 5)
            new_locs = []
            for i in range(len(locs)):
                if i % scale == 0:
                    new_locs.append(locs[i])
            locs = new_locs
        newXTicks = convertToAbsoluteTimeStr(locs)
        matplotlib.pylab.xticks(locs, newXTicks, rotation=15)
 
    matplotlib.pylab.xticks()
    # get the handle to the figure now that it has been created so we can manipulate on subsequent calls.
    
    #self.__figure = matplotlib.pylab.gcf()
      
    matplotlib.pylab.savefig(fullFilename)
    #matplotlib.pylab.show()
    matplotlib.pylab.clf()