diff --git a/doc/api/next_api_changes/behavior/23233-TH.rst b/doc/api/next_api_changes/behavior/23233-TH.rst new file mode 100644 index 000000000000..8cf50113adf5 --- /dev/null +++ b/doc/api/next_api_changes/behavior/23233-TH.rst @@ -0,0 +1,13 @@ +``stem(..., markerfmt=...)`` behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The behavior of the *markerfmt* parameter of `~.Axes.stem` has changed: + +- If *markerfmt* does not contain a color, the color is taken from *linefmt*. +- If *markerfmt* does not contain a marker, the default is 'o'. + +Before, *markerfmt* was passed unmodified to ``plot(..., fmt)``, which had +a number of unintended side-effects; e.g. only giving a color switched to +a solid line without markers. + +For a simple call ``stem(x, y)`` without parameters, the new rules still +reproduce the old behavior. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2ec7cc8d8f6a..583115e98d5a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2821,6 +2821,8 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, The *locs*-positions are optional. The formats may be provided either as positional or as keyword-arguments. + Passing *markerfmt* and *basefmt* positionally is deprecated since + Matplotlib 3.5. Parameters ---------- @@ -2853,8 +2855,8 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, markerfmt : str, optional A string defining the color and/or shape of the markers at the stem - heads. Default: 'C0o', i.e. filled circles with the first color of - the color cycle. + heads. If the marker is not given, use the marker 'o', i.e. filled + circles. If the color is not given, use the color from *linefmt*. basefmt : str, default: 'C3-' ('C2-' in classic mode) A format string defining the properties of the baseline. @@ -2918,16 +2920,27 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, else: # horizontal heads, locs = self._process_unit_info([("x", heads), ("y", locs)]) - # defaults for formats + # resolve line format if linefmt is None: linefmt = args[0] if len(args) > 0 else "C0-" linestyle, linemarker, linecolor = _process_plot_format(linefmt) + # resolve marker format if markerfmt is None: - markerfmt = args[1] if len(args) > 1 else "C0o" + # if not given as kwarg, check for positional or fall back to 'o' + markerfmt = args[1] if len(args) > 1 else "o" + if markerfmt == '': + markerfmt = ' ' # = empty line style; '' would resolve rcParams markerstyle, markermarker, markercolor = \ _process_plot_format(markerfmt) - + if markermarker is None: + markermarker = 'o' + if markerstyle is None: + markerstyle = 'None' + if markercolor is None: + markercolor = linecolor + + # resolve baseline format if basefmt is None: basefmt = (args[2] if len(args) > 2 else "C2-" if rcParams["_internal.classic_mode"] else "C3-") diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 5cb840685c30..c40f4bd32fd6 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3833,6 +3833,7 @@ def test_stem(use_line_collection): def test_stem_args(): + """Test that stem() correctly identifies x and y values.""" def _assert_equal(stem_container, expected): x, y = map(list, stem_container.markerline.get_data()) assert x == expected[0] @@ -3853,6 +3854,71 @@ def _assert_equal(stem_container, expected): _assert_equal(ax.stem(y, 'r--'), expected=([0, 1, 2], y)) +def test_stem_markerfmt(): + """Test that stem(..., markerfmt=...) produces the intended markers.""" + def _assert_equal(stem_container, linecolor=None, markercolor=None, + marker=None): + """ + Check that the given StemContainer has the properties listed as + keyword-arguments. + """ + if linecolor is not None: + assert mcolors.same_color( + stem_container.stemlines.get_color(), + linecolor) + if markercolor is not None: + assert mcolors.same_color( + stem_container.markerline.get_color(), + markercolor) + if marker is not None: + assert stem_container.markerline.get_marker() == marker + assert stem_container.markerline.get_linestyle() == 'None' + + fig, ax = plt.subplots() + + x = [1, 3, 5] + y = [9, 8, 7] + + # no linefmt + _assert_equal(ax.stem(x, y), markercolor='C0', marker='o') + _assert_equal(ax.stem(x, y, markerfmt='x'), markercolor='C0', marker='x') + _assert_equal(ax.stem(x, y, markerfmt='rx'), markercolor='r', marker='x') + + # positional linefmt + _assert_equal( + ax.stem(x, y, 'r'), # marker color follows linefmt if not given + linecolor='r', markercolor='r', marker='o') + _assert_equal( + ax.stem(x, y, 'rx'), # the marker is currently not taken from linefmt + linecolor='r', markercolor='r', marker='o') + _assert_equal( + ax.stem(x, y, 'r', markerfmt='x'), # only marker type specified + linecolor='r', markercolor='r', marker='x') + _assert_equal( + ax.stem(x, y, 'r', markerfmt='g'), # only marker color specified + linecolor='r', markercolor='g', marker='o') + _assert_equal( + ax.stem(x, y, 'r', markerfmt='gx'), # marker type and color specified + linecolor='r', markercolor='g', marker='x') + _assert_equal( + ax.stem(x, y, 'r', markerfmt=' '), # markerfmt=' ' for no marker + linecolor='r', markercolor='r', marker='None') + _assert_equal( + ax.stem(x, y, 'r', markerfmt=''), # markerfmt='' for no marker + linecolor='r', markercolor='r', marker='None') + + # with linefmt kwarg + _assert_equal( + ax.stem(x, y, linefmt='r'), + linecolor='r', markercolor='r', marker='o') + _assert_equal( + ax.stem(x, y, linefmt='r', markerfmt='x'), + linecolor='r', markercolor='r', marker='x') + _assert_equal( + ax.stem(x, y, linefmt='r', markerfmt='gx'), + linecolor='r', markercolor='g', marker='x') + + def test_stem_dates(): fig, ax = plt.subplots(1, 1) xs = [dateutil.parser.parse("2013-9-28 11:00:00"),