Repository: matplotlib/matplotlib

Issue:
boxplot/violinplot orientation-setting API
<!--To help us understand and resolve your issue, please fill out the form to the best of your ability.-->
<!--You can feel free to delete the sections that do not apply.-->

### Bug report

**Bug summary**

Currently, boxplot(), bxp(), violin(), and violinplot() take a `vert: bool` argument to switch between vertical and horizontal; this is inconsistent with colorbar(), hist(), eventplot(), and Slider(), which take an `orientation: {"vertical", "horizontal"}` argument.

For consistency, I'd suggest adding support for `orientation` (both because I think colorbar() and hist(), in particular, are probably the most used among all these, and because I like the API better :-)), and later deprecate `vert`.  Thoughts?

attn @phobson who has been involved in boxplots, IIRC.


Discussion:
I think this is a good idea.

Should be easy enough to capture `vert`, emit and deprecation warning, and then set `orientation` accordingly.
(and while we're at it, the manage_xticks arg should be renamed to manage_ticks, as it covers both orientations)
just throwing this out there: what would you say to `manage_labels`?
well technically it's managing ticks, tick labels, *and axis limits* so I think manage_labels is worse than manage_ticks, and perhaps manage_axis is better?
I also wonder whether it really needs to manage axis limits -- can't the standard margins system do that just as well?  (needs to be investigated)
good points all around.

just noticed a wrinkle: `boxplot.vert` is an rcParam
We know how to handle deprecation/renaming of rcParams too, but thanks for pointing that out.
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear.  Thanks for your help!
At least adding orientation should hopefully be uncontroversial.
Would you want separate PRs for `boxplot` and `violinplot` to keep it simple? The review process for the `boxplot` section will probably take a bit longer because it involves the deprecation of an rcParam which Ive never done before.
Should we simply deprecate the `boxplot.vert` rcParams without replacement? This is more of a plot-type setting rather than style. It’s not useful as a global config or style parameter. The only use case would be a local context (e.g. via a context manager) if somebody wants to make multiple horizontal plots. But for that rare case using the parameter explicitly may be good enough and is more clear for the reader.

I believe, we also don’t have that config for `bar()` or `hist()`.



Patch:
diff --git a/doc/api/next_api_changes/deprecations/27998-TS.rst b/doc/api/next_api_changes/deprecations/27998-TS.rst
new file mode 100644
index 000000000000..ab69b87f0989
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/27998-TS.rst
@@ -0,0 +1,7 @@
+``violinplot`` and ``violin`` *vert* parameter
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The parameter *vert: bool* has been deprecated on `~.Axes.violinplot` and
+`~.Axes.violin`.
+It will be replaced by *orientation: {"vertical", "horizontal"}* for API
+consistency.
diff --git a/doc/users/next_whats_new/violinplot_orientation.rst b/doc/users/next_whats_new/violinplot_orientation.rst
new file mode 100644
index 000000000000..23d81446ad35
--- /dev/null
+++ b/doc/users/next_whats_new/violinplot_orientation.rst
@@ -0,0 +1,21 @@
+``violinplot`` and ``violin`` orientation parameter
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Violinplots have a new parameter *orientation: {"vertical", "horizontal"}*
+to change the orientation of the plot. This will replace the deprecated
+*vert: bool* parameter.
+
+
+.. plot::
+    :include-source: true
+    :alt: Example of creating 4 horizontal violinplots.
+
+    import matplotlib.pyplot as plt
+    import numpy as np
+
+    fig, ax = plt.subplots()
+    np.random.seed(19680801)
+    all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
+
+    ax.violinplot(all_data, orientation='horizontal')
+    plt.show()
diff --git a/galleries/examples/statistics/violinplot.py b/galleries/examples/statistics/violinplot.py
index afcc1c977034..5d1701d865c3 100644
--- a/galleries/examples/statistics/violinplot.py
+++ b/galleries/examples/statistics/violinplot.py
@@ -62,37 +62,37 @@
                      quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='high')
 axs[0, 5].set_title('Custom violin 6', fontsize=fs)
 
-axs[1, 0].violinplot(data, pos, points=80, vert=False, widths=0.7,
+axs[1, 0].violinplot(data, pos, points=80, orientation='horizontal', widths=0.7,
                      showmeans=True, showextrema=True, showmedians=True)
 axs[1, 0].set_title('Custom violin 7', fontsize=fs)
 
-axs[1, 1].violinplot(data, pos, points=100, vert=False, widths=0.9,
+axs[1, 1].violinplot(data, pos, points=100, orientation='horizontal', widths=0.9,
                      showmeans=True, showextrema=True, showmedians=True,
                      bw_method='silverman')
 axs[1, 1].set_title('Custom violin 8', fontsize=fs)
 
-axs[1, 2].violinplot(data, pos, points=200, vert=False, widths=1.1,
+axs[1, 2].violinplot(data, pos, points=200, orientation='horizontal', widths=1.1,
                      showmeans=True, showextrema=True, showmedians=True,
                      bw_method=0.5)
 axs[1, 2].set_title('Custom violin 9', fontsize=fs)
 
-axs[1, 3].violinplot(data, pos, points=200, vert=False, widths=1.1,
+axs[1, 3].violinplot(data, pos, points=200, orientation='horizontal', widths=1.1,
                      showmeans=True, showextrema=True, showmedians=True,
                      quantiles=[[0.1], [], [], [0.175, 0.954], [0.75], [0.25]],
                      bw_method=0.5)
 axs[1, 3].set_title('Custom violin 10', fontsize=fs)
 
-axs[1, 4].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
-                     showmeans=True, showextrema=True, showmedians=True,
+axs[1, 4].violinplot(data[-1:], pos[-1:], points=200, orientation='horizontal',
+                     widths=1.1, showmeans=True, showextrema=True, showmedians=True,
                      quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5)
 axs[1, 4].set_title('Custom violin 11', fontsize=fs)
 
-axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
-                     showmeans=True, showextrema=True, showmedians=True,
+axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, orientation='horizontal',
+                     widths=1.1, showmeans=True, showextrema=True, showmedians=True,
                      quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='low')
 
-axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
-                     showmeans=True, showextrema=True, showmedians=True,
+axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, orientation='horizontal',
+                     widths=1.1, showmeans=True, showextrema=True, showmedians=True,
                      quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='high')
 axs[1, 5].set_title('Custom violin 12', fontsize=fs)
 
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index 26a3a580ba3e..a6e6bc9780b7 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -8350,9 +8350,10 @@ def matshow(self, Z, **kwargs):
 
     @_api.make_keyword_only("3.9", "vert")
     @_preprocess_data(replace_names=["dataset"])
-    def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
-                   showmeans=False, showextrema=True, showmedians=False,
-                   quantiles=None, points=100, bw_method=None, side='both'):
+    def violinplot(self, dataset, positions=None, vert=None,
+                   orientation='vertical', widths=0.5, showmeans=False,
+                   showextrema=True, showmedians=False, quantiles=None,
+                   points=100, bw_method=None, side='both',):
         """
         Make a violin plot.
 
@@ -8370,9 +8371,21 @@ def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
             The positions of the violins; i.e. coordinates on the x-axis for
             vertical violins (or y-axis for horizontal violins).
 
-        vert : bool, default: True.
-            If true, creates a vertical violin plot.
-            Otherwise, creates a horizontal violin plot.
+        vert : bool, optional
+            .. deprecated:: 3.10
+                Use *orientation* instead.
+
+                If this is given during the deprecation period, it overrides
+                the *orientation* parameter.
+
+            If True, plots the violins vertically.
+            If False, plots the violins horizontally.
+
+        orientation : {'vertical', 'horizontal'}, default: 'vertical'
+            If 'horizontal', plots the violins horizontally.
+            Otherwise, plots the violins vertically.
+
+            .. versionadded:: 3.10
 
         widths : float or array-like, default: 0.5
             The maximum width of each violin in units of the *positions* axis.
@@ -8457,12 +8470,14 @@ def _kde_method(X, coords):
         vpstats = cbook.violin_stats(dataset, _kde_method, points=points,
                                      quantiles=quantiles)
         return self.violin(vpstats, positions=positions, vert=vert,
-                           widths=widths, showmeans=showmeans,
-                           showextrema=showextrema, showmedians=showmedians, side=side)
+                           orientation=orientation, widths=widths,
+                           showmeans=showmeans, showextrema=showextrema,
+                           showmedians=showmedians, side=side)
 
     @_api.make_keyword_only("3.9", "vert")
-    def violin(self, vpstats, positions=None, vert=True, widths=0.5,
-               showmeans=False, showextrema=True, showmedians=False, side='both'):
+    def violin(self, vpstats, positions=None, vert=None,
+               orientation='vertical', widths=0.5, showmeans=False,
+               showextrema=True, showmedians=False, side='both'):
         """
         Draw a violin plot from pre-computed statistics.
 
@@ -8500,9 +8515,21 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
             The positions of the violins; i.e. coordinates on the x-axis for
             vertical violins (or y-axis for horizontal violins).
 
-        vert : bool, default: True.
-            If true, plots the violins vertically.
-            Otherwise, plots the violins horizontally.
+        vert : bool, optional
+            .. deprecated:: 3.10
+                Use *orientation* instead.
+
+                If this is given during the deprecation period, it overrides
+                the *orientation* parameter.
+
+            If True, plots the violins vertically.
+            If False, plots the violins horizontally.
+
+        orientation : {'vertical', 'horizontal'}, default: 'vertical'
+            If 'horizontal', plots the violins horizontally.
+            Otherwise, plots the violins vertically.
+
+            .. versionadded:: 3.10
 
         widths : float or array-like, default: 0.5
             The maximum width of each violin in units of the *positions* axis.
@@ -8572,6 +8599,18 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
         datashape_message = ("List of violinplot statistics and `{0}` "
                              "values must have the same length")
 
+        # vert and orientation parameters are linked until vert's
+        # deprecation period expires. If both are selected,
+        # vert takes precedence.
+        if vert is not None:
+            _api.warn_deprecated(
+                "3.10",
+                name="vert: bool",
+                alternative="orientation: {'vertical', 'horizontal'}"
+                )
+            orientation = 'vertical' if vert else 'horizontal'
+        _api.check_in_list(['horizontal', 'vertical'], orientation=orientation)
+
         # Validate positions
         if positions is None:
             positions = range(1, N + 1)
@@ -8600,7 +8639,7 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
             fillcolor = linecolor = self._get_lines.get_next_color()
 
         # Check whether we are rendering vertically or horizontally
-        if vert:
+        if orientation == 'vertical':
             fill = self.fill_betweenx
             if side in ['low', 'high']:
                 perp_lines = functools.partial(self.hlines, colors=linecolor,
diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi
index be0a0e48d662..ac407270bff3 100644
--- a/lib/matplotlib/axes/_axes.pyi
+++ b/lib/matplotlib/axes/_axes.pyi
@@ -739,7 +739,8 @@ class Axes(_AxesBase):
         dataset: ArrayLike | Sequence[ArrayLike],
         positions: ArrayLike | None = ...,
         *,
-        vert: bool = ...,
+        vert: bool | None = ...,
+        orientation: Literal["vertical", "horizontal"] = ...,
         widths: float | ArrayLike = ...,
         showmeans: bool = ...,
         showextrema: bool = ...,
@@ -758,7 +759,8 @@ class Axes(_AxesBase):
         vpstats: Sequence[dict[str, Any]],
         positions: ArrayLike | None = ...,
         *,
-        vert: bool = ...,
+        vert: bool | None = ...,
+        orientation: Literal["vertical", "horizontal"] = ...,
         widths: float | ArrayLike = ...,
         showmeans: bool = ...,
         showextrema: bool = ...,
diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py
index 2376c6243929..f10273495b51 100644
--- a/lib/matplotlib/pyplot.py
+++ b/lib/matplotlib/pyplot.py
@@ -4142,7 +4142,8 @@ def triplot(*args, **kwargs):
 def violinplot(
     dataset: ArrayLike | Sequence[ArrayLike],
     positions: ArrayLike | None = None,
-    vert: bool = True,
+    vert: bool | None = None,
+    orientation: Literal["vertical", "horizontal"] = "vertical",
     widths: float | ArrayLike = 0.5,
     showmeans: bool = False,
     showextrema: bool = True,
@@ -4161,6 +4162,7 @@ def violinplot(
         dataset,
         positions=positions,
         vert=vert,
+        orientation=orientation,
         widths=widths,
         showmeans=showmeans,
         showextrema=showextrema,


Test patch:
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index 3644f0861d1b..2c2b354cd71c 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -3747,7 +3747,7 @@ def test_horiz_violinplot_baseline():
     # First 9 digits of frac(sqrt(19))
     np.random.seed(358898943)
     data = [np.random.normal(size=100) for _ in range(4)]
-    ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
+    ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
                   showextrema=False, showmedians=False)
 
 
@@ -3757,7 +3757,7 @@ def test_horiz_violinplot_showmedians():
     # First 9 digits of frac(sqrt(23))
     np.random.seed(795831523)
     data = [np.random.normal(size=100) for _ in range(4)]
-    ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
+    ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
                   showextrema=False, showmedians=True)
 
 
@@ -3767,7 +3767,7 @@ def test_horiz_violinplot_showmeans():
     # First 9 digits of frac(sqrt(29))
     np.random.seed(385164807)
     data = [np.random.normal(size=100) for _ in range(4)]
-    ax.violinplot(data, positions=range(4), vert=False, showmeans=True,
+    ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=True,
                   showextrema=False, showmedians=False)
 
 
@@ -3777,7 +3777,7 @@ def test_horiz_violinplot_showextrema():
     # First 9 digits of frac(sqrt(31))
     np.random.seed(567764362)
     data = [np.random.normal(size=100) for _ in range(4)]
-    ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
+    ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
                   showextrema=True, showmedians=False)
 
 
@@ -3787,7 +3787,7 @@ def test_horiz_violinplot_showall():
     # First 9 digits of frac(sqrt(37))
     np.random.seed(82762530)
     data = [np.random.normal(size=100) for _ in range(4)]
-    ax.violinplot(data, positions=range(4), vert=False, showmeans=True,
+    ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=True,
                   showextrema=True, showmedians=True,
                   quantiles=[[0.1, 0.9], [0.2, 0.8], [0.3, 0.7], [0.4, 0.6]])
 
@@ -3798,7 +3798,7 @@ def test_horiz_violinplot_custompoints_10():
     # First 9 digits of frac(sqrt(41))
     np.random.seed(403124237)
     data = [np.random.normal(size=100) for _ in range(4)]
-    ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
+    ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
                   showextrema=False, showmedians=False, points=10)
 
 
@@ -3808,7 +3808,7 @@ def test_horiz_violinplot_custompoints_200():
     # First 9 digits of frac(sqrt(43))
     np.random.seed(557438524)
     data = [np.random.normal(size=100) for _ in range(4)]
-    ax.violinplot(data, positions=range(4), vert=False, showmeans=False,
+    ax.violinplot(data, positions=range(4), orientation='horizontal', showmeans=False,
                   showextrema=False, showmedians=False, points=200)
 
 
@@ -3819,11 +3819,11 @@ def test_violinplot_sides():
     data = [np.random.normal(size=100)]
     # Check horizontal violinplot
     for pos, side in zip([0, -0.5, 0.5], ['both', 'low', 'high']):
-        ax.violinplot(data, positions=[pos], vert=False, showmeans=False,
+        ax.violinplot(data, positions=[pos], orientation='horizontal', showmeans=False,
                       showextrema=True, showmedians=True, side=side)
     # Check vertical violinplot
     for pos, side in zip([4, 3.5, 4.5], ['both', 'low', 'high']):
-        ax.violinplot(data, positions=[pos], vert=True, showmeans=False,
+        ax.violinplot(data, positions=[pos], orientation='vertical', showmeans=False,
                       showextrema=True, showmedians=True, side=side)
 
 
@@ -9034,3 +9034,40 @@ def test_latex_pie_percent(fig_test, fig_ref):
 
     ax1 = fig_ref.subplots()
     ax1.pie(data, autopct=r"%1.0f\%%", textprops={'usetex': True})
+
+
+@check_figures_equal(extensions=['png'])
+def test_violinplot_orientation(fig_test, fig_ref):
+    # Test the `orientation : {'vertical', 'horizontal'}`
+    # parameter and deprecation of `vert: bool`.
+    fig, axs = plt.subplots(nrows=1, ncols=3)
+    np.random.seed(19680801)
+    all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
+
+    axs[0].violinplot(all_data)  # Default vertical plot.
+    # xticks and yticks should be at their default position.
+    assert all(axs[0].get_xticks() == np.array(
+        [0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5]))
+    assert all(axs[0].get_yticks() == np.array(
+        [-30., -20., -10., 0., 10., 20., 30.]))
+
+    # Horizontal plot using new `orientation` keyword.
+    axs[1].violinplot(all_data, orientation='horizontal')
+    # xticks and yticks should be swapped.
+    assert all(axs[1].get_xticks() == np.array(
+        [-30., -20., -10., 0., 10., 20., 30.]))
+    assert all(axs[1].get_yticks() == np.array(
+        [0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5]))
+
+    plt.close()
+
+    # Deprecation of `vert: bool` keyword
+    with pytest.warns(mpl.MatplotlibDeprecationWarning,
+                      match='vert: bool was deprecated in Matplotlib 3.10'):
+        # Compare images between a figure that
+        # uses vert and one that uses orientation.
+        ax_ref = fig_ref.subplots()
+        ax_ref.violinplot(all_data, vert=False)
+
+        ax_test = fig_test.subplots()
+        ax_test.violinplot(all_data, orientation='horizontal')