Skip to content

Commit

Permalink
Begin exposing order in map methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Waskom committed Sep 26, 2021
1 parent 708391b commit a8194b4
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 17 deletions.
33 changes: 25 additions & 8 deletions seaborn/_core/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from pandas import Series
from matplotlib.colors import Colormap, Normalize
from matplotlib.scale import Scale
from seaborn._core.typing import PaletteSpec
from seaborn._core.typing import PaletteSpec, OrderSpec

DashPattern = Tuple[float, ...]
DashPatternWithOffset = Tuple[float, Optional[DashPattern]]
Expand Down Expand Up @@ -51,7 +51,13 @@ def setup(
) -> LookupMapping:

provided = self._provided
order = None if scale is None else scale.order
if self.order is not None:
order = self.order
elif scale is not None:
order = scale.order
else:
order = None

levels = categorical_order(data, order)

if provided is None:
Expand Down Expand Up @@ -139,9 +145,10 @@ class FillSemantic(BinarySemantic):

class ColorSemantic(Semantic):

def __init__(self, palette: PaletteSpec = None):
def __init__(self, palette: PaletteSpec = None, order: OrderSpec = None):

self._palette = palette
self.order = order

def __call__(self, x): # TODO types; will need to overload

Expand Down Expand Up @@ -181,8 +188,15 @@ def setup(
palette: PaletteSpec = self._palette
cmap: Colormap | None = None

# TODO allow configuration of norm in mapping methods like we do with order?
norm = None if scale is None else scale.norm
order = None if scale is None else scale.order

if self.order is not None:
order = self.order
elif scale is not None:
order = scale.order
else:
order = None

# TODO We need to add some input checks ...
# e.g. specifying a numeric scale and a qualitative colormap should fail nicely.
Expand All @@ -198,7 +212,7 @@ def setup(
# return LookupMapping(mapping)
# TODO hack to keep things runnable until downstream refactor
mapping = LookupMapping(mapping)
mapping.levels = levels
mapping.order = levels # TODO
return mapping

elif map_type == "numeric":
Expand Down Expand Up @@ -282,7 +296,8 @@ class MarkerSemantic(DiscreteSemantic):
# TODO This may have to be a parameters? (e.g. for color/edgecolor)
_semantic = "marker"

def __init__(self, shapes: list | dict | None = None): # TODO full types
# TODO full types
def __init__(self, shapes: list | dict | None = None, order: OrderSpec = None):

# TODO fill or filled parameter?
# allow singletons? e.g. map_marker(shapes="o", filled=[True, False])?
Expand All @@ -294,6 +309,7 @@ def __init__(self, shapes: list | dict | None = None): # TODO full types
shapes = {k: MarkerStyle(v) for k, v in shapes.items()}

self._provided = shapes
self.order = order

def _default_values(self, n): # TODO or have this as an infinite generator?
"""Build an arbitrarily long list of unique marker styles for points.
Expand Down Expand Up @@ -345,7 +361,7 @@ class DashSemantic(DiscreteSemantic):

_semantic = "dash pattern"

def __init__(self, styles: list | dict | None = None): # TODO full types
def __init__(self, styles: list | dict | None = None, order: OrderSpec = None): # TODO full types

# TODO fill or filled parameter?
# allow singletons? e.g. map_marker(shapes="o", filled=[True, False])?
Expand All @@ -357,6 +373,7 @@ def __init__(self, styles: list | dict | None = None): # TODO full types
styles = {k: self._get_dash_pattern(v) for k, v in styles.items()}

self._provided = styles
self.order = order

def _default_values(self, n: int) -> list[DashPatternWithOffset]:
"""Build an arbitrarily long list of unique dash styles for lines.
Expand Down Expand Up @@ -526,7 +543,7 @@ def __call__(self, x): # TODO types; will need to overload (wheee)
class GroupMapping(SemanticMapping): # TODO only needed for levels...
"""Mapping that does not alter any visual properties of the artists."""
def setup(self, data: Series, scale: Scale | None = None) -> GroupMapping:
self.levels = categorical_order(data)
self.order = categorical_order(data)
return self


Expand Down
21 changes: 13 additions & 8 deletions seaborn/_core/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,46 +251,51 @@ def map_color(
self,
# TODO accept variable specification here?
palette: PaletteSpec = None,
order: OrderSpec = None,
) -> Plot:

# TODO we do some fancy business currently to avoid having to
# write these ... do we want that to persist or is it too confusing?
# If we do ... maybe we don't even need to write these methods, but can
# instead programatically add them based on central dict of mapping objects.
# ALSO TODO should these be initialized with defaults?
self._semantics["color"] = ColorSemantic(palette)
self._semantics["color"] = ColorSemantic(palette, order)
return self

def map_facecolor(
self,
palette: PaletteSpec = None,
order: OrderSpec = None,
) -> Plot:

self._semantics["facecolor"] = ColorSemantic(palette)
self._semantics["facecolor"] = ColorSemantic(palette, order)
return self

def map_edgecolor(
self,
palette: PaletteSpec = None,
order: OrderSpec = None,
) -> Plot:

self._semantics["edgecolor"] = ColorSemantic(palette)
self._semantics["edgecolor"] = ColorSemantic(palette, order)
return self

def map_marker(
self,
shapes: list | dict | None = None,
order: OrderSpec = None,
) -> Plot:

self._semantics["marker"] = MarkerSemantic(shapes)
self._semantics["marker"] = MarkerSemantic(shapes, order)
return self

def map_dash(
self,
styles: list | dict | None = None,
order: OrderSpec = None,
) -> Plot:

self._semantics["dash"] = DashSemantic(styles)
self._semantics["dash"] = DashSemantic(styles, order)
return self

# TODO have map_gradient?
Expand Down Expand Up @@ -531,10 +536,10 @@ def _setup_orderings(self) -> None:

for var in variables:
# TODO should the order be a property of the Semantic or the Mapping?
if var in self._mappings and self._mappings[var].levels is not None:
if var in self._mappings and self._mappings[var].order is not None:
# orderings[var] = self._mappings[var].order
# TODO FIXME:mappings mapping should always have order (can be None)
orderings[var] = self._mappings[var].levels
orderings[var] = self._mappings[var].order
elif self._scales[var].order is not None:
orderings[var] = self._scales[var].order

Expand Down Expand Up @@ -853,7 +858,7 @@ def _setup_split_generator(

allow_empty = False # TODO will need to recreate previous categorical plots

levels = {v: m.levels for v, m in mappings.items()}
levels = {v: m.order for v, m in mappings.items()}
grouping_vars = [
var for var in grouping_vars if var in df and var not in ["col", "row"]
]
Expand Down
2 changes: 1 addition & 1 deletion seaborn/_core/scales.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ScaleWrapper:
def __init__(
self,
scale: ScaleBase,
type: VariableType | None = None, # TODO don't use builtin name?
type: VariableType, # TODO don't use builtin name?
norm: tuple[float | None, float | None] | Normalize | None = None,
):

Expand Down

0 comments on commit a8194b4

Please sign in to comment.