Repository: matplotlib/matplotlib

Issue:
[Bug]: ".C10" does not work as plot shorthand format spec
### Bug summary

The plot shorthand format spec does not work when combining a Cn color with n>=10 with a linestyle or a markerstyle, e.g. ".C10".  Somewhat inconsistently, it does work when a Cn color is given by itself.

### Code for reproduction

```Python
from pylab import *; plot([0, 1], ".C10") 
# plot([0, 1], "C10") works; note that this also goes through the shorthand route
# unlike plot([0, 1], c="C10")
```


### Actual outcome

ValueError: '.C10' is not a valid format string (unrecognized character '0')

### Expected outcome

No error.

### Additional information

_No response_

### Operating system

macos

### Matplotlib Version

3.9.0.dev1178+g88d64e5f0c

### Matplotlib Backend

any

### Python version

3.12

### Jupyter version

none

### Installation

git checkout


Discussion:
The relevant code is:

https://github.com/matplotlib/matplotlib/blob/5e347777192b37ac77decc4f975e53ed9d87973b/lib/matplotlib/axes/_base.py#L192-L195

This is the case from the long if-elif chain that is handling the Cn colors. It only looks one character ahead (and advances the current index by exactly 2 as a result.

(If only color is provided, and multidigit `Cn` colors count for this, then that is handled prior to parsing the individual parts in this if/elif chain)



Patch:
diff --git a/doc/api/next_api_changes/behavior/27943-AL.rst b/doc/api/next_api_changes/behavior/27943-AL.rst
new file mode 100644
index 000000000000..1314b763987e
--- /dev/null
+++ b/doc/api/next_api_changes/behavior/27943-AL.rst
@@ -0,0 +1,10 @@
+plot() shorthand format interprets "Cn" (n>9) as a color-cycle color
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Previously, ``plot(..., "-C11")`` would be interpreted as requesting a plot
+using linestyle "-", color "C1" (color #1 of the color cycle), and marker "1"
+("tri-down").  It is now interpreted as requesting linestyle "-" and color
+"C11" (color #11 of the color cycle).
+
+It is recommended to pass ambiguous markers (such as "1") explicitly using the
+*marker* keyword argument.  If the shorthand form is desired, such markers can
+also be unambiguously set by putting them *before* the color string.
diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py
index be0aacd81bb0..36e146f31af1 100644
--- a/lib/matplotlib/axes/_base.py
+++ b/lib/matplotlib/axes/_base.py
@@ -5,6 +5,7 @@
 import logging
 from numbers import Real
 from operator import attrgetter
+import re
 import types
 
 import numpy as np
@@ -189,13 +190,14 @@ def _process_plot_format(fmt, *, ambiguous_fmt_datakey=False):
                 raise ValueError(errfmt.format(fmt, "two color symbols"))
             color = c
             i += 1
-        elif c == 'C' and i < len(fmt) - 1:
-            color_cycle_number = int(fmt[i + 1])
-            color = mcolors.to_rgba(f"C{color_cycle_number}")
-            i += 2
+        elif c == "C":
+            cn_color = re.match(r"C\d+", fmt[i:])
+            if not cn_color:
+                raise ValueError(errfmt.format(fmt, "'C' must be followed by a number"))
+            color = mcolors.to_rgba(cn_color[0])
+            i += len(cn_color[0])
         else:
-            raise ValueError(
-                errfmt.format(fmt, f"unrecognized character {c!r}"))
+            raise ValueError(errfmt.format(fmt, f"unrecognized character {c!r}"))
 
     if linestyle is None and marker is None:
         linestyle = mpl.rcParams['lines.linestyle']


Test patch:
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index 4e659a4d841d..5af508479592 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -8511,6 +8511,8 @@ def test_empty_line_plots():
     (":-", r"':-' is not a valid format string \(two linestyle symbols\)"),
     ("rk", r"'rk' is not a valid format string \(two color symbols\)"),
     (":o-r", r"':o-r' is not a valid format string \(two linestyle symbols\)"),
+    ("C", r"'C' is not a valid format string \('C' must be followed by a number\)"),
+    (".C", r"'.C' is not a valid format string \('C' must be followed by a number\)"),
 ))
 @pytest.mark.parametrize("data", [None, {"string": range(3)}])
 def test_plot_format_errors(fmt, match, data):
@@ -8543,6 +8545,11 @@ def test_plot_format():
     line = ax.plot([1, 2, 3], 'k3')
     assert line[0].get_marker() == '3'
     assert line[0].get_color() == 'k'
+    fig, ax = plt.subplots()
+    line = ax.plot([1, 2, 3], '.C12:')
+    assert line[0].get_marker() == '.'
+    assert line[0].get_color() == mcolors.to_rgba('C12')
+    assert line[0].get_linestyle() == ':'
 
 
 def test_automatic_legend():