diff --git a/examples/misc/svg_filter_line.py b/examples/misc/svg_filter_line.py index d4d42d2e7dca..d329e6f243b1 100644 --- a/examples/misc/svg_filter_line.py +++ b/examples/misc/svg_filter_line.py @@ -20,7 +20,7 @@ l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-", mec="b", lw=5, ms=10, label="Line 1") l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "rs-", - mec="r", lw=5, ms=10, color="r", label="Line 2") + mec="r", lw=5, ms=10, label="Line 2") for l in [l1, l2]: diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index cb6285c08d21..09d2a11b688e 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -117,6 +117,9 @@ def __call__(self, ax, renderer): self._transform - ax.figure.transSubfigure) +_FORMAT_UNSET = 'None' + + def _process_plot_format(fmt): """ Convert a MATLAB style color/line style format string to a (*linestyle*, @@ -129,6 +132,10 @@ def _process_plot_format(fmt): * 'r--': red dashed lines * 'C2--': the third color in the color cycle, dashed lines + The format is absolute in the sense that if a linestyle or marker is not + defined in *fmt*, there is no line or marker. This is expressed by + returning _FORMAT_UNSET for the respective quantity. + See Also -------- matplotlib.Line2D.lineStyles, matplotlib.colors.cnames @@ -196,9 +203,9 @@ def _process_plot_format(fmt): if linestyle is None and marker is None: linestyle = mpl.rcParams['lines.linestyle'] if linestyle is None: - linestyle = 'None' + linestyle = _FORMAT_UNSET if marker is None: - marker = 'None' + marker = _FORMAT_UNSET return linestyle, marker, color @@ -461,6 +468,21 @@ def _plot_args(self, tup, kwargs, return_kwargs=False): for prop_name, val in zip(('linestyle', 'marker', 'color'), (linestyle, marker, color)): if val is not None: + # check for conflicts between fmt and kwargs + if (fmt.lower() != 'none' + and prop_name in kwargs + and val != _FORMAT_UNSET): + # Technically ``plot(x, y, 'o', ls='--')`` is a conflict + # because 'o' implicitly unsets the linestyle + # (linestyle=_FORMAT_UNSET). + # We'll gracefully not warn in this case because an + # explicit set via kwargs can be seen as intention to + # override an implicit unset. + _api.warn_external( + f"{prop_name} is redundantly defined by the " + f"'{prop_name}' keyword argument and the fmt string " + f'"{fmt}" (-> {prop_name}={val!r}). The keyword ' + f"argument will take precedence.") kw[prop_name] = val if len(xy) == 2: diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2c57719481b9..2d448dd86d1a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -624,6 +624,17 @@ def test_fill_units(): fig.autofmt_xdate() +def test_plot_format_kwarg_redundant(): + with pytest.warns(UserWarning, match="marker .* redundantly defined"): + plt.plot([0], [0], 'o', marker='x') + with pytest.warns(UserWarning, match="linestyle .* redundantly defined"): + plt.plot([0], [0], '-', linestyle='--') + with pytest.warns(UserWarning, match="color .* redundantly defined"): + plt.plot([0], [0], 'r', color='blue') + # smoke-test: should not warn + plt.errorbar([0], [0], fmt='none', color='blue') + + @image_comparison(['single_point', 'single_point']) def test_single_point(): # Issue #1796: don't let lines.marker affect the grid