Skip to content

Commit

Permalink
Port HueMapping tests; with datetime mapping unimplemented/untested f…
Browse files Browse the repository at this point in the history
…or now
  • Loading branch information
mwaskom committed Jun 19, 2021
1 parent 1e7cb3a commit ce17015
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 22 deletions.
7 changes: 6 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ omit =
seaborn/colors/*
seaborn/cm.py
seaborn/conftest.py
seaborn/tests/*

[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
raise NotImplementedError
25 changes: 13 additions & 12 deletions seaborn/_core/mappings.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from __future__ import annotations

from collections import abc

import numpy as np
import pandas as pd
import matplotlib as mpl
from matplotlib.colors import to_rgb

from seaborn._core.rules import VarType, variable_type, categorical_order
from seaborn.utils import get_color_cycle, remove_na
Expand Down Expand Up @@ -33,9 +32,10 @@ def __call__(self, x): # TODO types; will need to overload (wheee)
if isinstance(x, pd.Series):
if x.dtype.name == "category": # TODO! possible pandas bug
x = x.astype(object)
return x.map(self.lookup_table)
# TODO where is best place to ensure that LUT values are rgba tuples?
return np.stack(x.map(self.lookup_table).map(to_rgb))
else:
return self.lookup_table[x]
return to_rgb(self.lookup_table[x])


# TODO Currently, the SemanticMapping objects are also the source of the information
Expand All @@ -60,7 +60,7 @@ def __call__(self, x): # TODO types; will need to overload (wheee)

class GroupMapping(SemanticMapping):
"""Mapping that does not alter any visual properties of the artists."""
def setup(self, data: Series, scale: Scale | None) -> GroupMapping:
def setup(self, data: Series, scale: Scale | None = None) -> GroupMapping:
self.levels = categorical_order(data)
return self

Expand All @@ -77,7 +77,7 @@ def __init__(self, palette: PaletteSpec = None):
def setup(
self,
data: Series, # TODO generally rename Series arguments to distinguish from DF?
scale: Scale | None, # TODO or always have a Scale?
scale: Scale | None = None, # TODO or always have a Scale?
) -> HueMapping:
"""Infer the type of mapping to use and define it using this vector of data."""
palette: PaletteSpec = self._input_palette
Expand All @@ -90,6 +90,7 @@ def setup(
# e.g. specifying a numeric scale and a qualitative colormap should fail nicely.

map_type = self._infer_map_type(scale, palette, data)
assert map_type in ["numeric", "categorical", "datetime"]

# Our goal is to end up with a dictionary mapping every unique
# value in `data` to a color. We will also keep track of the
Expand Down Expand Up @@ -123,9 +124,6 @@ def setup(
list(data), palette, order,
)

else:
raise RuntimeError() # TODO should never get here ...

# TODO do we need to return and assign out here or can the
# type-specific methods do the assignment internally

Expand All @@ -151,11 +149,10 @@ def _infer_map_type(
return scale.type
elif palette in QUAL_PALETTES:
map_type = VarType("categorical")
elif isinstance(palette, (abc.Mapping, abc.Sequence)):
elif isinstance(palette, (dict, list)):
map_type = VarType("categorical")
else:
map_type = variable_type(data)

map_type = variable_type(data, boolean_type="categorical")
return map_type

def _setup_categorical(
Expand Down Expand Up @@ -212,6 +209,10 @@ def _setup_numeric(

# The presence of a norm object overrides a dictionary of hues
# in specifying a numeric mapping, so we need to process it here.
# TODO this functionality only exists to support the old relplot
# hack for linking hue orders across facets. We don't need that any
# more and should probably remove this, but needs deprecation.
# (Also what should new behavior be? I think an error probably).
levels = list(sorted(palette))
colors = [palette[k] for k in sorted(palette)]
cmap = mpl.colors.ListedColormap(colors)
Expand Down
16 changes: 12 additions & 4 deletions seaborn/_core/scales.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Callable
from pandas import Series
from matplotlib.scale import ScaleBase
from seaborn._core.typing import VariableType

Expand All @@ -19,13 +20,15 @@ class ScaleWrapper:
def __init__(
self,
scale: ScaleBase,
type: VariableType,
type: VariableType, # TODO don't use builtin name?
norm: tuple[float | None, float | None] | Normalize | None = None,
):

transform = scale.get_transform()
self.forward = transform.transform
self.reverse = transform.inverted().transform

# TODO can't we get type from the scale object in most cases?
self.type = VarType(type)

if norm is None:
Expand All @@ -47,16 +50,21 @@ def cast(self, data):


class CategoricalScale(LinearScale):
def __init__(self, axis: str, order: list | None, formatter: Any):

def __init__(
self,
axis: str | None = None,
order: list | None = None,
formatter: Any = None
):
# TODO what type is formatter?

super().__init__(axis)
self.order = order
self.formatter = formatter

def cast(self, data):
def cast(self, data: Series) -> Series:

data = pd.Series(data)
order = pd.Index(categorical_order(data, self.order))
if self.formatter is None:
order = order.astype(str)
Expand Down
1 change: 1 addition & 0 deletions seaborn/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def wide_array(wide_df):
return wide_df.to_numpy()


# TODO s/flat/thin?
@pytest.fixture
def flat_series(rng):

Expand Down
Loading

0 comments on commit ce17015

Please sign in to comment.