141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
#!/usr/bin/env python
|
|
|
|
from datetime import date, datetime
|
|
|
|
import six
|
|
|
|
from leather.data_types import Date, DateTime, Number, Text
|
|
from leather.shapes import Bars, Columns
|
|
|
|
|
|
class Scale(object):
|
|
"""
|
|
Base class for various kinds of scale objects.
|
|
"""
|
|
@classmethod
|
|
def infer(cls, layers, dimension, data_type):
|
|
"""
|
|
Infer's an appropriate default scale for a given sequence of
|
|
:class:`.Series`.
|
|
|
|
:param chart_series:
|
|
A sequence of :class:`.Series` instances
|
|
:param dimension:
|
|
The dimension, :code:`X` or :code:`Y` of the data to infer for.
|
|
:param data_type:
|
|
The type of data contained in the series dimension.
|
|
"""
|
|
from leather.scales.linear import Linear
|
|
from leather.scales.ordinal import Ordinal
|
|
from leather.scales.temporal import Temporal
|
|
|
|
# Default Time scale is Temporal
|
|
if data_type is Date:
|
|
data_min = date.max
|
|
data_max = date.min
|
|
|
|
for series, shape in layers:
|
|
data_min = min(data_min, series.min(dimension))
|
|
data_max = max(data_max, series.max(dimension))
|
|
|
|
scale = Temporal(data_min, data_max)
|
|
elif data_type is DateTime:
|
|
data_min = datetime.max
|
|
data_max = datetime.min
|
|
|
|
for series, shape in layers:
|
|
data_min = min(data_min, series.min(dimension))
|
|
data_max = max(data_max, series.max(dimension))
|
|
|
|
scale = Temporal(data_min, data_max)
|
|
# Default Number scale is Linear
|
|
elif data_type is Number:
|
|
force_zero = False
|
|
data_min = None
|
|
data_max = None
|
|
|
|
for series, shape in layers:
|
|
if isinstance(shape, (Bars, Columns)):
|
|
force_zero = True
|
|
|
|
if data_min is None:
|
|
data_min = series.min(dimension)
|
|
else:
|
|
data_min = min(data_min, series.min(dimension))
|
|
|
|
if data_max is None:
|
|
data_max = series.max(dimension)
|
|
else:
|
|
data_max = max(data_max, series.max(dimension))
|
|
|
|
if force_zero:
|
|
if data_min > 0:
|
|
data_min = 0
|
|
|
|
if data_max < 0:
|
|
data_max = 0
|
|
|
|
scale = Linear(data_min, data_max)
|
|
# Default Text scale is Ordinal
|
|
elif data_type is Text:
|
|
scale_values = None
|
|
|
|
# First case: a single set of ordinal labels
|
|
if len(layers) == 1:
|
|
scale_values = layers[0][0].values(dimension)
|
|
else:
|
|
first_series = set(layers[0][0].values(dimension))
|
|
data_series = [series.values(dimension) for series, shape in layers]
|
|
all_same = True
|
|
|
|
for series in data_series:
|
|
if set(series) != first_series:
|
|
all_same = False
|
|
break
|
|
|
|
# Second case: multiple identical sets of ordinal labels
|
|
if all_same:
|
|
scale_values = layers[0][0].values(dimension)
|
|
# Third case: multiple different sets of ordinal labels
|
|
else:
|
|
scale_values = sorted(list(set().union(*data_series)))
|
|
|
|
scale = Ordinal(scale_values)
|
|
|
|
return scale
|
|
|
|
def contains(self, v):
|
|
"""
|
|
Return :code:`True` if a given value is contained within this scale's
|
|
displayed domain.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def project(self, value, range_min, range_max):
|
|
"""
|
|
Project a value in this scale's domain to a target range.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def project_interval(self, value, range_min, range_max):
|
|
"""
|
|
Project a value in this scale's domain to an interval in the target
|
|
range. This is used for places :class:`.Bars` and :class:`.Columns`.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def ticks(self):
|
|
"""
|
|
Generate a series of ticks for this scale.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def format_tick(self, value, i, count):
|
|
"""
|
|
Format ticks for display.
|
|
|
|
This method is used as a default which will be ignored if the user
|
|
provides a custom tick formatter to the axis.
|
|
"""
|
|
return six.text_type(value)
|