Repository: enthought/chaco

Issue:
Remove chaco.datamapper and subdivision code
These modules, although they contain some algorithmic content, are holdovers from Chaco 1.  They are not used by any current code, are completely untested, and opaque regarding their use.  There may be value to them, but that value is gone as far as institutional memory is concerned.

Removing them would be technically not backward compatible, but likely not problematic.



Discussion:

Resolving patch:
diff --git a/chaco/datamapper.py b/chaco/datamapper.py
deleted file mode 100644
--- a/chaco/datamapper.py
+++ /dev/null
@@ -1,301 +0,0 @@
-"""
-CAUTION: This is an old file from Chaco 1.x to support the spatial subdivision
-structures.  It will be refactored soon.
-
-If you are looking for Chaco's mappers (subclasses of AbstractMapper),
-look in abstract_mapper.py, linear_mapper.py, and log_mapper.py.
-
-Defines AbstractDataMapper and BruteForceDataMapper classes, and related trait
-and functions.
-"""
-
-
-from numpy import array, concatenate, take, argsort, argmin, \
-                  argmax, transpose, newaxis, sort
-
-from traits.api import HasStrictTraits, Bool, Enum, Tuple, \
-                             Property, Any, Float
-
-
-#-------------------------------------------------------------------
-# Module-specific traits
-#-------------------------------------------------------------------
-
-# Expresses sorting order of
-ArraySortTrait = Enum('ascending', 'descending')
-
-
-#-------------------------------------------------------------------
-# Module-specific utility functions
-#-------------------------------------------------------------------
-
-def right_shift(ary, newval):
-    "Returns a right-shifted version of *ary* with *newval* inserted on the left."
-    return concatenate([[newval], ary[:-1]])
-
-def left_shift(ary, newval):
-    "Returns a left-shifted version of *ary* with *newval* inserted on the right."
-    return concatenate([ary[1:], [newval]])
-
-def sort_points(points, index=0):
-    """
-    sort_points(array_of_points, index=<0|1>) -> sorted_array
-
-    Takes a list of points as an Nx2 array and sorts them according
-    to their x-coordinate (index=0) or y-coordinate (index=1).
-    """
-    if len(points.shape) != 2 or (2 not in points.shape):
-        raise RuntimeError("sort_points(): Array of wrong shape.")
-    return take( points, argsort(points[:,index]) )
-
-def array_zip(*arys):
-    """
-    Returns a Numeric array that is the concatenation of the input 1-D
-    *arys* along a new axis.  This function is basically equivalent to
-    ``array(zip(*arys))``, but is more resource-efficient.
-    """
-    return transpose(array(arys))
-
-
-class AbstractDataMapper(HasStrictTraits):
-    """
-    A data mapper maps from coordinate space to data elements.  In its most
-    basic form, it loops over all the available data points to find the ones
-    near a given coordinate or within an area.  More advanced functionality
-    includes returning rect-aligned "affected regions" enclosing all the
-    returned points, etc.
-    """
-
-    # How to sort the output list of intersected points that the
-    # get_points_near_*() function returns.  The points are always sorted
-    # by their domain (first/X-value) coordinate.
-    sort_order = ArraySortTrait
-
-    # A read-only property that describes the origin and size of the data
-    # set in data space as a 4-tuple (min_x, min_y, width, height)
-    extents = Property()
-
-
-    #-------------------------------------------------------------------
-    # Private traits
-    #-------------------------------------------------------------------
-
-    _data = Any
-
-    # Internally we expect Nx2 arrays; if the user hands in something
-    # different, we stored a transposed version but always remember to
-    # transpose once again whenever we return data.
-    _is_transposed = Bool(False)
-
-    # the max and min points in data space expressed as a 4-tuple (x,y,w,h)
-    _extents = Tuple
-
-    # a "fudge factor" to make the extents slightly larger than the actual
-    # values in the data set
-    _extents_delta = Float(0.1)
-
-    def __init__(self, data=None, data_sorting='none', **kw):
-        "See set_data() for description."
-        self._data = array([])
-        HasStrictTraits.__init__(self, **kw)
-        if data is not None:
-            self.set_data(data, data_sorting)
-        return
-
-    def get_points_near(self, pointlist, radius=0.0):
-        """
-        get_points_near([points], radius=0.0) -> Nx2 array of candidate points
-
-        Returns a list of points near the input points (Nx2 array).
-
-        For each point in the input set, *radius* is used to create a
-        conceptual circle; if any points in the DataMapper's values lie inside
-        this circle, they are returned.
-
-        The returned list is not guaranteed to be a minimum or exact set,
-        but it is guaranteed to contain all points that intersect the
-        *pointlist*.  The caller still must do fine-grained testing to see
-        if the points in the returned point list are a match.
-        """
-        raise NotImplementedError
-
-    def get_points_near_polyline(self, line):
-        """
-        get_points_near_polyline([v1, ... vN]) -> [ [points], [points], ... ]
-
-        This method is like get_points_near(), except that it takes a polyline
-        as input.  A polyline is a list of vertices, each connected to the next
-        by a straight line. The polyline has infinitely thin width.
-
-        The input array can have shape 2xN or Nx2.
-        """
-        raise NotImplementedError
-
-    def get_points_in_rect(self, rect):
-        """
-        get_points_in_rect( (x,y,w,h) ) -> [ [points], [points], ... ]
-
-        This method is like get_points_near(), except that it takes a rectangle
-        as input.  The rectangle has infinitely thin width.
-        """
-        raise NotImplementedError
-
-    def get_points_in_poly(self, poly):
-        """
-        get_points_in_poly([v1, ... vN]) -> [ [points], [points], ... ]
-
-        This method is like get_points_near(), except that it takes a polygon
-        as input.  The polygon has infinitely thin width and can be
-        self-intersecting and concave.
-
-        The input array can have shape 2xN or Nx2.
-        """
-        raise NotImplementedError
-
-    def get_last_region(self):
-        """
-        Returns a region of screen space that contains all of the
-        points/lines/rect/polys in the last get_points_in_*() call.  The
-        region returned by this method is guaranteed to only contain the points
-        that were returned by the previous call.
-
-        The region is returned as a list of (possibly disjoint) rectangles,
-        where each rectangle is a 4-tuple (x,y,w,h).
-        """
-        raise NotImplementedError
-
-    def set_data(self, new_data, new_data_sorting='none'):
-        """
-        set_data(new_data, new_data_sorting='none')
-
-        Sets the data used by this DataMapper.  The *new_data_sorting* parameter
-        indicates how the new data is sorted: 'none', 'ascending', or 'descending'.
-        The default is 'none', which causes the data mapper to perform
-        a full sort of the input data.
-
-        The input data can be shaped 2xN or Nx2.
-        """
-        if len(new_data) == 0:
-            self.clear()
-            return
-
-        if new_data.shape[0] == 2:
-            self._is_transposed = True
-            self._data = transpose(new_data)
-        else:
-            self._is_transposed = False
-            self._data = new_data
-
-        if new_data_sorting == 'none':
-            if self.sort_order == 'ascending':
-                self._data = sort_points(self._data)
-            else:
-                self._data = sort_points(self._data)[::-1]
-        elif new_data_sorting != self.sort_order:
-            self._data = self._data[::-1]
-
-        self._calc_data_extents()
-        self._update_datamap()
-        # a re-sorting is unnecessary because any internal data structures
-        # will have been updated by the _data update process.
-        return
-
-    def clear(self):
-        """
-        clear()
-
-        Resets internal state and any cached data to reflect an empty
-        data set/data space.
-        """
-        self._data = None
-        self._extents = (0,0,0,0)
-        self._clear()
-        return
-
-    def get_data(self):
-        "Returns the actual data used by the DataMapper."
-        if self._is_transposed:
-            return transpose(self._data)
-        else:
-            return self._data
-
-    #-------------------------------------------------------------------
-    # Concrete private methods and event handlers
-    # Child classes shouldn't have to override these.
-    #-------------------------------------------------------------------
-
-    def _get_extents(self):
-        return self._extents
-
-    def _calc_data_extents(self):
-        """
-        Computes ((minX, minY), (width, height)) of self._data; sets self._extent and
-        returns nothing.
-        """
-        if len(self._data) == 0:
-            self._extents = ((0,0), (0,0))
-        else:
-            value = self._data
-            min_indices = argmin(value, axis=0)
-            max_indices = argmax(value, axis=0)
-            x = value[min_indices[0], 0] - self._extents_delta
-            y = value[min_indices[1], 1] - self._extents_delta
-            maxX = value[max_indices[0], 0] + self._extents_delta
-            maxY = value[max_indices[1], 1] + self._extents_delta
-            self._extents = ((x, y), (maxX-x, maxY-y))
-        return
-
-
-    #-------------------------------------------------------------------
-    # Abstract private methods and event handlers
-    #-------------------------------------------------------------------
-
-    def _update_datamap(self):
-        """
-        This function gets called after self._data has changed.  Child classes
-        should implement this function if they need to recompute any cached
-        data structures, etc.
-        """
-        return
-
-    def _clear(self):
-        "Performs subclass-specific clearing/cleanup."
-        return
-
-    def _sort_order_changed(self, old, new):
-        return
-
-
-class BruteForceDataMapper(AbstractDataMapper):
-    """
-    The BruteForceDataMapper returns all the points, all the time.
-    This is basically the same behavior as not having a data mapper in
-    the pipeline at all.
-    """
-
-    def get_points_near(self, pointlist, radius=0):
-        return self.get_data()
-
-    def get_points_near_polyline(self, line):
-        return self.get_data()
-
-    def get_points_in_rect(self, rect):
-        return self.get_data()
-
-    def get_points_in_poly(self, poly):
-        return self.get_data()
-
-    def get_last_region(self):
-        return self._extents
-
-    def _sort_order_changed(self, old, new):
-        if len(self._data) == 0:
-            return
-        else:
-            if self.sort_order == 'ascending':
-                self._data = sort_points(self._data)
-            else:
-                self._data = sort_points(self._data)[::-1]
-        return
-
-#EOF
diff --git a/chaco/subdivision_cells.py b/chaco/subdivision_cells.py
deleted file mode 100644
--- a/chaco/subdivision_cells.py
+++ /dev/null
@@ -1,245 +0,0 @@
-""" Defines cell-related classes and functions.
-"""
-import itertools
-
-from numpy import take, array, concatenate, nonzero
-
-from traits.api import HasStrictTraits, Instance, Delegate, Array, List, \
-                             Tuple, Property, Trait, Any, Disallow
-
-from .datamapper import AbstractDataMapper, right_shift, left_shift, sort_points
-
-
-def find_runs(int_array, order='ascending'):
-    """
-    find_runs(int_array, order=<'ascending'|'flat'|'descending'>) -> list_of_int_arrays
-
-    Given an integer array sorted in ascending, descending, or flat order, this
-    function returns a list of continuous runs of integers inside the list.
-    For example::
-
-        find_runs([1,2,3,6,7,8,9,10,11,15])
-
-    returns [ [1,2,3], [6,7,8,9,10,11], [15] ]
-    and::
-
-        find_runs([0,0,0,1,1,1,1,0,0,0,0])
-
-    returns [ [0,0,0], [1,1,1,1], [0,0,0,0] ]
-    """
-    ranges = arg_find_runs(int_array, order)
-    if ranges:
-        return [int_array[i:j] for (i,j) in ranges]
-    else:
-        return []
-
-def arg_find_runs(int_array, order='ascending'):
-    """
-    This function is like find_runs(), but it returns a list of tuples
-    indicating the start and end indices of runs in the input *int_array*.
-    """
-    if len(int_array) == 0:
-        return []
-    assert len(int_array.shape)==1, "find_runs() requires a 1D integer array."
-    if order == 'ascending':
-        increment = 1
-    elif order == 'descending':
-        increment = -1
-    else:
-        increment = 0
-    rshifted = right_shift(int_array, int_array[0]-increment)
-    start_indices = concatenate([[0], nonzero(int_array - (rshifted+increment))[0]])
-    end_indices = left_shift(start_indices, len(int_array))
-    return list(zip(start_indices, end_indices))
-
-
-class AbstractCell(HasStrictTraits):
-    """ Abstract class for grid cells in a uniform subdivision.
-
-    Individual subclasses store points in different, possibly optimized
-    fashion, and performance may be drastically different between different
-    cell subclasses for a given set of data.
-    """
-    # The parent of this cell.
-    parent = Instance(AbstractDataMapper)
-
-    # The sort traits characterizes the internal points list.
-    _sort_order = Delegate('parent')
-
-    # The point array for this cell. This attribute delegates to parent._data,
-    # which references the actual point array. For the sake of simplicity,
-    # cells assume that _data is sorted in fashion indicated by **_sort_order**.
-    # If this doesn't hold, then each cell needs to have its own duplicate
-    # copy of the sorted data.
-    data = Delegate('parent', '_data')
-
-    # A list of indices into **data** that reflect the points inside this cell.
-    indices = Property
-
-    # Shadow trait for **indices**.
-    _indices = Any
-
-    def add_indices(self, indices):
-        """ Adds a list of integer indices to the existing list of indices.
-        """
-        raise NotImplementedError
-
-    def get_points(self):
-        """ Returns a list of points that was previously set.
-
-        This operation might be large and expensive; in general, use
-        _get_indices() instead.
-        """
-        raise NotImplementedError
-
-    def reverse_indices(self):
-        """ Tells the cell to manipulate its indices so that they index to the
-        same values in a reversed data array.
-
-        Generally this method handles the situation when the parent's _data
-        array has been flipped due to a sort order change.
-
-        The length of _data must not have changed; otherwise there is no way to
-        know the proper way to manipulate indices.
-        """
-        raise NotImplementedError
-
-    def _set_indices(self, indices):
-        raise NotImplementedError
-
-    def _get_indices(self):
-        """  Returns the list of indices into _data that reflect the points
-        inside this cell.
-        """
-        raise NotImplementedError
-
-
-class Cell(AbstractCell):
-    """
-    A basic cell that stores its point indices as an array of integers.
-    """
-    # A list of indices into **data** that reflect the points inside this cell
-    # (overrides AbstractCell).
-    indices = Property(Array)
-
-    # A 1-D array of indices into _data.
-    _indices = Array
-
-    def __init__(self, **kw):
-        self._indices = array([])
-        super(AbstractCell, self).__init__(**kw)
-
-    def add_indices(self, indices):
-        """ Adds a list of integer indices to the existing list of indices.
-
-        Implements AbstractCell.
-        """
-        self._indices = concatenate([self._indices, indices])
-        return
-
-    def get_points(self):
-        """ Returns a list of points that was previously set.
-
-        Implements AbstractCell.
-        """
-        return take(self.data, self._indices)
-
-    def reverse_indices(self):
-        """ Tells the cell to manipulate its indices so that they index to the
-        same values in a reversed data array.
-
-        Implements AbstractCell.
-        """
-        length = len(self.data)
-        self._indices = [length-i-1 for i in self._indices]
-        return
-
-    def _set_indices(self, indices):
-        self._indices = indices
-        return
-
-    def _get_indices(self):
-        return self._indices
-
-
-
-class RangedCell(AbstractCell):
-    """ A cell optimized for storing lists of continuous points.
-
-    Rather than storing each individual point index as an element in an array,
-    RangedCell stores a list of index ranges; each range is a (start,end) tuple).
-    """
-
-    # A list of indices into **data** that reflect the points inside this cell
-    # (overrides AbstractCell).
-    indices = Property
-
-    # Don't use the _indices shadow trait; rather, the getters and setters
-    # for 'index' procedurally generate indices from **ranges**.
-    _indices = Disallow
-
-    # Ranges are an additional interface on RangedCells.
-    ranges = Property(List(Tuple))
-
-    # Shadow trait for ranges.
-    _ranges = List(Tuple)
-
-    #---------------------------------------------------------------------
-    # AbstractCell methods
-    #---------------------------------------------------------------------
-
-    def add_indices(self, indices):
-        """ Adds a list of integer indices to the existing list of indices.
-
-        Implements AbstractCell.
-        """
-        self.add_ranges(find_runs(indices))
-        return
-
-    def get_points(self):
-        """ Returns a list of points that was previously set.
-
-        Implements AbstractCell.
-        """
-        return take(self.data, self.indices)
-
-    def reverse_indices(self):
-        """ Tells the cell to manipulate its indices so that they index to the
-        same values in a reversed data array.
-
-        Implements AbstractCell.
-        """
-        length = len(self.data)
-        self._ranges = [(length-end-1, length-start-1) for (start,end) in self._ranges]
-        return
-
-    def _set_indices(self, indices):
-        self._ranges = find_runs(indices)
-        return
-
-    def _get_indices(self):
-        list_of_indices = [range(i, j) for (i, j) in self._ranges]
-        return list(itertools.chain(*list_of_indices))
-
-
-    #---------------------------------------------------------------------
-    # additional RangedCell methods
-    #---------------------------------------------------------------------
-
-    def get_ranges(self):
-        """ Returns a list of tuples representing the (start,end) indices of
-        continuous ranges of points in self._data.
-        """
-        return self._ranges()
-
-    def add_ranges(self, ranges):
-        """ Adds a list of ranges ((start,end) tuples) to the current list.
-
-        This method doesn't check for duplicate or overlapping ranges.
-        """
-        if self._ranges:
-            self._ranges.extend(ranges)
-        else:
-            self._ranges = ranges
-        return
-#EOF
diff --git a/chaco/subdivision_mapper.py b/chaco/subdivision_mapper.py
deleted file mode 100644
--- a/chaco/subdivision_mapper.py
+++ /dev/null
@@ -1,217 +0,0 @@
-""" Defines the SubdivisionDataMapper and SubdivisionLineDataMapper classes.
-"""
-# Major library imports
-
-import math
-from numpy import array, arange, concatenate, searchsorted, nonzero, transpose, \
-                  argsort, zeros, sort, vstack
-import numpy
-
-# Enthought library imports
-from traits.api import List, Array, Tuple, Int, Float
-
-# Local, relative imports
-from .datamapper import AbstractDataMapper, right_shift, left_shift, \
-                       sort_points, ArraySortTrait, \
-                       array_zip
-from .subdivision_cells import AbstractCell, Cell, RangedCell, find_runs, \
-                              arg_find_runs
-
-
-class SubdivisionDataMapper(AbstractDataMapper):
-    """
-    A data mapper that uses a uniform grid of rectangular cells. It doesn't make
-    any assumptions about the continuity of the input data set, and explicitly
-    stores each point in the data set in its cell.
-
-    If the incoming data is ordered in some fashion such that most cells end
-    up with large ranges of data, then it's better to use the
-    SubdivisionLineDataMapper subclass.
-    """
-    celltype = Cell
-    _last_region = List(Tuple)
-    _cellgrid = Array   # a Numeric array of Cell objects
-    _points_per_cell = Int(100)   # number of datapoints/cell to shoot for
-    _cell_lefts = Array    # locations of left edge for all cells
-    _cell_bottoms = Array  # locations of bottom edge for all cells
-    _cell_extents = Tuple(Float, Float)         # the width and height of a cell
-
-    #-------------------------------------------------------------------
-    # Public AbstractDataMapper methods
-    #-------------------------------------------------------------------
-
-    def get_points_near(self, pointlist, radius=0.0):
-        if radius != 0:
-            # tmp is a list of list of arrays
-            d = 2*radius
-            cell_points = [ self.get_points_in_rect((px-radius,py-radius,d,d))
-                                for (px,py) in pointlist ]
-        else:
-            indices = self._get_indices_for_points(pointlist)
-            cells = [self._cellgrid[i,j] for (i,j) in indices]
-            self._last_region = self._cells_to_rects(indices)
-            # unique-ify the list of cells
-            cell_points = [c.get_points() for c in set(cells)]
-        return vstack(cell_points)
-
-
-    def get_points_in_rect(self, rect):
-        x_span = (rect[0], rect[0]+rect[2])
-        y_span = (rect[1], rect[1]+rect[3])
-        min_i, max_i = searchsorted(self._cell_lefts, x_span) - 1
-        min_j, max_j = searchsorted(self._cell_bottoms, y_span) - 1
-        cellpts = [ self._cellgrid[i,j].get_points()
-                        for i in range(min_i, max_i+1) \
-                            for j in range(min_j, max_j+1) ]
-        self._last_region = ( self._cell_lefts[min_i], self._cell_bottoms[min_j], \
-                              (max_i - min_i + 1) * self._cell_extents[0], \
-                              (max_j - min_j + 1) * self._cell_extents[1] )
-        return vstack(cellpts)
-
-
-    def get_last_region(self):
-        return self._last_region
-
-    #-------------------------------------------------------------------
-    # AbstractDataMapper's abstract private methods
-    #-------------------------------------------------------------------
-
-    def _update_datamap(self):
-        self._last_region = []
-        # Create a new grid of the appropriate size, initialize it with new
-        # Cell instance (of type self.celltype), and perform point insertion
-        # on the new data.
-        if self._data is None:
-            self._cellgrid = array([], dtype=object)
-            self._cell_lefts = array([])
-            self._cell_bottoms = array([])
-        else:
-            num_x_cells, num_y_cells = self._calc_grid_dimensions()
-            self._cellgrid = zeros((num_x_cells, num_y_cells), dtype=object)
-            for i in range(num_x_cells):
-                for j in range(num_y_cells):
-                    self._cellgrid[i,j] = self.celltype(parent=self)
-            ll, ur = self._extents
-            cell_width = ur[0]/num_x_cells
-            cell_height = ur[1]/num_y_cells
-
-            # calculate the left and bottom edges of all the cells and store
-            # them in two arrays
-            self._cell_lefts = arange(ll[0], ll[0]+ur[0]-cell_width/2, step=cell_width)
-            self._cell_bottoms = arange(ll[1], ll[1]+ur[1]-cell_height/2, step=cell_height)
-
-            self._cell_extents = (cell_width, cell_height)
-
-            # insert the data points
-            self._basic_insertion(self.celltype)
-        return
-
-    def _clear(self):
-        self._last_region = []
-        self._cellgrid = []
-        self._cell_lefts = []
-        self._cell_bottoms = []
-        self._cell_extents = (0,0)
-        return
-
-    def _sort_order_changed(self, old, new):
-        # since trait event notification only happens if the value has changed,
-        # and there are only two types of sorting, it's safe to just reverse our
-        # internal _data object
-        self._data = self._data[::-1]
-        for cell in self._cellgrid:
-            # since cellgrid is a Numeric array, iterating over it produces
-            # a length-1 array
-            cell[0].reverse_indices()
-        return
-
-
-    #-------------------------------------------------------------------
-    # helper private methods
-    #-------------------------------------------------------------------
-
-    def _calc_grid_dimensions(self):
-        numpoints = self._data.shape[0]
-        numcells = numpoints / self._points_per_cell
-        ll, ur = self._extents
-        aspect_ratio = (ur[0]-ll[0]) / (ur[1]-ll[1])
-        num_y_cells = int(math.sqrt(numcells / aspect_ratio))
-        num_x_cells = int(aspect_ratio * num_y_cells)
-        if num_y_cells == 0:
-            num_y_cells += 1
-        if num_y_cells*num_x_cells*self._points_per_cell < numpoints:
-            num_x_cells += 1
-        return (num_x_cells, num_y_cells)
-
-    def _basic_insertion(self, celltype):
-        # generate a list of which cell each point in self._data belongs in
-        cell_indices = self._get_indices_for_points(self._data)
-
-        # We now look for ranges of points belonging to the same cell.
-        # 1. shift lengthwise and difference; runs of cells with the same
-        # (i,j) indices will be zero, and nonzero value for i or j will
-        # indicate a transition to a new cell.  (Just like find_runs().)
-        differences = cell_indices[1:] - cell_indices[:-1]
-
-        # Since nonzero() only works for 1D arrays, we merge the X and Y columns
-        # together to detect any point where either X or Y are nonzero.  We have
-        # to add 1 because we shifted cell_indices before differencing (above).
-        diff_indices = nonzero(differences[:,0] + differences[:,1])[0] + 1
-
-        start_indices = concatenate([[0], diff_indices])
-        end_indices = concatenate([diff_indices, [len(self._data)]])
-
-        for start,end in zip(start_indices, end_indices):
-            gridx, gridy = cell_indices[start]  # can use 'end' here just as well
-            if celltype == RangedCell:
-                self._cellgrid[gridx,gridy].add_ranges([(start,end)])
-            else:
-                self._cellgrid[gridx,gridy].add_indices(list(range(start,end)))
-        return
-
-    def _get_indices_for_points(self, pointlist):
-        """
-        Given an input Nx2 array of points, returns a list Nx2 corresponding
-        to the column and row indices into the cell grid.
-        """
-        x_array = searchsorted(self._cell_lefts, pointlist[:,0]) - 1
-        y_array = searchsorted(self._cell_bottoms, pointlist[:,1]) - 1
-        return array_zip(x_array, y_array)
-
-
-    def _cells_to_rects(self, cells):
-        """
-        Converts the extents of a list of cell grid coordinates (i,j) into
-        a list of rect tuples (x,y,w,h).  The set should be disjoint, but may
-        or may not be minimal.
-        """
-        # Since this function is generally used to generate clipping regions
-        # or other screen-related graphics, we should try to return large
-        # rectangular blocks if possible.
-        # For now, we just look for horizontal runs and return those.
-        cells = array(cells)
-        y_sorted = sort_points(cells, index=1)  # sort acoording to row
-        rownums = sort(array(tuple(set(cells[:,1]))))
-
-        row_start_indices = searchsorted(y_sorted[:,1], rownums)
-        row_end_indices = left_shift(row_start_indices, len(cells))
-
-        rects = []
-        for rownum, start, end in zip(rownums, row_start_indices, row_end_indices):
-            # y_sorted is sorted by the J (row) coordinate, so after we
-            # extract the column indices, we need to sort them before
-            # passing them to find_runs().
-            grid_column_indices = sort(y_sorted[start:end][:,0])
-            for span in find_runs(grid_column_indices):
-                x = self._cell_lefts[span[0]]
-                y = self._cell_bottoms[rownum]
-                w = (span[-1] - span[0] + 1) * self._cell_extents[0]
-                h = self._cell_extents[1]
-                rects.append((x,y,w,h))
-        return rects
-
-
-class SubdivisionLineDataMapper(SubdivisionDataMapper):
-    """ A subdivision data mapper that uses ranged cells.
-    """
-    celltype = RangedCell


Test patch: