Repository: matplotlib/matplotlib

Issue:
[Bug]: Markers with numeric name like CARETLEFT cannot be specified using a cycler
### Bug summary

I want to cycle between multiple markerstyles, including those with a numeric value like CARETRIGHT as documented [here](https://matplotlib.org/stable/api/markers_api.html#module-matplotlib.markers), but I get an exception if I add one of those.

### Code for reproduction

```python
#!/usr/bin/env python3

from cycler import cycler
import matplotlib.pyplot as plt
import matplotlib.markers as mk

cycle = cycler(marker=["+",mk.CARETRIGHT])

t=[0,1]
y=[5,6]

plt.rc('axes', prop_cycle=cycle)
plt.plot(t,y)
plt.show()
```


### Actual outcome

```
Traceback (most recent call last):
  File "/home/mcd/bugs/bug-cycler.py", line 11, in <module>
    plt.rc('axes', prop_cycle=cycle)
  File "/usr/lib/python3.11/site-packages/matplotlib/pyplot.py", line 670, in rc
    matplotlib.rc(group, **kwargs)
  File "/usr/lib/python3.11/site-packages/matplotlib/__init__.py", line 1062, in rc
    rcParams[key] = v
    ~~~~~~~~^^^^^
  File "/usr/lib/python3.11/site-packages/matplotlib/__init__.py", line 734, in __setitem__
    raise ValueError(f"Key {key}: {ve}") from None
ValueError: Key axes.prop_cycle: Could not convert 5 to str
```

### Expected outcome

A plot using CARETLEFT as marker.

### Additional information

This seems like it has to do with the fact, that some markers are referred to by number and not by a string like "+".
I don't know whether the problem is in matplotlib, cycler or the combination of both.

### Operating system

Artix Linux

### Matplotlib Version

3.8.1

### Matplotlib Backend

GTK4Agg

### Python version

Python 3.11.6

### Jupyter version

_No response_

### Installation

Linux package manager


Discussion:
Looks like the validator for the `marker` property is a stringlist
validator, so it is trying to coerce things into strings. Probably should
update the validator.

On Wed, Jan 3, 2024 at 1:06 PM castilma ***@***.***> wrote:

> Bug summary
>
> I want to cycle between multiple markerstyles, including those with a
> numeric value like CARETRIGHT as documented here
> <https://matplotlib.org/stable/api/markers_api.html#module-matplotlib.markers>,
> but I get an exception if I add one of those.
> Code for reproduction
>
> #!/usr/bin/env python3
> from cycler import cyclerimport matplotlib.pyplot as pltimport matplotlib.markers as mk
> cycle = cycler(marker=["+",mk.CARETRIGHT])
> t=[0,1]y=[5,6]
> plt.rc('axes', prop_cycle=cycle)plt.plot(t,y)plt.show()
>
> Actual outcome
>
> Traceback (most recent call last):
>   File "/home/mcd/bugs/bug-cycler.py", line 11, in <module>
>     plt.rc('axes', prop_cycle=cycle)
>   File "/usr/lib/python3.11/site-packages/matplotlib/pyplot.py", line 670, in rc
>     matplotlib.rc(group, **kwargs)
>   File "/usr/lib/python3.11/site-packages/matplotlib/__init__.py", line 1062, in rc
>     rcParams[key] = v
>     ~~~~~~~~^^^^^
>   File "/usr/lib/python3.11/site-packages/matplotlib/__init__.py", line 734, in __setitem__
>     raise ValueError(f"Key {key}: {ve}") from None
> ValueError: Key axes.prop_cycle: Could not convert 5 to str
>
> Expected outcome
>
> A plot using CARETLEFT as marker.
> Additional information
>
> This seems like it has to do with the fact, that some markers are referred
> to by number and not by a string like "+".
> I don't know whether the problem is in matplotlib, cycler or the
> combination of both.
> Operating system
>
> Artix Linux
> Matplotlib Version
>
> 3.8.1
> Matplotlib Backend
>
> GTK4Agg
> Python version
>
> Python 3.11.6
> Jupyter version
>
> *No response*
> Installation
>
> Linux package manager
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/matplotlib/matplotlib/issues/27596>, or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACHF6ELTISA2WV6D5ACXBLYMWM3TAVCNFSM6AAAAABBLZNEFCVHI2DSMVQWIX3LMV43ASLTON2WKOZSGA3DINBUGUZTKOI>
> .
> You are receiving this because you are subscribed to this thread.Message
> ID: ***@***.***>
>

More specifically https://github.com/matplotlib/matplotlib/blob/8703dc5682d3655258d8917c1f8ef63cb1576dbb/lib/matplotlib/rcsetup.py#L648  needs to be updated to accept the int(/enum) values.

```python
In [13]: plt.rcParams['lines.marker'] = mk.CARETRIGHT
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[13], line 1
----> 1 plt.rcParams['lines.marker'] = mk.CARETRIGHT

File /usr/lib/python3.11/site-packages/matplotlib/__init__.py:734, in RcParams.__setitem__(self, key, val)
    732         cval = self.validate[key](val)
    733     except ValueError as ve:
--> 734         raise ValueError(f"Key {key}: {ve}") from None
    735     self._set(key, cval)
    736 except KeyError as err:

ValueError: Key lines.marker: Could not convert 5 to str
```

is a simpler way exercise the same issue.
### Good first issue - notes for new contributors

This issue is suited to new contributors because it does not require understanding of the Matplotlib internals. To get started, please see our [contributing guide](https://matplotlib.org/stable/devel/index).

**We do not assign issues**. Check the *Development* section in the sidebar for linked pull requests (PRs). If there are none, feel free to start working on it. If there is an open PR, please collaborate on the work by reviewing it rather than duplicating it in a competing PR.

If something is unclear, please reach out on any of our [communication channels](https://matplotlib.org/stable/devel/contributing.html#get-connected).
Steps:

 - update the marker validator to accept all valid marker specifications (atleast the integers, maybe the path and `Marker` classes as well)
 - add a test
 - add a whats new (if extended past integers)

Good first issue because this is very narrowly scoped, but medium because there is some meta-programming in the validator code.  Some thought will need to be given to writing a validator that works for both "live" objects and for strings coming from reading and .matplotlibrc.
The problem might exist in several places such as
https://github.com/matplotlib/matplotlib/blob/8703dc5682d3655258d8917c1f8ef63cb1576dbb/lib/matplotlib/rcsetup.py#L911

On Wed, Jan 3, 2024 at 1:38 PM Thomas A Caswell ***@***.***>
wrote:

> Steps:
>
>    - update the marker validator to accept all valid marker
>    specifications (atleast the integers, maybe the path and Marker
>    classes as well)
>    - add a test
>    - add a whats new (if extended past integers)
>
> Good first issue because this is very narrowly scoped, but medium because
> there is some meta-programming in the validator code. Some thought will
> need to be given to writing a validator that works for both "live" objects
> and for strings coming from reading and .matplotlibrc.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/matplotlib/matplotlib/issues/27596#issuecomment-1875801971>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACHF6GY7VSBKB7VZ77OGN3YMWQTFAVCNFSM6AAAAABBLZNEFCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNZVHAYDCOJXGE>
> .
> You are receiving this because you commented.Message ID:
> ***@***.***>
>




Patch:
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index cf4dd07a6b98..f730db0dee3b 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -209,6 +209,20 @@ def validator(s):
     validate_float, doc='return a list of floats')
 
 
+def _validate_marker(s):
+    try:
+        return validate_int(s)
+    except ValueError as e:
+        try:
+            return validate_string(s)
+        except ValueError as e:
+            raise ValueError('Supported markers are [string, int]') from e
+
+
+_validate_markerlist = _listify_validator(
+    _validate_marker, doc='return a list of markers')
+
+
 def _validate_pathlike(s):
     if isinstance(s, (str, os.PathLike)):
         # Store value as str because savefig.directory needs to distinguish
@@ -645,7 +659,7 @@ def _validate_minor_tick_ndivs(n):
         'markeredgecolor': validate_colorlist,
         'markevery': validate_markeverylist,
         'alpha': validate_floatlist,
-        'marker': validate_stringlist,
+        'marker': _validate_markerlist,
         'hatch': validate_hatchlist,
         'dashes': validate_dashlist,
     }
@@ -908,7 +922,7 @@ def _convert_validator_spec(key, conv):
     "lines.linewidth":       validate_float,  # line width in points
     "lines.linestyle":       _validate_linestyle,  # solid line
     "lines.color":           validate_color,  # first color in color cycle
-    "lines.marker":          validate_string,  # marker name
+    "lines.marker":          _validate_marker,  # marker name
     "lines.markerfacecolor": validate_color_or_auto,  # default color
     "lines.markeredgecolor": validate_color_or_auto,  # default color
     "lines.markeredgewidth": validate_float,
@@ -957,7 +971,7 @@ def _convert_validator_spec(key, conv):
     "boxplot.meanline":    validate_bool,
 
     "boxplot.flierprops.color":           validate_color,
-    "boxplot.flierprops.marker":          validate_string,
+    "boxplot.flierprops.marker":          _validate_marker,
     "boxplot.flierprops.markerfacecolor": validate_color_or_auto,
     "boxplot.flierprops.markeredgecolor": validate_color,
     "boxplot.flierprops.markeredgewidth": validate_float,
@@ -982,7 +996,7 @@ def _convert_validator_spec(key, conv):
     "boxplot.medianprops.linestyle": _validate_linestyle,
 
     "boxplot.meanprops.color":           validate_color,
-    "boxplot.meanprops.marker":          validate_string,
+    "boxplot.meanprops.marker":          _validate_marker,
     "boxplot.meanprops.markerfacecolor": validate_color,
     "boxplot.meanprops.markeredgecolor": validate_color,
     "boxplot.meanprops.markersize":      validate_float,
@@ -1107,7 +1121,7 @@ def _convert_validator_spec(key, conv):
     "axes3d.zaxis.panecolor":    validate_color,  # 3d background pane
 
     # scatter props
-    "scatter.marker":     validate_string,
+    "scatter.marker":     _validate_marker,
     "scatter.edgecolors": validate_string,
 
     "date.epoch": _validate_date,
diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi
index 70e94a7694a9..1538dac7510e 100644
--- a/lib/matplotlib/rcsetup.pyi
+++ b/lib/matplotlib/rcsetup.pyi
@@ -39,6 +39,8 @@ def validate_int_or_None(s: Any) -> int | None: ...
 def validate_float(s: Any) -> float: ...
 def validate_float_or_None(s: Any) -> float | None: ...
 def validate_floatlist(s: Any) -> list[float]: ...
+def _validate_marker(s: Any) -> int | str: ...
+def _validate_markerlist(s: Any) -> list[int | str]: ...
 def validate_fonttype(s: Any) -> int: ...
 
 _auto_backend_sentinel: object


Test patch:
diff --git a/lib/matplotlib/tests/test_cycles.py b/lib/matplotlib/tests/test_cycles.py
index 9bbb9bc9f19c..4fa261619490 100644
--- a/lib/matplotlib/tests/test_cycles.py
+++ b/lib/matplotlib/tests/test_cycles.py
@@ -27,6 +27,11 @@ def test_marker_cycle():
     assert [l.get_marker() for l in ax.lines] == ['.', '*', 'x', '.']
 
 
+def test_valid_marker_cycles():
+    fig, ax = plt.subplots()
+    ax.set_prop_cycle(cycler(marker=[1, "+", ".", 4]))
+
+
 def test_marker_cycle_kwargs_arrays_iterators():
     fig, ax = plt.subplots()
     ax.set_prop_cycle(c=np.array(['r', 'g', 'y']),