@@ -132,9 +132,7 @@ def _resolve_continuous_norm(values: Any, cmap_params: CmapParams) -> Normalize:
132132 if vmax is None :
133133 vmax = data_max
134134 if vmin == vmax and not isinstance (base , LogNorm ):
135- # A constant/degenerate range collapses the colormap onto its floor; fall back to the
136- # unit interval so the single value maps to a stable color. LogNorm is exempt: 0 is
137- # out of its domain.
135+ # degenerate range collapses the cmap onto its floor; fall back to [0, 1]. LogNorm exempt (0 not in domain).
138136 vmin , vmax = 0.0 , 1.0
139137 resolved = copy (base )
140138 resolved .vmin , resolved .vmax = vmin , vmax
@@ -701,20 +699,19 @@ def _set_color_source_vec(
701699
702700@dataclass (frozen = True , eq = False )
703701class ColorSpec :
704- """Resolved color for one element layer: the explicit color-state the renderers consume .
702+ """Resolved color for one element layer, with the color-state made explicit .
705703
706- ``colortype`` names the three states the renderers used to infer implicitly from
707- ``source_vector``/``categorical``: ``categorical `` (``source_vector `` is the Categorical),
708- ``continuous`` ( ``source_vector`` is None, ``color_vector`` is numeric), ``none`` (no/uncolorable
709- value -> ``color_vector`` is the na_color, ``source_vector`` is a non-None na array) .
704+ ``colortype``: ``categorical`` (``source_vector`` is the Categorical), ``continuous``
705+ ( ``source_vector`` is None, ``color_vector`` numeric), ``none `` (uncolorable -> ``color_vector `` is
706+ na_color, ``source_vector`` a non- None na array). Trap: ``source_vector is not None`` means
707+ categorical OR none, not categorical — branch on :attr:`is_categorical` .
710708 """
711709
712710 colortype : ColorType
713711 source_vector : ArrayLike | pd .Series | None
714712 color_vector : ArrayLike
715713
716- # Predicates on the invariant ``colortype`` — safe to read anywhere (unlike the vectors, which
717- # the renderers mutate after resolution). They replace scattered, typo-prone ``colortype == "..."``.
714+ # Predicates on the invariant ``colortype`` (the vectors get mutated after resolution; the type does not).
718715 @property
719716 def is_categorical (self ) -> bool :
720717 return self .colortype == "categorical"
@@ -727,8 +724,7 @@ def is_continuous(self) -> bool:
727724 def is_none (self ) -> bool :
728725 return self .colortype == "none"
729726
730- # Immutable transforms — return a new spec so the renderers can thread one ``color_spec`` through
731- # the post-resolution mutations instead of reassigning two vectors in lockstep (the sync footgun).
727+ # Immutable transforms: return a new spec so renderers thread one ``color_spec``, not two vectors in lockstep.
732728 def filter (self , mask : Any ) -> ColorSpec :
733729 """Row-subset both vectors by a boolean/index ``mask``; ``colortype`` is unchanged.
734730
@@ -753,25 +749,27 @@ def apply_transfunc(self, transfunc: Any) -> ColorSpec:
753749 def align_to_length (self , n : int , na_color : Color ) -> ColorSpec :
754750 """Pad or truncate both vectors to ``n`` rows (cross-table / rasterize outline alignment).
755751
756- Padded rows render as ``na_color``: a categorical (hex) ``color_vector`` pads with the na_color
757- hex — NaN would crash ``to_rgba_array`` — and ``source_vector`` with an absent category; a
758- continuous vector pads with NaN so the cmap maps it to na_color .
752+ Padded rows render as ``na_color``: continuous pads with NaN; categorical/none pad the hex
753+ ``color_vector`` with the na_color hex ( NaN would crash ``to_rgba_array``) and the
754+ ``source_vector`` with an absent category / na entry .
759755 """
760756 vec = self .color_vector
761757 if vec is None or len (vec ) == n :
762758 return self
763759 if len (vec ) > n :
764760 source = None if self .source_vector is None else self .source_vector [:n ]
765- return replace (self , source_vector = source , color_vector = np . asarray ( vec ) [:n ])
761+ return replace (self , source_vector = source , color_vector = vec [:n ])
766762 pad = n - len (vec )
767- if self .source_vector is not None :
768- padded = np .concatenate (
769- [np .asarray (vec , dtype = object ), np .full (pad , na_color .get_hex_with_alpha (), dtype = object )]
770- )
763+ if self .is_continuous :
764+ padded = np .concatenate ([np .asarray (vec , dtype = float ), np .full (pad , np .nan )])
765+ return replace (self , color_vector = padded )
766+ na_hex = na_color .get_hex_with_alpha ()
767+ padded = np .concatenate ([np .asarray (vec , dtype = object ), np .full (pad , na_hex , dtype = object )])
768+ if self .is_categorical :
771769 source = pd .Categorical (list (self .source_vector ) + [None ] * pad , categories = self .source_vector .categories )
772- return replace ( self , source_vector = source , color_vector = padded )
773- padded = np .concatenate ([np .asarray (vec , dtype = float ), np .full (pad , np . nan )])
774- return replace (self , color_vector = padded )
770+ else : # none: source is an na array, not a Categorical — extend it with na entries
771+ source = np .concatenate ([np .asarray (self . source_vector , dtype = object ), np .full (pad , na_hex , dtype = object )])
772+ return replace (self , source_vector = source , color_vector = padded )
775773
776774
777775def resolve_color (* args : Any , ** kwargs : Any ) -> ColorSpec :
0 commit comments