1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
from datetime import date, datetime
from leather.data_types import Date, DateTime, Number, Text
from leather.shapes import Bars, Columns
class Scale:
"""
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(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 str(value)
|