Repository: matplotlib/matplotlib
Pull request: Adding elinestyle property to errorbar
This PR is intended to add an elinestyle parameter to the errorbar function. The issue is described here: https://github.com/matplotlib/matplotlib/issues/29681


The errorbar object contains horizontal and vertical lines to demonstrate error ranges on a graph. At the moment, the line style of the errorbar can only be changed by calling the set_linestyle function of the line objects representing the horizontal and vertical error lines individually. 

The elinestyle property will provide a method to assign the line style of the error lines when the errorbar is created.
the proposed use of the updated errorbar is as follows:     

```
Sample data
x = np.linspace(0, 10, 10)  # X values
y = np.sin(x)  # Y values
yerr = 0.2  # Error values for Y
xerr = 0.1  # Error values for X

# Create figure and axis
fig, ax = plt.subplots()

# Plot error bars
eb1 = ax.errorbar(x, y, xerr=xerr, yerr=yerr,elinestyle ='-.', fmt='o', capsize=5, capthick=2, color='blue', label='Data with Error')
eb2 = ax.errorbar(x, y, xerr=xerr, yerr=yerr,elinestyle ='dotted', fmt='o', capsize=5, capthick=2, color='blue', label='Data with Error')

```

- Why is this change necessary?
- What problem does it solve?
- What is the reasoning for this implementation?


<!-- Please describe the pull request, using the questions below as guidance, and link to any relevant issues and PRs:
Additionally, please summarize the changes in the title, for example "Raise ValueError on
non-numeric input to set_xlim" and avoid non-descriptive titles such as "Addresses
issue #8576".

If possible, please provide a minimum self-contained example.  If you have used
generative AI as an aid in preparing this PR, see

https://matplotlib.org/devdocs/devel/contribute.html#restrictions-on-generative-ai-usage
-->


## PR checklist
<!-- Please mark any checkboxes that do not apply to this PR as [N/A].-->

- [x] "closes #29681" is in the body of the PR description to [link the related issue](https://github.com/matplotlib/matplotlib/issues/29681)
- [x] new and changed code is [tested](https://matplotlib.org/devdocs/devel/testing.html)
- [N/A] *Plotting related* features are demonstrated in an [example](https://matplotlib.org/devdocs/devel/document.html#write-examples-and-tutorials)
- [ N/A] *New Features* and *API Changes* are noted with a [directive and release note](https://matplotlib.org/devdocs/devel/api_changes.html#announce-changes-deprecations-and-new-features)
- [N/A] Documentation complies with [general](https://matplotlib.org/devdocs/devel/document.html#write-rest-pages) and [docstring](https://matplotlib.org/devdocs/devel/document.html#write-docstrings) guidelines

<!--We understand that PRs can sometimes be overwhelming, especially as the
reviews start coming in.  Please let us know if the reviews are unclear or
the recommended next step seems overly demanding, if you would like help in
addressing a reviewer's comments, or if you have been waiting too long to hear
back on your PR.-->


Problem statement:
[ENH]: Add parameter 'error_linestyle' to plt.errorbar()
### Problem

Currently, **plt.errorbar()** does not provide a direct way to change the linestyle of the error bars themselves. The _linestyle_ parameter only affects the main connecting line, and modifying the error bars requires accessing the returned object and manually setting e.g. _set_linestyle('--')_ on the error bar lines. 

### Proposed solution

Introduce a new parameter, e.g., _error_linestyle_, that allows users to set the linestyle of the error bars directly. Example usage:

`plt.errorbar(x, y, yerr=yerr, fmt='o', error_linestyle='--')`

Hints from issue discussion:
While naming is debatable, I‘d call this `elinestyle` for consistency with `ecolor` and `elinewidth`.

@MBeusch do you want to make a pull request?
Thanks for the quick response! I originally proposed this as a suggestion rather than something I planned to implement myself. If the idea seems useful, I’d be happy to see it implemented.
Thanks for the feedback. While structurally feasible, I suspect that the need for this functionality is limited. I haven't seen non-solid error bars so far. Therefore, we won't spend time to implement this ourselves. But we will accept a PR if someone has a use case and wants to implement this.

Commit message: Update lib/matplotlib/axes/_axes.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

Patch to review:
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index b46cbce39c58..b238ce05ce2f 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -3458,7 +3458,7 @@ def _errorevery_to_mask(x, errorevery):
                       label_namer="y")
     @_docstring.interpd
     def errorbar(self, x, y, yerr=None, xerr=None,
-                 fmt='', ecolor=None, elinewidth=None, capsize=None,
+                 fmt='', ecolor=None, elinewidth=None, elinestyle=None, capsize=None,
                  barsabove=False, lolims=False, uplims=False,
                  xlolims=False, xuplims=False, errorevery=1, capthick=None,
                  **kwargs):
@@ -3508,6 +3508,12 @@ def errorbar(self, x, y, yerr=None, xerr=None,
             The linewidth of the errorbar lines. If None, the linewidth of
             the current style is used.
 
+        elinestyle : str or tuple, default: 'solid'
+           The linestyle of the errorbar lines.
+           Valid values for linestyles include {'-', '--', '-.',
+            ':', '', (offset, on-off-seq)}. See `.Line2D.set_linestyle` for a
+            complete description.
+
         capsize : float, default: :rc:`errorbar.capsize`
             The length of the error bar caps in points.
 
@@ -3822,6 +3828,13 @@ def apply_mask(arrays, mask):
 
         self._request_autoscale_view()
         caplines = caplines['x'] + caplines['y']
+
+        if(elinestyle is not None):
+            if(xerr is not None and yerr is not None): 
+                barcols[-2].set_linestyle(elinestyle)
+                barcols[-1].set_linestyle(elinestyle)
+
+
         errorbar_container = ErrorbarContainer(
             (data_line, tuple(caplines), tuple(barcols)),
             has_xerr=(xerr is not None), has_yerr=(yerr is not None),
diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi
index 6d9a5d14db72..a8bf35aef681 100644
--- a/lib/matplotlib/axes/_axes.pyi
+++ b/lib/matplotlib/axes/_axes.pyi
@@ -315,6 +315,7 @@ class Axes(_AxesBase):
         *,
         ecolor: ColorType | None = ...,
         elinewidth: float | None = ...,
+        elinestyle: str | None=...,
         capsize: float | None = ...,
         barsabove: bool = ...,
         lolims: bool | ArrayLike = ...,
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index 70d1671cafa3..73b10b2025a3 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -4525,6 +4525,28 @@ def test_errorbar_every(fig_test, fig_ref):
 def test_errorbar_linewidth_type(elinewidth):
     plt.errorbar([1, 2, 3], [1, 2, 3], yerr=[1, 2, 3], elinewidth=elinewidth)
 
+def test_errorbar_linestyle_type():
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], yerr=[1, 2, 3], elinestyle='-')
+    assert eb[-1][0].get_linestyle(), '-'
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], yerr=[1, 2, 3], elinestyle='--')
+    assert eb[-1][0].get_linestyle(), '--'
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], yerr=[1, 2, 3], elinestyle='-.')
+    assert eb[-1][0].get_linestyle(), '-.'
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], yerr=[1, 2, 3], elinestyle=':')
+    assert eb[-1][0].get_linestyle(), ':'
+
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], xerr=[1, 2, 3], yerr=[1, 2, 3], elinestyle='-')
+    assert eb[-1][0].get_linestyle(), '-'
+    assert eb[-1][1].get_linestyle(), '-'
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], xerr=[1, 2, 3], yerr=[1, 2, 3], elinestyle='--')
+    assert eb[-1][0].get_linestyle(), '--'
+    assert eb[-1][1].get_linestyle(), '--'
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], xerr=[1, 2, 3], yerr=[1, 2, 3], elinestyle='-.')
+    assert eb[-1][0].get_linestyle(), '-.'
+    assert eb[-1][1].get_linestyle(), '-.'
+    eb = plt.errorbar([1, 2, 3], [1, 2, 3], xerr=[1, 2, 3], yerr=[1, 2, 3], elinestyle=':')
+    assert eb[-1][0].get_linestyle(), ':'
+    assert eb[-1][1].get_linestyle(), ':'
 
 @check_figures_equal()
 def test_errorbar_nan(fig_test, fig_ref):


Code review comments:
diff_hunk: @@ -3458,7 +3458,7 @@ def _errorevery_to_mask(x, errorevery):
                       label_namer="y")
     @_docstring.interpd
     def errorbar(self, x, y, yerr=None, xerr=None,
-                 fmt='', ecolor=None, elinewidth=None, capsize=None,
+                 fmt='', ecolor=None, elinewidth=None, elinestyle=None, capsize=None,
                  barsabove=False, lolims=False, uplims=False,
                  xlolims=False, xuplims=False, errorevery=1, capthick=None,
path: lib/matplotlib/axes/_axes.py
text: @user2:
```suggestion
                 fmt='', ecolor=None, elinewidth=None, capsize=None,
                 barsabove=False, lolims=False, uplims=False,
                 xlolims=False, xuplims=False, errorevery=1, capthick=None,
                 elinestyle=None,
```
For strict backward compatibility, we have to insert this at the end. The position can only be changed after introducing the keyword-only flag, which we can do for 3.12. Please add a respective comment above so that we don't forget this:
```
@user1.make_keyword_only("3.10", "ecolor")  # move elinestyle up when this expires
```