Repository: silx-kit/silx
Issue: plot: Shape can be drawn outside of the plot

The shapes are not clampled in the plot view with `matplotlib`.
It works fine with the OpenGL backend.

Also i use the trick with NaN to display multiple polygones at once. `p0 p1 p2 NaN p3 p4 p5 NaN p6 p7 p8` (as result a ring is a single shape item).

```
ringItem = silxItems.Shape("polylines")
ringItem.setPoints(numpy.array((x, y)).T)
ringItem.setName(key)
ringItem.setLineStyle(":")
ringItem.setColor("#FFFFFFA0")
ringItem.setLineBgColor("#000000A0")
plot.addItem(ringItem)
```


![image](https://github.com/silx-kit/silx/assets/7579321/2bf450d8-b10f-489f-932b-06df97df79ad)

# System

```
$ conda list qt
pyqt                      5.15.7           py39h18e9c17_2    conda-forge
pyqt5-sip                 12.11.0          py39h5a03fae_2    conda-forge
qt-main                   5.15.6               h7acdfc8_2    conda-forge

$ conda list matplotlib
matplotlib                3.5.3                    pypi_0    pypi
matplotlib-inline         0.1.6                    pypi_0    pypi

$ conda list python
ipython                   8.8.0                    pypi_0    pypi
ptpython                  3.0.20                   pypi_0    pypi
python                    3.9.15          h47a2c10_0_cpython    conda-forge
python-dateutil           2.8.2                    pypi_0    pypi
python_abi                3.9                      3_cp39    conda-forge
```


Changed files:
src/silx/gui/plot/backends/BackendMatplotlib.py

diff --git a/src/silx/gui/plot/backends/BackendMatplotlib.py b/src/silx/gui/plot/backends/BackendMatplotlib.py
index 11006bc65..eb32dfa4e 100755
--- a/src/silx/gui/plot/backends/BackendMatplotlib.py
+++ b/src/silx/gui/plot/backends/BackendMatplotlib.py
@@ -393,42 +393,47 @@ class _MarkerContainer(_PickableContainer):
         return self.line.contains(mouseevent)
 
 
-class _DoubleColoredLinePatch(matplotlib.patches.Patch):
-    """Matplotlib patch to display any patch using double color."""
+class SecondEdgeColorPatchMixIn:
+    """Mix-in class to add a second color for patches with dashed lines"""
 
-    def __init__(self, patch):
-        super(_DoubleColoredLinePatch, self).__init__()
-        self.__patch = patch
-        self.linebgcolor = None
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._second_edgecolor = None
+
+    def set_second_edgecolor(self, color):
+        """Set the second color used to fill dashed edges"""
+        self._second_edgecolor = color
 
-    def __getattr__(self, name):
-        return getattr(self.__patch, name)
+    def get_second_edgecolor(self):
+        """Returns the second color used to fill dashed edges"""
+        return self._second_edgecolor
 
     def draw(self, renderer):
-        oldLineStype = self.__patch.get_linestyle()
-        if self.linebgcolor is not None and oldLineStype != "solid":
-            oldLineColor = self.__patch.get_edgecolor()
-            oldHatch = self.__patch.get_hatch()
-            self.__patch.set_linestyle("solid")
-            self.__patch.set_edgecolor(self.linebgcolor)
-            self.__patch.set_hatch(None)
-            self.__patch.draw(renderer)
-            self.__patch.set_linestyle(oldLineStype)
-            self.__patch.set_edgecolor(oldLineColor)
-            self.__patch.set_hatch(oldHatch)
-        self.__patch.draw(renderer)
+        linestyle = self.get_linestyle()
+        if linestyle == "solid" or self.get_second_edgecolor() is None:
+            super().draw(renderer)
+            return
+
+        edgecolor = self.get_edgecolor()
+        hatch = self.get_hatch()
+
+        self.set_linestyle("solid")
+        self.set_edgecolor(self.get_second_edgecolor())
+        self.set_hatch(None)
+        super().draw(renderer)
 
-    def set_transform(self, transform):
-        self.__patch.set_transform(transform)
+        self.set_linestyle(linestyle)
+        self.set_edgecolor(edgecolor)
+        self.set_hatch(hatch)
+        super().draw(renderer)
 
-    def get_path(self):
-        return self.__patch.get_path()
 
-    def contains(self, mouseevent, radius=None):
-        return self.__patch.contains(mouseevent, radius)
+class Rectangle2EdgeColor(SecondEdgeColorPatchMixIn, Rectangle):
+    """Rectangle patch with a second edge color for dashed line"""
 
-    def contains_point(self, point, radius=None):
-        return self.__patch.contains_point(point, radius)
+
+class Polygon2EdgeColor(SecondEdgeColorPatchMixIn, Polygon):
+    """Polygon patch with a second edge color for dashed line"""
 
 
 class Image(AxesImage):
@@ -805,19 +810,20 @@ class BackendMatplotlib(BackendBase.BackendBase):
             yMax = numpy.nanmax(yView)
             w = xMax - xMin
             h = yMax - yMin
-            item = Rectangle(xy=(xMin, yMin),
-                             width=w,
-                             height=h,
-                             fill=False,
-                             color=color,
-                             linestyle=linestyle,
-                             linewidth=linewidth)
+            item = Rectangle2EdgeColor(
+                xy=(xMin, yMin),
+                width=w,
+                height=h,
+                fill=False,
+                color=color,
+                linestyle=linestyle,
+                linewidth=linewidth,
+            )
+            item.set_second_edgecolor(linebgcolor)
+
             if fill:
                 item.set_hatch('.')
 
-            if linestyle != "solid" and linebgcolor is not None:
-                item = _DoubleColoredLinePatch(item)
-                item.linebgcolor = linebgcolor
 
             self.ax.add_patch(item)
 
@@ -827,19 +833,19 @@ class BackendMatplotlib(BackendBase.BackendBase):
                 closed = True
             else:  # shape == 'polylines'
                 closed = numpy.all(numpy.equal(points[0], points[-1]))
-            item = Polygon(points,
-                           closed=closed,
-                           fill=False,
-                           color=color,
-                           linestyle=linestyle,
-                           linewidth=linewidth)
+            item = Polygon2EdgeColor(
+                points,
+                closed=closed,
+                fill=False,
+                color=color,
+                linestyle=linestyle,
+                linewidth=linewidth,
+            )
+            item.set_second_edgecolor(linebgcolor)
+
             if fill and shape == 'polygon':
                 item.set_hatch('/')
 
-            if linestyle != "solid" and linebgcolor is not None:
-                item = _DoubleColoredLinePatch(item)
-                item.linebgcolor = linebgcolor
-
             self.ax.add_patch(item)
 
         else: