climate-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From good...@apache.org
Subject svn commit: r1506643 - in /incubator/climate/branches/RefactorInput: ocw/plotter.py ocw/tests/test_plotter.py rcmet/src/main/python/rcmes/utils/taylor.py
Date Wed, 24 Jul 2013 18:08:52 GMT
Author: goodman
Date: Wed Jul 24 18:08:51 2013
New Revision: 1506643

URL: http://svn.apache.org/r1506643
Log:
CLIMATE-215 - Added new plotting functions and skeleton for Plotter class

Added:
    incubator/climate/branches/RefactorInput/ocw/plotter.py
    incubator/climate/branches/RefactorInput/ocw/tests/test_plotter.py
    incubator/climate/branches/RefactorInput/rcmet/src/main/python/rcmes/utils/taylor.py

Added: incubator/climate/branches/RefactorInput/ocw/plotter.py
URL: http://svn.apache.org/viewvc/incubator/climate/branches/RefactorInput/ocw/plotter.py?rev=1506643&view=auto
==============================================================================
--- incubator/climate/branches/RefactorInput/ocw/plotter.py (added)
+++ incubator/climate/branches/RefactorInput/ocw/plotter.py Wed Jul 24 18:08:51 2013
@@ -0,0 +1,721 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+'''
+Classes: 
+    Plotter - Visualizes pre-calculated metrics
+'''
+
+import os
+from tempfile import TemporaryFile
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from mpl_toolkits.basemap import Basemap
+from mpl_toolkits.axes_grid1 import ImageGrid
+import scipy.stats.mstats as mstats
+import numpy as np
+import numpy.ma as ma
+from utils.taylor import TaylorDiagram
+#from toolkit import plots
+
+class Plotter:
+    '''
+    Visualizes pre-calculated metrics
+    
+    This class is used for quickly plotting metrics as processed by an 
+    :class:`evaluation.Evaluation` object. Different plots are
+    generated depending on the type of metric. For example, spatial 
+    correlation (2D) would be contoured over a map projection region.
+    On the other hand, a time series of annual averages would be
+    drawn as a line plot. Each plot is saved in a supported format
+    (eg png, eps, pdf) to a user-specified directory.
+    
+    If the given evaluation contains multiple target datasets (eg multiple
+    models), subplots will be drawn for each dataset in a single file.
+    '''
+    
+    def __init__(self, evaluation, work_dir=None, fmt='png'):
+        '''
+        Plotter Constructor.
+        
+        Arguments:
+        :param evaluation: An evaluation that has been previously ran for a
+            series of datasets.
+        :type evaluation: Evaluation 
+        
+        Keyword Arguments:
+        :param work_dir: The working directory to save generated figures.
+        	If set to None, it will be set to your current working directory.
+        :type work_dir: str or None
+        :param fmt: The file type to save figures. Supported formats include
+            png, eps, and pdf.
+        :type fmt: str 
+        '''
+        if work_dir is None:
+            work_dir = os.getcwd()
+            
+        self.work_dir = work_dir
+        self.assign_plot_data(evaluation)
+    
+    def assign_plot_data(self, evaluation):
+        '''
+        Builds a list of plotting functions and corresponding arguments based
+        on the shape of the each result in the evaluation.
+        
+        Plotting function shapes are based on the length of the following 
+        evaluation attributes (X denotes an arbitrary value):
+        Contour Map: datasets, lats, lons
+        Portrait Diagram: X, subregions, datasets
+        Time Series: datasets, X, times
+        Taylor Diagram: datasets, 2
+        
+        Arguments:
+        :param evaluation: A previously ran Evaluation instance
+        :type evaluation: Evaluation
+        '''
+        # Check if the evaluation was ran previously. Could check the results
+        # attribute but it may be better to have a flag defined somewhere that
+        # checks if run() has been called successfully in the Evaluation class.
+        # if not evaluation.processed:
+        if evaluation.results is None:
+            # Should either raise an exception (ValueError or a custom class)
+            # or run the evaluation directly
+            # self.evaluation.run() 
+            raise ValueError('evaluation must be ran before being processed')
+        
+        for result in evaluation.results:
+            # Function to be selected from if-else block based on result shape
+            
+            # Function arguments are also constructed from the attributes of
+            # evaluation
+            
+            # self.plot_data.append(func, args, kwargs)
+            pass
+        
+    def make_plots(self):
+        '''
+        Calls the actual plotting functions, either defined in a separate module
+        (eg rcmes/toolkit/plots.py or placed directly in this one. 
+        '''
+        # for func, args, kwargs in self.plot_data:
+        #     func(*args, **kwargs)
+        pass
+    
+# Plotting function library. Originally from a separate file, but placed here
+# for now so that the original rcmet code in this branch still works
+def _nice_intervals(data, nlevs):
+    '''
+    Purpose::
+        Calculates nice intervals between each color level for colorbars
+        and contour plots. The target minimum and maximum color levels are
+        calculated by taking the minimum and maximum of the distribution
+        after cutting off the tails to remove outliers. 
+    
+    Input::
+        data - an array of data to be plotted
+        nlevs - an int giving the target number of intervals
+        
+    Output::
+        clevs - A list of floats for the resultant colorbar levels
+    '''
+    # Find the min and max levels by cutting off the tails of the distribution
+    # This mitigates the influence of outliers
+    data = data.ravel()
+    mnlvl = mstats.scoreatpercentile(data, 5)
+    mxlvl = mstats.scoreatpercentile(data, 95)
+    locator = mpl.ticker.MaxNLocator(nlevs)
+    clevs = locator.tick_values(mnlvl, mxlvl)
+    
+    # Make sure the bounds of clevs are reasonable since sometimes
+    # MaxNLocator gives values outside the domain of the input data
+    clevs = clevs[(clevs >= mnlvl) & (clevs <= mxlvl)]
+    return clevs
+
+def _best_grid_shape(nplots, oldshape):
+    '''
+    Purpose::
+        Calculate a better grid shape in case the user enters more columns
+        and rows than needed to fit a given number of subplots.
+        
+    Input::
+        nplots - an int giving the number of plots that will be made
+        oldshape - a tuple denoting the desired grid shape (nrows, ncols) for arranging
+                    the subplots originally requested by the user. 
+    
+    Output::
+        newshape - the smallest possible subplot grid shape needed to fit nplots
+    '''
+    nrows, ncols = oldshape
+    size = nrows * ncols
+    diff = size - nplots
+    if diff < 0:
+        raise ValueError('gridshape=(%d, %d): Cannot fit enough subplots for data' %(nrows,
ncols))
+    else:
+        # If the user enters an excessively large number of
+        # rows and columns for gridshape, automatically
+        # correct it so that it fits only as many plots
+        # as needed
+        while diff >= ncols:
+            nrows -= 1
+            size = nrows * ncols
+            diff = size - nplots
+            
+        # Don't forget to remove unnecessary columns too
+        if nrows == 1:
+            ncols = nplots
+            
+        newshape = nrows, ncols
+        return newshape
+    
+def _fig_size(gridshape):
+    '''
+    Purpose::
+        Calculates the figure dimensions from a subplot gridshape
+        
+    Inputs::
+        gridshape - Tuple denoting the subplot gridshape
+        
+    Outputs::
+        width - float for width of the figure in inches
+        height - float for height of the figure in inches
+    '''
+    nrows, ncols = gridshape
+    
+    # Assuming base dimensions of 8.5" x 5.5". May change this later to be
+    # user defined.
+    if nrows >= ncols:
+        width, height = 8.5, 5.5 * nrows / ncols
+    else:
+        width, height = 8.5 * ncols / nrows, 5.5
+        
+    return width, height
+    
+def draw_taylor_diagram(data, refname, fname, fmt='png', ptitle='', 
+                      pos='upper right', frameon=False, radmax=1.5):
+    '''
+    Purpose::
+        Draws a Taylor diagram
+        
+    Input::
+        data - an Nx3 array containing normalized standard deviations,
+               correlation coefficients, and names of evaluation datasetss
+        refname - The name of the reference datasets
+        fname  - a string specifying the filename of the plot
+        fmt  - an optional string specifying the filetype, default is .png
+        ptitle - an optional string specifying the plot title
+        pos - an optional string or tuple of float for determining 
+                    the position of the legend
+        frameon - an optional boolean that determines whether to draw a frame
+                        around the legend box
+        radmax - an optional float to adjust the extent of the axes in terms of
+                 standard deviation.
+    '''
+    fig = plt.figure()
+    fig.suptitle(ptitle)            
+    dia = TaylorDiagram (1, fig=fig, rect=111, label=refname, radmax=radmax)
+    for i, (stddev, corrcoef, name) in enumerate(data):
+        dia.add_sample(stddev, corrcoef, marker='$%d$' % (i + 1), ms=6, label=name)
+    
+    legend = fig.legend(dia.samplePoints, [p.get_label() for p in dia.samplePoints], handlelength=0.,

+                        prop={'size': 10}, numpoints=1, loc=pos)
+    legend.draw_frame(frameon)
+    fig.savefig('%s.%s' %(fname, fmt), bbox_inches='tight')    
+    fig.clf()
+    
+def draw_subregions(subregions, lats, lons, fname, fmt='png', ptitle='',
+                   parallels=None, meridians=None, subregion_masks=None):
+    '''
+    Purpose::
+        Function to draw subregion domain(s) on a map
+        
+    Input::
+        subregions - a list of subRegion objects
+        lats - array of latitudes
+        lons - array of longitudes
+        fname  - a string specifying the filename of the plot
+        fmt  - an optional string specifying the filetype, default is .png
+        ptitle - an optional string specifying plot title
+        parallels - an optional list of ints or floats for the parallels to be drawn 
+        meridians - an optional list of ints or floats for the meridians to be drawn
+        subregion_masks - optional dictionary of boolean arrays for each subRegion
+                         for giving finer control of the domain to be drawn, by default
+                         the entire domain is drawn. 
+    '''
+    # Set up the figure
+    fig = plt.figure()
+    fig.set_size_inches((8.5, 11.))
+    fig.dpi = 300
+    ax = fig.add_subplot(111)
+    
+    # Determine the map boundaries and construct a Basemap object
+    lonmin = lons.min()
+    lonmax = lons.max()
+    latmin = lats.min()
+    latmax = lats.max()
+    m = Basemap(projection='cyl', llcrnrlat=latmin, urcrnrlat=latmax,
+                llcrnrlon=lonmin, urcrnrlon=lonmax, resolution='l', ax=ax)
+    
+    # Draw the borders for coastlines and countries
+    m.drawcoastlines(linewidth=1)
+    m.drawcountries(linewidth=.75)
+    m.drawstates()
+    
+    # Create default meridians and parallels. The interval between
+    # them should be 1, 5, 10, 20, 30, or 40 depending on the size
+    # of the domain
+    length = max((latmax - latmin), (lonmax - lonmin)) / 5
+    if length <= 1:
+        dlatlon = 1
+    elif length <= 5:
+        dlatlon = 5
+    else:
+        dlatlon = np.round(length, decimals=-1)
+        
+    if meridians is None:
+        meridians = np.r_[np.arange(0, -180, -dlatlon)[::-1], np.arange(0, 180, dlatlon)]
+    if parallels is None:
+        parallels = np.r_[np.arange(0, -90, -dlatlon)[::-1], np.arange(0, 90, dlatlon)]
+        
+    # Draw parallels / meridians
+    m.drawmeridians(meridians, labels=[0, 0, 0, 1], linewidth=.75, fontsize=10)
+    m.drawparallels(parallels, labels=[1, 0, 0, 1], linewidth=.75, fontsize=10)
+
+    # Set up the color scaling
+    cmap = plt.cm.rainbow
+    norm = mpl.colors.BoundaryNorm(np.arange(1, len(subregions) + 3), cmap.N)
+    
+    # Process the subregions 
+    for i, reg in enumerate(subregions):
+        if subregion_masks is not None and reg.name in subregion_masks.keys():
+            domain = (i + 1) * subregion_masks[reg.name]
+        else:        
+            domain = (i + 1) * np.ones((2, 2))
+        
+        nlats, nlons = domain.shape
+        domain = ma.masked_equal(domain, 0)
+        reglats = np.linspace(reg.latmin, reg.latmax, nlats)
+        reglons = np.linspace(reg.lonmin, reg.lonmax, nlons)            
+        reglons, reglats = np.meshgrid(reglons, reglats)
+        
+        # Convert to to projection coordinates. Not really necessary
+        # for cylindrical projections but keeping it here in case we need
+        # support for other projections.
+        x, y = m(reglons, reglats)
+        
+        # Draw the subregion domain
+        m.pcolormesh(x, y, domain, cmap=cmap, norm=norm, alpha=.5)
+        
+        # Label the subregion
+        xm, ym = x.mean(), y.mean()
+        m.plot(xm, ym, marker='$%s$' %(reg.name), markersize=12, color='k')
+    
+    # Add the title
+    ax.set_title(ptitle)
+    
+    # Save the figure
+    fig.savefig('%s.%s' %(fname, fmt), bbox_inches='tight', dpi=fig.dpi)
+    fig.clf()
+
+def draw_time_series(datasets, times, labels, fname, fmt='png', gridshape=(1, 1), 
+                   xlabel='', ylabel='', ptitle='', subtitles=None, 
+                   label_month=False, yscale='linear'):
+    '''
+    Purpose::
+        Function to draw a time series plot
+     
+    Input:: 
+        datasets - a 3d array of time series
+        times - a list of python datetime objects
+        labels - a list of strings with the names of each set of data
+        fname - a string specifying the filename of the plot
+        fmt - an optional string specifying the output filetype
+        gridshape - optional tuple denoting the desired grid shape (nrows, ncols) for arranging
+                    the subplots. 
+        xlabel - a string specifying the x-axis title
+        ylabel - a string specifying the y-axis title
+        ptitle - a string specifying the plot title
+        subtitles - an optional list of strings specifying the title for each subplot
+        label_month - optional bool to toggle drawing month labels
+        yscale - optional string for setting the y-axis scale, 'linear' for linear
+                 and 'log' for log base 10.
+    '''
+    # Handle the single plot case. 
+    if datasets.ndim == 2:
+        datasets = datasets.reshape(1, *datasets.shape)
+
+    # Make sure gridshape is compatible with input data
+    nplots = datasets.shape[0]
+    gridshape = _best_grid_shape(nplots, gridshape)
+        
+    # Set up the figure
+    width, height = _fig_size(gridshape)
+    fig = plt.figure()
+    fig.set_size_inches((width, height))
+    fig.dpi = 300
+    
+    # Make the subplot grid
+    grid = ImageGrid(fig, 111,
+                     nrows_ncols=gridshape,
+                     axes_pad=0.3,
+                     share_all=True,
+                     add_all=True,
+                     ngrids=nplots,
+                     label_mode='L',
+                     aspect=False,
+                     cbar_mode='single',
+                     cbar_location='bottom',
+                     cbar_size=.05,
+                     cbar_pad=.20
+                     )
+    
+    # Make the plots
+    for i, ax in enumerate(grid):
+        data = datasets[i]
+        if label_month:
+            xfmt = mpl.dates.DateFormatter('%b')
+            ax.xaxis.set_major_formatter(xfmt)
+    
+        # Set the y-axis scale
+        ax.set_yscale(yscale)
+    
+        # Set up list of lines for legend
+        lines = []
+        ymin, ymax = 0, 0
+                
+        # Plot each line
+        for tSeries in data:
+            line = ax.plot_date(times, tSeries, '')
+            lines.extend(line)
+            cmin, cmax = tSeries.min(), tSeries.max()
+            ymin = min(ymin, cmin)
+            ymax = max(ymax, cmax)
+            
+        # Add a bit of padding so lines don't touch bottom and top of the plot
+        ymin = ymin - ((ymax - ymin) * 0.1)
+        ymax = ymax + ((ymax - ymin) * 0.1)
+        ax.set_ylim((ymin, ymax))
+        
+        # Set the subplot title if desired
+        if subtitles is not None:
+            ax.set_title(subtitles[i], fontsize='small')
+        
+    # Create a master axes rectangle for figure wide labels
+    fax = fig.add_subplot(111, frameon=False)
+    fax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
+    fax.set_ylabel(ylabel)
+    fax.set_title(ptitle, fontsize=16)
+    fax.title.set_y(1.04)
+    
+    # Create the legend using a 'fake' colorbar axes. This lets us have a nice
+    # legend that is in sync with the subplot grid
+    cax = ax.cax
+    cax.set_frame_on(False)
+    cax.set_xticks([])
+    cax.set_yticks([])
+    cax.legend((lines), labels, loc='upper center', ncol=10, fontsize='small', 
+                   mode='expand', frameon=False)
+    
+    # Note that due to weird behavior by axes_grid, it is more convenient to
+    # place the x-axis label relative to the colorbar axes instead of the
+    # master axes rectangle.
+    cax.set_title(xlabel, fontsize=12)
+    cax.title.set_y(-1.5)
+    
+    # Rotate the x-axis tick labels
+    for ax in grid:
+        for xtick in ax.get_xticklabels():
+            xtick.set_ha('right')
+            xtick.set_rotation(30)
+    
+    # Save the figure
+    fig.savefig('%s.%s' %(fname, fmt), bbox_inches='tight', dpi=fig.dpi)
+    fig.clf()
+
+def draw_contour_map(dataset, lats, lons, fname, fmt='png', gridshape=(1, 1),
+                   clabel='', ptitle='', subtitles=None, cmap=None, 
+                   clevs=None, nlevs=10, parallels=None, meridians=None,
+                   extend='neither'):
+    '''
+    Purpose::
+        Create a multiple panel contour map plot.
+       
+    Input::
+        dataset -  3d array of the field to be plotted with shape (nT, nLon, nLat)
+        lats - array of latitudes
+        lons - array of longitudes
+        fname  - a string specifying the filename of the plot
+        fmt  - an optional string specifying the filetype, default is .png
+        gridshape - optional tuple denoting the desired grid shape (nrows, ncols) for arranging
+                    the subplots. 
+        clabel - an optional string specifying the colorbar title
+        ptitle - an optional string specifying plot title
+        subtitles - an optional list of strings specifying the title for each subplot
+        cmap - an optional matplotlib.LinearSegmentedColormap object denoting the colormap
+        clevs - an optional list of ints or floats specifying contour levels
+        nlevs - an optional integer specifying the target number of contour levels if
+                clevs is None        
+        parallels - an optional list of ints or floats for the parallels to be drawn 
+        meridians - an optional list of ints or floats for the meridians to be drawn
+        extend - an optional string to toggle whether to place arrows at the colorbar
+             boundaries. Default is 'neither', but can also be 'min', 'max', or
+             'both'. Will be automatically set to 'both' if clevs is None.     
+    '''
+    # Handle the single plot case. Meridians and Parallels are not labeled for
+    # multiple plots to save space.
+    if dataset.ndim == 2 or (dataset.ndim == 3 and dataset.shape[0] == 1):
+        if dataset.ndim == 2:
+            dataset = dataset.reshape(1, *dataset.shape)
+        mlabels = [0, 0, 0, 1]
+        plabels = [1, 0, 0, 1]
+    else:
+        mlabels = [0, 0, 0, 0]
+        plabels = [0, 0, 0, 0]
+        
+    # Make sure gridshape is compatible with input data
+    nplots = dataset.shape[0]
+    gridshape = _best_grid_shape(nplots, gridshape)
+        
+    # Set up the figure
+    fig = plt.figure()
+    fig.set_size_inches((8.5, 11.))
+    fig.dpi = 300
+    
+    # Make the subplot grid
+    grid = ImageGrid(fig, 111,
+                     nrows_ncols=gridshape,
+                     axes_pad=0.3,
+                     share_all=True,
+                     add_all=True,
+                     ngrids=nplots,
+                     label_mode='L',
+                     cbar_mode='single',
+                     cbar_location='bottom',
+                     cbar_size=.15,
+                     cbar_pad='0%'
+                     )
+        
+    # Determine the map boundaries and construct a Basemap object
+    lonmin = lons.min()
+    lonmax = lons.max()
+    latmin = lats.min()
+    latmax = lats.max()
+    m = Basemap(projection = 'cyl', llcrnrlat = latmin, urcrnrlat = latmax,
+                llcrnrlon = lonmin, urcrnrlon = lonmax, resolution = 'l')
+    
+    # Convert lats and lons to projection coordinates
+    if lats.ndim == 1 and lons.ndim == 1:
+        lons, lats = np.meshgrid(lons, lats)
+    
+    # Calculate contour levels if not given
+    if clevs is None:
+        # Cut off the tails of the distribution
+        # for more representative contour levels
+        clevs = _nice_intervals(dataset, nlevs)
+        extend = 'both'
+    
+    if cmap is None:
+        cmap = plt.cm.coolwarm
+    
+    # Create default meridians and parallels. The interval between
+    # them should be 1, 5, 10, 20, 30, or 40 depending on the size
+    # of the domain
+    length = max((latmax - latmin), (lonmax - lonmin)) / 5
+    if length <= 1:
+        dlatlon = 1
+    elif length <= 5:
+        dlatlon = 5
+    else:
+        dlatlon = np.round(length, decimals = -1)
+    if meridians is None:
+        meridians = np.r_[np.arange(0, -180, -dlatlon)[::-1], np.arange(0, 180, dlatlon)]
+    if parallels is None:
+        parallels = np.r_[np.arange(0, -90, -dlatlon)[::-1], np.arange(0, 90, dlatlon)]
+            
+    x, y = m(lons, lats)
+    for i, ax in enumerate(grid):        
+        # Load the data to be plotted
+        data = dataset[i]
+        m.ax = ax
+        
+        # Draw the borders for coastlines and countries
+        m.drawcoastlines(linewidth=1)
+        m.drawcountries(linewidth=.75)
+        
+        # Draw parallels / meridians
+        m.drawmeridians(meridians, labels=mlabels, linewidth=.75, fontsize=10)
+        m.drawparallels(parallels, labels=plabels, linewidth=.75, fontsize=10)
+        
+        # Draw filled contours
+        cs = m.contourf(x, y, data, cmap=cmap, levels=clevs, extend=extend)
+        
+        # Add title
+        if subtitles is not None:
+            ax.set_title(subtitles[i], fontsize='small')
+
+    # Add colorbar
+    cbar = fig.colorbar(cs, cax=ax.cax, drawedges=True, orientation='horizontal',
+                        extendfrac='auto')
+    cbar.set_label(clabel)
+    cbar.set_ticks(clevs)
+    cbar.ax.xaxis.set_ticks_position('none')
+    cbar.ax.yaxis.set_ticks_position('none')
+        
+    # This is an ugly hack to make the title show up at the correct height.
+    # Basically save the figure once to achieve tight layout and calculate
+    # the adjusted heights of the axes, then draw the title slightly above
+    # that height and save the figure again
+    fig.savefig(TemporaryFile(), bbox_inches='tight', dpi=fig.dpi)
+    ymax = 0
+    for ax in grid:
+        bbox = ax.get_position()
+        ymax = max(ymax, bbox.ymax)
+    
+    # Add figure title
+    fig.suptitle(ptitle, y=ymax + .06, fontsize=16)
+    fig.savefig('%s.%s' %(fname, fmt), bbox_inches='tight', dpi=fig.dpi)
+    fig.clf()
+
+def draw_portrait_diagram(datasets, rowlabels, collabels, fname, fmt='png', 
+                        gridshape=(1, 1), xlabel='', ylabel='', clabel='', 
+                        ptitle='', subtitles=None, cmap=None, clevs=None, 
+                        nlevs=10, extend='neither'):
+    '''
+    Purpose::
+        Makes a portrait diagram plot.
+        
+    Input::
+        datasets - 3d array of the field to be plotted. The second dimension 
+                  should correspond to the number of rows in the diagram and the
+                  third should correspond to the number of columns.
+        rowlabels - a list of strings denoting labels for each row
+        collabels - a list of strings denoting labels for each column
+        fname - a string specifying the filename of the plot
+        fmt - an optional string specifying the output filetype
+        gridshape - optional tuple denoting the desired grid shape (nrows, ncols) for arranging
+                    the subplots. 
+        xlabel - an optional string specifying the x-axis title
+        ylabel - an optional string specifying the y-axis title
+        clabel - an optional string specifying the colorbar title
+        ptitle - a string specifying the plot title
+        subtitles - an optional list of strings specifying the title for each subplot
+        cmap - an optional matplotlib.LinearSegmentedColormap object denoting the colormap
+        clevs - an optional list of ints or floats specifying colorbar levels
+        nlevs - an optional integer specifying the target number of contour levels if
+                clevs is None        
+        extend - an optional string to toggle whether to place arrows at the colorbar
+             boundaries. Default is 'neither', but can also be 'min', 'max', or
+             'both'. Will be automatically set to 'both' if clevs is None.     
+        
+    '''  
+    # Handle the single plot case.
+    if datasets.ndim == 2:
+        datasets = datasets.reshape(1, *datasets.shape)
+    
+    nplots = datasets.shape[0]
+    
+    # Make sure gridshape is compatible with input data
+    gridshape = _best_grid_shape(nplots, gridshape)
+    
+    # Row and Column labels must be consistent with the shape of
+    # the input data too
+    prows, pcols = datasets.shape[1:]
+    if len(rowlabels) != prows or len(collabels) != pcols:
+        raise ValueError('rowlabels and collabels must have %d and %d elements respectively'
%(prows, pcols))
+     
+    # Set up the figure
+    width, height = _fig_size(gridshape)
+    fig = plt.figure()
+    fig.set_size_inches((width, height))
+    fig.dpi = 300
+    
+    # Make the subplot grid
+    grid = ImageGrid(fig, 111,
+                     nrows_ncols=gridshape,
+                     axes_pad=0.4,
+                     share_all=True,
+                     aspect=False,
+                     add_all=True,
+                     ngrids=nplots,
+                     label_mode='all',
+                     cbar_mode='single',
+                     cbar_location='bottom',
+                     cbar_size=.15,
+                     cbar_pad='3%'
+                     )
+    
+    # Calculate colorbar levels if not given
+    if clevs is None:
+        # Cut off the tails of the distribution
+        # for more representative colorbar levels
+        clevs = _nice_intervals(datasets, nlevs)
+        extend = 'both'
+        
+    if cmap is None:
+        cmap = plt.cm.coolwarm
+        
+    norm = mpl.colors.BoundaryNorm(clevs, cmap.N)
+    
+    # Do the plotting
+    for i, ax in enumerate(grid):
+        data = datasets[i]
+        cs = ax.matshow(data, cmap=cmap, aspect='auto', origin='lower', norm=norm)
+        
+        # Add grid lines
+        ax.xaxis.set_ticks(np.arange(data.shape[1] + 1))
+        ax.yaxis.set_ticks(np.arange(data.shape[0] + 1))
+        x = (ax.xaxis.get_majorticklocs() - .5)
+        y = (ax.yaxis.get_majorticklocs() - .5)
+        ax.vlines(x, y.min(), y.max())
+        ax.hlines(y, x.min(), x.max())
+        
+        # Configure ticks
+        ax.xaxis.tick_bottom()
+        ax.xaxis.set_ticks_position('none')
+        ax.yaxis.set_ticks_position('none')
+        ax.set_xticklabels(collabels, fontsize='xx-small')
+        ax.set_yticklabels(rowlabels, fontsize='xx-small')
+        
+        # Add axes title
+        if subtitles is not None:
+            ax.text(0.5, 1.04, subtitles[i], va='center', ha='center', 
+                    transform = ax.transAxes, fontsize='small')
+    
+    # Create a master axes rectangle for figure wide labels
+    fax = fig.add_subplot(111, frameon=False)
+    fax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
+    fax.set_ylabel(ylabel)
+    fax.set_title(ptitle, fontsize=16)
+    fax.title.set_y(1.04)
+    
+    # Add colorbar
+    cax = ax.cax
+    cbar = fig.colorbar(cs, cax=cax, norm=norm, boundaries=clevs, drawedges=True, 
+                        extend=extend, orientation='horizontal', extendfrac='auto')
+    cbar.set_label(clabel)
+    cbar.set_ticks(clevs)
+    cbar.ax.xaxis.set_ticks_position('none')
+    cbar.ax.yaxis.set_ticks_position('none')
+    
+    # Note that due to weird behavior by axes_grid, it is more convenient to
+    # place the x-axis label relative to the colorbar axes instead of the
+    # master axes rectangle.
+    cax.set_title(xlabel, fontsize=12)
+    cax.title.set_y(1.5)
+    
+    # Save the figure
+    fig.savefig('%s.%s' %(fname, fmt), bbox_inches='tight', dpi=fig.dpi)
+    fig.clf()

Added: incubator/climate/branches/RefactorInput/ocw/tests/test_plotter.py
URL: http://svn.apache.org/viewvc/incubator/climate/branches/RefactorInput/ocw/tests/test_plotter.py?rev=1506643&view=auto
==============================================================================
--- incubator/climate/branches/RefactorInput/ocw/tests/test_plotter.py (added)
+++ incubator/climate/branches/RefactorInput/ocw/tests/test_plotter.py Wed Jul 24 18:08:51
2013
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+'''Unit tests for the plotter.py module'''
+
+import unittest
+
+class TestPlotter(unittest.TestCase):
+    pass

Added: incubator/climate/branches/RefactorInput/rcmet/src/main/python/rcmes/utils/taylor.py
URL: http://svn.apache.org/viewvc/incubator/climate/branches/RefactorInput/rcmet/src/main/python/rcmes/utils/taylor.py?rev=1506643&view=auto
==============================================================================
--- incubator/climate/branches/RefactorInput/rcmet/src/main/python/rcmes/utils/taylor.py (added)
+++ incubator/climate/branches/RefactorInput/rcmet/src/main/python/rcmes/utils/taylor.py Wed
Jul 24 18:08:51 2013
@@ -0,0 +1,126 @@
+"""
+Taylor diagram (Taylor, 2001) test implementation.
+ 
+http://www-pcmdi.llnl.gov/about/staff/Taylor/CV/Taylor_diagram_primer.htm
+"""
+ 
+__version__ = "Time-stamp: <2012-02-17 20:59:35 ycopin>"
+__author__ = "Yannick Copin <yannick.copin@laposte.net>"
+ 
+import numpy as NP
+import matplotlib.pyplot as PLT
+ 
+class TaylorDiagram(object):
+    """Taylor diagram: plot model standard deviation and correlation
+    to reference (data) sample in a single-quadrant polar plot, with
+    r=stddev and theta=arccos(correlation).
+    """
+ 
+    def __init__(self, refstd, radmax=1.5, fig=None, rect=111, label='_'):
+        """Set up Taylor diagram axes, i.e. single quadrant polar
+        plot, using mpl_toolkits.axisartist.floating_axes. refstd is
+        the reference standard deviation to be compared to.
+        """
+ 
+        from matplotlib.projections import PolarAxes
+        import mpl_toolkits.axisartist.floating_axes as FA
+        import mpl_toolkits.axisartist.grid_finder as GF
+ 
+        self.refstd = refstd            # Reference standard deviation
+ 
+        tr = PolarAxes.PolarTransform()
+ 
+        # Correlation labels
+        rlocs = NP.concatenate((NP.arange(10)/10.,[0.95,0.99]))
+        tlocs = NP.arccos(rlocs)        # Conversion to polar angles
+        gl1 = GF.FixedLocator(tlocs)    # Positions
+        tf1 = GF.DictFormatter(dict(zip(tlocs, map(str,rlocs))))
+ 
+        # Standard deviation axis extent
+        self.smin = 0
+        self.smax = radMax*self.refstd
+ 
+        ghelper = FA.GridHelperCurveLinear(tr,
+                                           extremes=(0,NP.pi/2, # 1st quadrant
+                                                     self.smin,self.smax),
+                                           grid_locator1=gl1,
+                                           tick_formatter1=tf1,
+                                           )
+ 
+        if fig is None:
+            fig = PLT.figure()
+ 
+        ax = FA.FloatingSubplot(fig, rect, grid_helper=ghelper)
+        fig.add_subplot(ax)
+ 
+        # Adjust axes
+        ax.axis["top"].set_axis_direction("bottom")  # "Angle axis"
+        ax.axis["top"].toggle(ticklabels=True, label=True)
+        ax.axis["top"].major_ticklabels.set_axis_direction("top")
+        ax.axis["top"].label.set_axis_direction("top")
+        ax.axis["top"].label.set_text("Correlation")
+ 
+        ax.axis["left"].set_axis_direction("bottom") # "X axis"
+        ax.axis["left"].label.set_text("Standard deviation")
+ 
+        ax.axis["right"].set_axis_direction("top")   # "Y axis"
+        ax.axis["right"].toggle(ticklabels=True)
+        ax.axis["right"].major_ticklabels.set_axis_direction("left")
+ 
+        ax.axis["bottom"].set_visible(False)         # Useless
+        
+        # Contours along standard deviations
+        ax.grid(False)
+ 
+        self._ax = ax                   # Graphical axes
+        self.ax = ax.get_aux_axes(tr)   # Polar coordinates
+ 
+        # Add reference point and stddev contour
+        # print "Reference std:", self.refstd
+        l, = self.ax.plot([0], self.refstd, 'k*',
+                          ls='', ms=10, label=label)
+        t = NP.linspace(0, NP.pi/2)
+        r = NP.zeros_like(t) + self.refstd
+        self.ax.plot(t,r, 'k--', label='_')
+ 
+        # Collect sample points for latter use (e.g. legend)
+        self.samplePoints = [l]
+ 
+    def add_sample(self, stddev, corrcoef, *args, **kwargs):
+        """Add sample (stddev,corrcoeff) to the Taylor diagram. args
+        and kwargs are directly propagated to the Figure.plot
+        command."""
+ 
+        l, = self.ax.plot(NP.arccos(corrcoef), stddev,
+                          *args, **kwargs) # (theta,radius)
+        self.samplePoints.append(l)
+ 
+        return l
+ 
+    def add_rms_contours(self, levels=5, **kwargs):
+        """Add constant centered RMS difference contours."""
+ 
+        rs,ts = NP.meshgrid(NP.linspace(self.smin,self.smax),
+                            NP.linspace(0,NP.pi/2))
+        # Compute centered RMS difference
+        rms = NP.sqrt(self.refstd**2 + rs**2 - 2*self.refstd*rs*NP.cos(ts))
+        
+        contours = self.ax.contour(ts, rs, rms, levels, **kwargs)
+    
+    def add_stddev_contours(self, std, corr1, corr2, **kwargs):
+        """Add a curved line with a radius of std between two points
+        [std, corr1] and [std, corr2]"""
+
+        t = NP.linspace(NP.arccos(corr1), NP.arccos(corr2)) 
+        r = NP.zeros_like(t) + std
+        return self.ax.plot(t,r,'red', linewidth=2)
+    
+    def add_contours(self,std1,corr1,std2,corr2, **kwargs):
+        """Add a line between two points
+        [std1, corr1] and [std2, corr2]"""
+        
+        t = NP.linspace(NP.arccos(corr1), NP.arccos(corr2)) 
+        r = NP.linspace(std1, std2)
+
+        return self.ax.plot(t,r,'red',linewidth=2)
+ 



Mime
View raw message