Source code for plotnine.scales.limits
import sys
from contextlib import suppress
import pandas as pd
from ..exceptions import PlotnineError
from ..geoms import geom_blank
from ..mapping.aes import ALL_AESTHETICS, aes
from ..scales.scales import make_scale
from ..utils import array_kind
# By adding limits, we create a scale of the appropriate type
class _lim:
aesthetic = None
def __init__(self, *limits):
if not limits:
msg = "{}lim(), is missing limits"
raise PlotnineError(msg.format(self.aesthetic))
elif len(limits) == 1:
limits = limits[0]
series = pd.Series(limits)
# Type of transform
if not any(x is None for x in limits) and limits[0] > limits[1]:
self.trans = "reverse"
elif array_kind.continuous(series):
self.trans = "identity"
elif array_kind.discrete(series):
self.trans = None
elif array_kind.datetime(series):
self.trans = "datetime"
elif array_kind.timedelta(series):
self.trans = "timedelta"
else:
msg = f"Unknown type {type(limits[0])} of limits"
raise TypeError(msg)
self.limits = limits
self.limits_series = series
def get_scale(self, gg):
"""
Create a scale
"""
# This method does some introspection to save users from
# scale mismatch error. This could happen when the
# aesthetic is mapped to a categorical but the limits
# are not provided in categorical form. We only handle
# the case where the mapping uses an expression to
# conver to categorical e.g `aes(color='factor(cyl)')`.
# However if `'cyl'` column is a categorical and the
# mapping is `aes(color='cyl')`, that will result in
# an error. If later case proves common enough then we
# could inspect the data and be clever based on that too!!
ae = self.aesthetic
series = self.limits_series
ae_values = []
# Look through all the mappings for this aesthetic,
# if we detect any factor stuff then we convert the
# limits data to categorical so that the right scale
# can be choosen. This should take care of the most
# common use cases.
for layer in gg.layers:
with suppress(KeyError):
value = layer.mapping[ae]
if isinstance(value, str):
ae_values.append(value)
for value in ae_values:
if "factor(" in value or "Categorical(" in value:
series = pd.Categorical(self.limits_series)
break
return make_scale(
self.aesthetic, series, limits=self.limits, trans=self.trans
)
def __radd__(self, gg):
scale = self.get_scale(gg)
gg.scales.append(scale)
return gg
[docs]class xlim(_lim):
"""
Set x-axis limits
Parameters
----------
limits : array_like
Min and max limits. Must be of size 2.
You can also pass two values e.g
``xlim(40, 100)``
"""
aesthetic = "x"
[docs]class ylim(_lim):
"""
Set y-axis limits
Parameters
----------
limits : array_like
Min and max limits. Must be of size 2.
You can also pass two values e.g
``ylim(40, 100)``
Notes
-----
If the 2nd value of ``limits`` is less than
the first, a reversed scale will be created.
"""
aesthetic = "y"
class alphalim(_lim):
"""
Alpha limits
"""
aesthetic = "alpha"
class colorlim(_lim):
"""
Color limits
"""
aesthetic = "color"
class filllim(_lim):
"""
Fill limits
"""
aesthetic = "fill"
class linetypelim(_lim):
"""
Linetype limits
"""
aesthetic = "linetype"
class shapelim(_lim):
"""
Shapee limits
"""
aesthetic = "shape"
class sizelim(_lim):
"""
Size limits
"""
aesthetic = "size"
class strokelim(_lim):
"""
Stroke limits
"""
aesthetic = "stroke"
[docs]class lims:
"""
Set aesthetic limits
Parameters
----------
kwargs : dict
Aesthetic and the values of the limits.
e.g ``x=(40, 100)``
Notes
-----
If the 2nd value of ``limits`` is less than
the first, a reversed scale will be created.
"""
def __init__(self, **kwargs):
self._kwargs = kwargs
def __radd__(self, gg):
"""
Add limits to ggplot object
"""
thismodule = sys.modules[__name__]
for ae, value in self._kwargs.items():
try:
klass = getattr(thismodule, f"{ae}lim")
except AttributeError:
raise PlotnineError("Cannot change limits for '{}'")
gg += klass(value)
return gg
[docs]def expand_limits(**kwargs):
"""
Expand the limits any aesthetic using data
Parameters
----------
kwargs : dict or dataframe
Data to use in expanding the limits.
The keys should be aesthetic names
e.g. *x*, *y*, *colour*, ...
"""
def as_list(key):
with suppress(KeyError):
if isinstance(kwargs[key], (int, float, str)):
kwargs[key] = [kwargs[key]]
if isinstance(kwargs, dict):
as_list("x")
as_list("y")
data = pd.DataFrame(kwargs)
else:
data = kwargs
mapping = aes()
for ae in set(kwargs) & ALL_AESTHETICS:
mapping[ae] = ae
return geom_blank(data=data, mapping=mapping, inherit_aes=False)