Skip to content

Commit

Permalink
A little more test coverage and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
mwaskom committed Jun 26, 2022
1 parent a49727a commit ad693ae
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 76 deletions.
178 changes: 107 additions & 71 deletions seaborn/_core/scales.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ def __post_init__(self):
def tick(self):
raise NotImplementedError()

def _get_locators(self):
def label(self):
raise NotImplementedError()

def label(self):
def _get_locators(self):
raise NotImplementedError()

def _get_formatter(self):
Expand Down Expand Up @@ -230,6 +230,14 @@ def tick(self, locator=None):
}
return new

def label(self, formatter=None):

new = copy(self)
new._label_params = {
"formatter": formatter,
}
return new

def _get_locators(self, locator):

if locator is not None:
Expand All @@ -239,14 +247,6 @@ def _get_locators(self, locator):

return locator, None

def label(self, formatter=None):

new = copy(self)
new._label_params = {
"formatter": formatter,
}
return new

def _get_formatter(self, locator, formatter):

if formatter is not None:
Expand Down Expand Up @@ -390,7 +390,7 @@ def tick(
every: float | None = None,
between: tuple[float, float] | None = None,
minor: int | None = None,
) -> Continuous: # TODO type return value as Self
) -> Continuous:
"""
Configure the selection of ticks for the scale's axis or legend.
Expand Down Expand Up @@ -450,6 +450,55 @@ def tick(
}
return new

def label(
self,
formatter=None, *,
like: str | Callable | None = None,
base: int | None = None,
unit: str | None = None,
) -> Continuous:
"""
Configure the appearance of tick labels for the scale's axis or legend.
Parameters
----------
formatter : matplotlib Formatter
Pre-configured formatter to use; other parameters will be ignored.
like : str or callable
Either a format pattern (e.g., `".2f"`), a format string with fields named
`x` and/or `pos` (e.g., `"${x:.2f}"`), or a callable that consumes a number
and returns a string.
base : number
Use log formatter (with scientific notation) having this value as the base.
unit : str or (str, str) tuple
Use engineering formatter with SI units (e.g., with `unit="g"`, a tick value
of 5000 will appear as `5 kg`). When a tuple, the first element gives the
seperator between the number and unit.
Returns
-------
Copy of self with new label configuration.
"""
if formatter is not None and not isinstance(formatter, Formatter):
raise TypeError(
f"Label formatter must be an instance of {Formatter!r}, "
f"not {type(formatter)!r}"
)

if like is not None and not (isinstance(like, str) or callable(like)):
msg = f"`like` must be a string or callable, not {type(like).__name__}."
raise TypeError(msg)

new = copy(self)
new._label_params = {
"formatter": formatter,
"like": like,
"base": base,
"unit": unit,
}
return new

def _get_locators(self, locator, at, upto, count, every, between, minor):

# TODO what about symlog?
Expand Down Expand Up @@ -510,55 +559,6 @@ def _get_locators(self, locator, at, upto, count, every, between, minor):

return major_locator, minor_locator

def label(
self,
formatter=None, *,
like: str | Callable | None = None,
base: int | None = None,
unit: str | None = None,
) -> Continuous:
"""
Configure the appearance of tick labels for the scale's axis or legend.
Parameters
----------
formatter : matplotlib Formatter
Pre-configured formatter to use; other parameters will be ignored.
like : str or callable
Either a format pattern (e.g., `".2f"`), a format string with fields named
`x` and/or `pos` (e.g., `"${x:.2f}"`), or a callable that consumes a number
and returns a string.
base : number
Use log formatter (with scientific notation) having this value as the base.
unit : str or (str, str) tuple
Use engineering formatter with SI units (e.g., with `unit="g"`, a tick value
of 5000 will appear as `5 kg`). When a tuple, the first element gives the
seperator between the number and unit.
Returns
-------
Copy of self with new label configuration.
"""
if formatter is not None and not isinstance(formatter, Formatter):
raise TypeError(
f"Label formatter must be an instance of {Formatter!r}, "
f"not {type(formatter)!r}"
)

if like is not None and not (isinstance(like, str) or callable(like)):
msg = f"`like` must be a string or callable, not {type(like).__name__}."
raise TypeError(msg)

new = copy(self)
new._label_params = {
"formatter": formatter,
"like": like,
"base": base,
"unit": unit,
}
return new

def _get_formatter(self, locator, formatter, like, base, unit):

# TODO this has now been copied in a few places
Expand Down Expand Up @@ -617,9 +617,26 @@ class Temporal(ContinuousBase):
_priority: ClassVar[int] = 2

def tick(
self, locator: Locator | None = None, *, upto: int | None = None,
self, locator: Locator | None = None, *,
upto: int | None = None,
) -> Temporal:
"""
Configure the selection of ticks for the scale's axis or legend.
This API is under construction and will be enhanced over time.
Parameters
----------
locator: matplotlib Locator
Pre-configured matplotlib locator; other parameters will not be used.
upto : int
Choose "nice" locations for ticks, but do not exceed this number.
Returns
-------
Copy of self with new tick configuration.
"""
if locator is not None and not isinstance(locator, Locator):
err = (
f"Tick locator must be an instance of {Locator!r}, "
Expand All @@ -631,12 +648,38 @@ def tick(
new._tick_params = {"locator": locator, "upto": upto}
return new

def label(
self,
formatter: Formatter | None = None, *,
concise: bool = False,
) -> Temporal:
"""
Configure the appearance of tick labels for the scale's axis or legend.
This API is under construction and will be enhanced over time.
Parameters
----------
formatter : matplotlib Formatter
Pre-configured formatter to use; other parameters will be ignored.
concise : bool
If True, use :class:`matplotlib.dates.ConciseDateFormatter` to make
the tick labels as compact as possible.
Returns
-------
Copy of self with new label configuration.
"""
new = copy(self)
new._label_params = {"formatter": formatter, "concise": concise}
return new

def _get_locators(self, locator, upto):

if locator is not None:
major_locator = locator
elif upto is not None:
# TODO atleast for minticks?
major_locator = AutoDateLocator(minticks=2, maxticks=upto)

else:
Expand All @@ -647,7 +690,8 @@ def _get_locators(self, locator, upto):

def _get_formatter(self, locator, formatter, concise):

# TODO handle formatter
if formatter is not None:
return formatter

if concise:
# TODO ideally we would have concise coordinate ticks,
Expand All @@ -658,14 +702,6 @@ def _get_formatter(self, locator, formatter, concise):

return formatter

def label(
self, formatter: Formatter | None = None, *, concise: bool = False,
) -> Temporal:

new = copy(self)
new._label_params = {"formatter": formatter, "concise": concise}
return new


# ----------------------------------------------------------------------------------- #

Expand Down
27 changes: 22 additions & 5 deletions tests/_core/test_scales.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,12 +585,13 @@ def test_coordinate_axis(self, t, x):
assert isinstance(locator, mpl.dates.AutoDateLocator)
assert isinstance(formatter, mpl.dates.AutoDateFormatter)

def test_label_concise(self, t, x):
def test_tick_locator(self, t):

ax = mpl.figure.Figure().subplots()
Temporal().label(concise=True)._setup(t, Coordinate(), ax.xaxis)
formatter = ax.xaxis.get_major_formatter()
assert isinstance(formatter, mpl.dates.ConciseDateFormatter)
locator = mpl.dates.YearLocator(month=3, day=15)
s = Temporal().tick(locator)
a = PseudoAxis(s._setup(t, Coordinate())._matplotlib_scale)
a.set_view_interval(0, 365)
assert 73 in a.major.locator()

def test_tick_upto(self, t, x):

Expand All @@ -599,3 +600,19 @@ def test_tick_upto(self, t, x):
Temporal().tick(upto=n)._setup(t, Coordinate(), ax.xaxis)
locator = ax.xaxis.get_major_locator()
assert set(locator.maxticks.values()) == {n}

def test_label_concise(self, t, x):

ax = mpl.figure.Figure().subplots()
Temporal().label(concise=True)._setup(t, Coordinate(), ax.xaxis)
formatter = ax.xaxis.get_major_formatter()
assert isinstance(formatter, mpl.dates.ConciseDateFormatter)

def test_label_formatter(self, t):

formatter = mpl.dates.DateFormatter("%Y")
s = Temporal().label(formatter)
a = PseudoAxis(s._setup(t, Coordinate())._matplotlib_scale)
a.set_view_interval(10, 1000)
label, = a.major.formatter.format_ticks([100])
assert label == "1970"

0 comments on commit ad693ae

Please sign in to comment.