wafo-project/pywafo

View on GitHub
src/wafo/graphutil.py

Summary

Maintainability
F
3 days
Test Coverage
'''
Created on 20. jan. 2011

@author: pab

license BSD
'''
import warnings
import numpy as np
from .plotbackend import plotbackend
from matplotlib import mlab
__all__ = ['cltext', 'epcolor', 'tallibing', 'test_docstrings']

_TALLIBING_GID = 'TALLIBING'
_CLTEXT_GID = 'CLTEXT'


def _matchfun(x, gidtxt):
    if hasattr(x, 'get_gid'):
        return x.get_gid() == gidtxt
    return False


def delete_text_object(gidtxt, figure=None, axis=None, verbose=False):
    '''
    Delete all text objects matching the gidtxt if it exists

    Parameters
    ----------
    gidtxt : string

    figure, axis : objects
        current figure and current axis, respectively.
    verbose : bool
        If true print warnings when trying to delete non-existent objects
    '''
    if figure is None:
        figure = plotbackend.gcf()
    if axis is None:
        axis = figure.gca()

    def lmatchfun(x):
        return _matchfun(x, gidtxt)

    def _delete_gid_objects(handle, gidtxt, verbose):
        objs = handle.findobj(lmatchfun)
        try:
            name = handle.__class__.__name__
        except AttributeError:
            name = 'unknown object'
        msg = "Tried to delete a non-existing {0} from {1}".format(gidtxt,
                                                                   name)
        for obj in objs:
            try:
                handle.texts.remove(obj)
            except:
                if verbose:
                    warnings.warn(msg)

    for handle in [axis, figure]:
        _delete_gid_objects(handle, gidtxt, verbose)


def cltext(levels, percent=False, n=4, xs=0.036, ys=0.94, zs=0, figure=None,
           axis=None):
    '''
    Places contour level text in the current window

    Parameters
    ----------
    levels :  vector
        contour levels or the corresponding percent which the
        contour line encloses
    percent : bool
        False if levels are the actual contour levels (default)
        True  if levels are the corresponding percent which the
            contour line encloses
    n : integer
        maximum N digits of precision (default 4)
    figure, axis : objects
        current figure and current axis, respectively.
        default figure = plotbackend.gcf(),
                axis = plotbackend.gca()

    Returns
    -------
    h       = handles to the text objects.


    Notes
    -----
    CLTEXT creates text objects in the current figure and prints
          "Level curves at:"        if percent is False and
          "Level curves enclosing:" otherwise
    and the contour levels or percent.

    The handles to the lines of text may also be found by
          h  = findobj(gcf,'gid','CLTEXT','type','text');
          h  = findobj(gca,'gid','CLTEXT','type','text');
    To make the text objects follow the data in the axes set the units
    for the text objects 'data' by
          set(h,'unit','data')

    Examples
    --------
    >>> import wafo.graphutil as wg
    >>> import wafo.demos as wd
    >>> import pylab as plt
    >>> x,y,z  = wd.peaks();
    >>> h = plt.contour(x,y,z)
    >>> h = wg.cltext(h.levels)

    >>> plt.close('all')

    plt.show()
    '''
    # TODO : Make it work like legend does (but without the box): include
    # position options etc...
    if figure is None:
        figure = plotbackend.gcf()
    if axis is None:
        axis = figure.gca()

    clevels = np.atleast_1d(levels)

    axpos = axis.get_position()
    xint = axpos.intervalx
    yint = axpos.intervaly

    xss = xint[0] + xs * (xint[1] - xint[0])
    yss = yint[0] + ys * (yint[1] - yint[0])

    # delete cltext object if it exists
    try:
        delete_text_object(_CLTEXT_GID, axis=axis)
    except Exception:
        pass

    charHeight = 1.0 / 33.0
    delta_y = charHeight

    if percent:
        titletxt = 'Level curves enclosing:'
    else:
        titletxt = 'Level curves at:'
    format_ = '%0.' + ('%d' % n) + 'g\n'

    cltxt = ''.join([format_ % level for level in clevels.tolist()])

    titleProp = dict(gid=_CLTEXT_GID, horizontalalignment='left',
                     verticalalignment='center', fontweight='bold', axes=axis)

    ha1 = figure.text(xss, yss, titletxt, **titleProp)

    yss -= delta_y
    txtProp = dict(gid=_CLTEXT_GID, horizontalalignment='left',
                   verticalalignment='top', axes=axis)

    ha2 = figure.text(xss, yss, cltxt, **txtProp)
    plotbackend.draw_if_interactive()
    return ha1, ha2


def tallibing(*args, **kwds):
    '''
    TALLIBING  Display numbers on field-plot

    CALL h=tallibing(x,y,n,size,color)

    Parameters
    ----------
    x, y : array
        position matrices
    n : array
        corresponding matrix of the values to be written
             (non-integers are rounded)
    mid_points : bool (default True)
        data-point-positions are in the middle of bins instead of the corners
    size : int, (default=8)
        font size (optional)
    color : str, (default='white')
        color of text (optional)

    Returns
    -------
    h : list
        handles to TEXT objects

    TALLIBING writes the numbers in a 2D array as text at the positions
    given by the x and y coordinate matrices.
    When plotting binned results, the number of datapoints in each
    bin can be written on the bins in the plot.

    Examples
    --------
    >>> import wafo.graphutil as wg
    >>> import wafo.demos as wd
    >>> [x,y,z] = wd.peaks(n=20)
    >>> h = wg.pcolor(x,y,z)
    >>> h = wg.tallibing(x,y,z)

    pcolor(x,y,z); shading interp;

    See also
    --------
    text
    '''

    axis = kwds.pop('axis', None)
    if axis is None:
        axis = plotbackend.gca()

    x, y, n = _parse_data(*args, **kwds)
    if mlab.isvector(x) or mlab.isvector(y):
        x, y = np.meshgrid(x, y)

    n = np.round(n)

    # delete tallibing object if it exists
    delete_text_object(_TALLIBING_GID, axis=axis)

    txtProp = dict(gid=_TALLIBING_GID, size=8, color='w',
                   horizontalalignment='center',
                   verticalalignment='center', fontweight='demi', axes=axis)

    txtProp.update(**kwds)
    h = []
    for xi, yi, ni in zip(x.ravel(), y.ravel(), n.ravel()):
        if ni:
            h.append(axis.text(xi, yi, str(ni), **txtProp))
    plotbackend.draw_if_interactive()
    return h


def _parse_data(*args, **kwds):
    nargin = len(args)
    data = np.atleast_2d(args[-1]).copy()
    M, N = data.shape
    if nargin == 1:
        x = np.arange(N)
        y = np.arange(M)
    elif nargin == 3:
        x, y = np.atleast_1d(*args[:-1])
        if min(x.shape) != 1:
            x = x[0]
        if min(y.shape) != 1:
            y = y[:, 0]
    else:
        raise ValueError(
            'Requires 3 or 1 in arguments! (x,y,data) or (data)')
    if kwds.pop('mid_point', True):
        xx = _find_mid_points(x)
        yy = _find_mid_points(y)
        return xx, yy, data
    return x, y, data

pcolor = getattr(plotbackend, 'pcolor', None)
pcolormesh = getattr(plotbackend, 'pcolormesh', None)


def _find_mid_points(x):
    ''' Return points half way between all values of X and outside the
     endpoints. The outer limits have same distance from X's endpoints as
     the limits just inside.
    '''
    dx = np.diff(x) * 0.5
    dx = np.hstack((dx, dx[-1]))
    return x + dx


def test_docstrings():
    import doctest
    print('Testing docstrings in %s' % __file__)
    doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)

if __name__ == '__main__':
    test_docstrings()