Repository URL to install this package:
Version:
5.0.0 ▾
|
chaco
/
point_data_source.py
|
---|
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
"""
Defines the PointDataSource class.
"""
# Major library imports
from numpy import array, transpose
# Enthought library imports
from traits.api import Enum, Property, ReadOnly, Tuple
# Local, relative imports
from .base import PointTrait, reverse_map_1d, SortOrderTrait
from .array_data_source import ArrayDataSource
class PointDataSource(ArrayDataSource):
"""A data source representing a (possibly unordered) set of (X,Y) points.
This is internally always represented by an Nx2 array, so that data[i]
refers to a single point (represented as a length-2 array).
Most of the traits and methods of ArrayDataSeries work for the
PointDataSeries as well, since its data is linear. This class
overrides only the methods and traits that are different.
"""
#: The dimensionality of the indices into this data source (overrides
#: ArrayDataSource).
index_dimension = ReadOnly("scalar")
#: The dimensionality of the value at each index point (overrides
#: ArrayDataSource).
value_dimension = ReadOnly("point")
#: The sort order of the data. Although sort order is less common with point
#: data, it can be useful in case where the value data is sorted along some
#: axis. Note that **sort_index** is used only if **sort_order** is not
#: 'none'.
sort_order = SortOrderTrait
#: Which of the value axes the **sort_order** refers to.
#: If **sort_order** is 'none', this attribute is ignored.
#: In the unlikely event that the value data is sorted along both
#: X and Y (i.e., monotonic in both axes), then set **sort_index** to
#: whichever one has the best binary-search performance for hit-testing.
sort_index = Enum(0, 1)
# ------------------------------------------------------------------------
# Private traits
# ------------------------------------------------------------------------
# The actual data (overrides ArrayDataSource).
_data = PointTrait
# Cached values of min and max as long as **_data** doesn't change
# (overrides ArrayDataSource). ((min_x, max_x), (min_y, max_y))
_cached_bounds = Tuple
# These return lists of all the x and y positions, respectively
# List of X positions.
_xdata = Property
# List of Y positions.
_ydata = Property
# ------------------------------------------------------------------------
# AbstractDataSource interface
# ------------------------------------------------------------------------
def __init__(self, data=transpose(array([[], []])), **kw):
shape = data.shape
if (len(shape) != 2) or (shape[1] != 2):
raise RuntimeError(
"PointDataSource constructor requires Nx2 array, but got array of shape "
+ str(shape)
+ " instead."
)
super().__init__(data, **kw)
def get_data(self):
"""Returns the data for this data source, or (0.0, 0.0) if it has no
data.
Overrides ArryDataSource.
"""
if self._data is not None:
return self._data
else:
return (0.0, 0.0)
def reverse_map(self, pt, index=0, outside_returns_none=True):
"""Returns the index of *pt* in the data source.
Overrides ArrayDataSource.
Parameters
----------
pt : (x, y)
value to find
index : 0 or 1
Which of the axes of *pt* the *sort_order* refers to.
outside_returns_none : Boolean
Whether the method returns None if *pt* is outside the range of
the data source; if False, the method returns the value of the
bound that *pt* is outside of, in the *index* dimension.
"""
# reverse_map is of limited utility for a PointDataSeries and thus
# we only perform reverse-mapping if the data is sorted along an axis.
if self.sort_order == "none":
# By default, only provide reverse_map if the value data is sorted
# along one of its axes.
raise NotImplementedError
if index != 0 and index != 1:
raise ValueError("Index must be 0 or 1.")
# This basically reduces to a scalar data search along self.data[index].
lowerleft, upperright = self._cached_bounds
min_val = lowerleft[index]
max_val = upperright[index]
val = pt[index]
if val < min_val:
if outside_returns_none:
return None
else:
return self._min_index
elif val > max_val:
if outside_returns_none:
return None
else:
return self._max_index
else:
return reverse_map_1d(self._data[:, index], val, self.sort_order)
# ------------------------------------------------------------------------
# Private methods
# ------------------------------------------------------------------------
def _compute_bounds(self):
"""Computes the minimum and maximum values of self._data.
Overrides ArrayDataSource.
"""
if len(self._data) == 0:
self._cached_bounds = ((0.0, 0.0), (0.0, 0.0))
elif len(self._data) == 1:
x, y = self._data[0]
self._cached_bounds = ((x, y), (x, y))
else:
# calculate the X and Y values independently
x = self._data[:, 0]
min_x = min(x)
max_x = max(x)
y = self._data[:, 1]
min_y = min(y)
max_y = max(y)
self._cached_bounds = ((min_x, min_y), (max_x, max_y))
def _get__xdata(self):
return ArrayDataSource(self._data[:, 0])
def _get__ydata(self):
return ArrayDataSource(self._data[:, 1])