142 lines
4.0 KiB
Python
142 lines
4.0 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
try:
|
||
|
from collections.abc import Iterable, Sequence, Mapping
|
||
|
except ImportError:
|
||
|
from collections import Iterable, Sequence, Mapping
|
||
|
from functools import partial
|
||
|
|
||
|
import six
|
||
|
|
||
|
from leather.data_types import DataType
|
||
|
from leather.utils import DIMENSION_NAMES, X, Y, Datum
|
||
|
|
||
|
|
||
|
class Series(object):
|
||
|
"""
|
||
|
A series of data and its associated metadata.
|
||
|
|
||
|
Series object does not modify the data it is passed.
|
||
|
|
||
|
:param data:
|
||
|
A sequence (rows) of sequences (columns), a.k.a. :func:`csv.reader`
|
||
|
format. If the :code:`x` and :code:`y` are not specified then the first
|
||
|
column is used as the X values and the second column is used for Y.
|
||
|
|
||
|
Or, a sequence of (rows) of dicts (columns), a.k.a.
|
||
|
:class:`csv.DictReader` format. If this format is used then :code:`x`
|
||
|
and :code:`y` arguments must specify the columns to be charted.
|
||
|
|
||
|
Or, a custom data format, in which case :code:`x` and :code:`y` must
|
||
|
specify :func:`.key_function`.
|
||
|
:param x:
|
||
|
If using sequence row data, then this may be either an integer index
|
||
|
identifying the X column, or a :func:`.key_function`.
|
||
|
|
||
|
If using dict row data, then this may be either a key name identifying
|
||
|
the X column, or a :func:`.key_function`.
|
||
|
|
||
|
If using a custom data format, then this must be a
|
||
|
:func:`.key_function`.`
|
||
|
:param y:
|
||
|
See :code:`x`.
|
||
|
:param name:
|
||
|
An optional name to be used in labeling this series. This will be
|
||
|
used as the chart title if rendered in a :class:`.Lattice`.
|
||
|
"""
|
||
|
def __init__(self, data, x=None, y=None, name=None):
|
||
|
self._data = data
|
||
|
self._name = name
|
||
|
|
||
|
self._keys = [
|
||
|
self._make_key(x if x is not None else X),
|
||
|
self._make_key(y if y is not None else Y)
|
||
|
]
|
||
|
|
||
|
self._types = [
|
||
|
self._infer_type(X),
|
||
|
self._infer_type(Y)
|
||
|
]
|
||
|
|
||
|
def _make_key(self, key):
|
||
|
"""
|
||
|
Process a user-specified data key and convert to a function if needed.
|
||
|
"""
|
||
|
if callable(key):
|
||
|
return key
|
||
|
else:
|
||
|
return lambda row, index: row[key]
|
||
|
|
||
|
def _infer_type(self, dimension):
|
||
|
"""
|
||
|
Infer the datatype of this column by sampling the data.
|
||
|
"""
|
||
|
key = self._keys[dimension]
|
||
|
|
||
|
for i, row in enumerate(self._data):
|
||
|
v = key(row, i)
|
||
|
|
||
|
if v is not None:
|
||
|
break
|
||
|
|
||
|
if v is None:
|
||
|
raise ValueError('All values in %s dimension are null.' % DIMENSION_NAMES[dimension])
|
||
|
|
||
|
return DataType.infer(v)
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return self._name
|
||
|
|
||
|
def data_type(self, dimension):
|
||
|
"""
|
||
|
Return the data type for a dimension of this series.
|
||
|
"""
|
||
|
return self._types[dimension]
|
||
|
|
||
|
def data(self):
|
||
|
"""
|
||
|
Return data for this series.
|
||
|
"""
|
||
|
x = self._keys[X]
|
||
|
y = self._keys[Y]
|
||
|
|
||
|
for i, row in enumerate(self._data):
|
||
|
yield Datum(i, x(row, i), y(row, i), None, row)
|
||
|
|
||
|
def values(self, dimension):
|
||
|
"""
|
||
|
Get a flattened list of values for a given dimension of the data.
|
||
|
"""
|
||
|
key = self._keys[dimension]
|
||
|
|
||
|
return [key(row, i) for i, row in enumerate(self._data)]
|
||
|
|
||
|
def min(self, dimension):
|
||
|
"""
|
||
|
Compute the minimum value of a given dimension.
|
||
|
"""
|
||
|
return min(v for v in self.values(dimension) if v is not None)
|
||
|
|
||
|
def max(self, dimension):
|
||
|
"""
|
||
|
Compute the minimum value of a given dimension.
|
||
|
"""
|
||
|
return max(v for v in self.values(dimension) if v is not None)
|
||
|
|
||
|
|
||
|
def key_function(row, index):
|
||
|
"""
|
||
|
This example shows how to define a function to extract X and Y values
|
||
|
from custom data.
|
||
|
|
||
|
:param row:
|
||
|
The function will be called with the row data, in whatever format it
|
||
|
was provided to the :class:`.Series`.
|
||
|
:param index:
|
||
|
The row index in the series data will also be provided.
|
||
|
:returns:
|
||
|
The function must return a chartable value.
|
||
|
"""
|
||
|
pass
|