Repository: mwaskom/seaborn

Issue:
next gen version (0.12.0a1) bug: StopIteration when doing relplot with kind='box'
version: 0.12.0a1
The following code will produce StopIteration error, but works fine in stable version (e.g., 0.11.2)

```python
import seaborn as sns
import pandas as pd
tips = sns.load_dataset('tips')
sns.catplot(data=tips, y='tip', kind='box') # error in version: 0.12.0a1, but ok in 0.11.2
```
btw, really thank you for your hard work in creating next gen seaborn! I can see this version has lots of potential! Really love your work!

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2839?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) Report
> Merging [#2839](https://codecov.io/gh/mwaskom/seaborn/pull/2839?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) (bd77ba6) into [master](https://codecov.io/gh/mwaskom/seaborn/commit/e2cae5211192a1a8108f5cf4f4035abe0da8e74b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) (e2cae52) will **decrease** coverage by `4.12%`.
> The diff coverage is `92.75%`.

```diff
@@            Coverage Diff             @@
##           master    #2839      +/-   ##
==========================================
- Coverage   98.05%   93.92%   -4.13%     
==========================================
  Files          68       69       +1     
  Lines       22066    22178     +112     
==========================================
- Hits        21637    20831     -806     
- Misses        429     1347     +918     
```


| [Impacted Files](https://codecov.io/gh/mwaskom/seaborn/pull/2839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | Coverage Δ | |
|---|---|---|
| [tests/\_core/test\_data.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9kYXRhLnB5) | `100.00% <ø> (ø)` | |
| [tests/\_core/test\_groupby.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9ncm91cGJ5LnB5) | `100.00% <ø> (ø)` | |
| [tests/\_core/test\_moves.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9tb3Zlcy5weQ==) | `100.00% <ø> (ø)` | |
| [tests/\_core/test\_plot.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9wbG90LnB5) | `98.11% <ø> (ø)` | |
| [tests/\_core/test\_properties.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9wcm9wZXJ0aWVzLnB5) | `98.91% <ø> (ø)` | |
| [tests/\_core/test\_rules.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9ydWxlcy5weQ==) | `100.00% <ø> (ø)` | |
| [tests/\_core/test\_scales.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9zY2FsZXMucHk=) | `100.00% <ø> (ø)` | |
| [tests/\_core/test\_subplots.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX2NvcmUvdGVzdF9zdWJwbG90cy5weQ==) | `100.00% <ø> (ø)` | |
| [tests/\_marks/test\_area.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX21hcmtzL3Rlc3RfYXJlYS5weQ==) | `100.00% <ø> (ø)` | |
| [tests/\_marks/test\_bars.py](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvX21hcmtzL3Rlc3RfYmFycy5weQ==) | `87.30% <ø> (ø)` | |
| ... and [46 more](https://codecov.io/gh/mwaskom/seaborn/pull/2839/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2839?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2839?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom). Last update [627c6c2...bd77ba6](https://codecov.io/gh/mwaskom/seaborn/pull/2839?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom).


Patch:
diff --git a/seaborn/_core/plot.py b/seaborn/_core/plot.py
index 56d933cb..2469df00 100644
--- a/seaborn/_core/plot.py
+++ b/seaborn/_core/plot.py
@@ -55,6 +55,7 @@ class Layer(TypedDict, total=False):
     source: DataSource
     vars: dict[str, VariableSpec]
     orient: str
+    legend: bool
 
 
 class FacetSpec(TypedDict, total=False):
@@ -297,9 +298,10 @@ class Plot:
         self,
         mark: Mark,
         stat: Stat | None = None,
-        move: Move | None = None,  # TODO or list[Move]
+        move: Move | list[Move] | None = None,
         *,
         orient: str | None = None,
+        legend: bool = True,
         data: DataSource = None,
         **variables: VariableSpec,
     ) -> Plot:
@@ -318,6 +320,8 @@ class Plot:
             A transformation applied to the data before plotting.
         move : :class:`seaborn.objects.Move`
             Additional transformation(s) to handle over-plotting.
+        legend : bool
+            Option to suppress the mark/mappings for this layer from the legend.
         orient : "x", "y", "v", or "h"
             The orientation of the mark, which affects how the stat is computed.
             Typically corresponds to the axis that defines groups for aggregation.
@@ -356,6 +360,7 @@ class Plot:
             "move": move,
             "vars": variables,
             "source": data,
+            "legend": legend,
             "orient": {"v": "x", "h": "y"}.get(orient, orient),  # type: ignore
         })
 
@@ -1140,7 +1145,8 @@ class Plotter:
         for view in self._subplots:
             view["ax"].autoscale_view()
 
-        self._update_legend_contents(mark, data, scales)
+        if layer["legend"]:
+            self._update_legend_contents(mark, data, scales)
 
     def _scale_coords(self, subplots: list[dict], df: DataFrame) -> DataFrame:
         # TODO stricter type on subplots
diff --git a/seaborn/categorical.py b/seaborn/categorical.py
index ee8d3c22..335674b5 100644
--- a/seaborn/categorical.py
+++ b/seaborn/categorical.py
@@ -3605,9 +3605,7 @@ def catplot(
         warnings.warn(msg, UserWarning)
         kwargs.pop("ax")
 
-    refactored_kinds = [
-        "strip", "swarm",
-    ]
+    refactored_kinds = ["strip", "swarm"]
     if kind in refactored_kinds:
 
         p = _CategoricalFacetPlotter(
@@ -3785,17 +3783,8 @@ def catplot(
     # so we need to define ``palette`` to get default behavior for the
     # categorical functions
     p.establish_colors(color, palette, 1)
-    if (
-        (kind != "point" or hue is not None)
-        # XXX changing this to temporarily support bad sharex=False behavior where
-        # cat variables could take different colors, which we already warned
-        # about "breaking" (aka fixing) in the future
-        and ((sharex and p.orient == "v") or (sharey and p.orient == "h"))
-    ):
-        if p.hue_names is None:
-            palette = dict(zip(p.group_names, p.colors))
-        else:
-            palette = dict(zip(p.hue_names, p.colors))
+    if kind != "point" or hue is not None:
+        palette = p.colors
 
     # Determine keyword arguments for the facets
     facet_kws = {} if facet_kws is None else facet_kws


Test patch:
diff --git a/tests/_core/test_plot.py b/tests/_core/test_plot.py
index 5fbf2eba..b07922d6 100644
--- a/tests/_core/test_plot.py
+++ b/tests/_core/test_plot.py
@@ -1718,3 +1718,9 @@ class TestLegend:
         s = pd.Series(["r", "g", "b", "g"])
         p = Plot(**xy).add(MockMark(), color=s).scale(color=None).plot()
         assert not p._legend_contents
+
+    def test_suppression_in_add_method(self, xy):
+
+        s = pd.Series(["a", "b", "a", "c"], name="s")
+        p = Plot(**xy).add(MockMark(), color=s, legend=False).plot()
+        assert not p._legend_contents
diff --git a/tests/test_categorical.py b/tests/test_categorical.py
index 401d4c76..7eee35ff 100644
--- a/tests/test_categorical.py
+++ b/tests/test_categorical.py
@@ -2788,6 +2788,10 @@ class TestCatPlot(CategoricalFixture):
         assert len(g.ax.patches) == want_elements
         assert len(g.ax.lines) == 0
 
+        g = cat.catplot(y="y", data=self.df, kind="box")
+        want_artists = 1
+        assert len(self.get_box_artists(g.ax)) == want_artists
+
         g = cat.catplot(x="g", y="y", data=self.df, kind="box")
         want_artists = self.g.unique().size
         assert len(self.get_box_artists(g.ax)) == want_artists