diff --git a/doc/users/whats_new/filled_plus_and_x_markers.rst b/doc/users/whats_new/filled_plus_and_x_markers.rst new file mode 100644 index 000000000000..f73dcec3b575 --- /dev/null +++ b/doc/users/whats_new/filled_plus_and_x_markers.rst @@ -0,0 +1,7 @@ +Filled ``+`` and ``x`` markers +------------------------------ + +New fillable *plus* and *x* markers have been added. See +the :mod:`~matplotlib.markers` module and +:ref:`marker reference ` +examples. diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index b0ee5c51f077..ba43041174d3 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -31,6 +31,8 @@ "d" thin_diamond "|" vline "_" hline +"P" plus (filled) +"X" x (filled) TICKLEFT tickleft TICKRIGHT tickright TICKUP tickup @@ -126,6 +128,8 @@ class MarkerStyle(object): 'd': 'thin_diamond', '|': 'vline', '_': 'hline', + 'P': 'plus_filled', + 'X': 'x_filled', TICKLEFT: 'tickleft', TICKRIGHT: 'tickright', TICKUP: 'tickup', @@ -147,7 +151,8 @@ class MarkerStyle(object): # Just used for informational purposes. is_filled() # is calculated in the _set_* functions. filled_markers = ( - 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd') + 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', + 'P', 'X') fillstyles = ('full', 'left', 'right', 'bottom', 'top', 'none') _half_fillstyles = ('left', 'right', 'bottom', 'top') @@ -828,3 +833,87 @@ def _set_x(self): self._snap_threshold = 3.0 self._filled = False self._path = self._x_path + + _plus_filled_path = Path([(1/3, 0), (2/3, 0), (2/3, 1/3), + (1, 1/3), (1, 2/3), (2/3, 2/3), + (2/3, 1), (1/3, 1), (1/3, 2/3), + (0, 2/3), (0, 1/3), (1/3, 1/3), + (1/3, 0)], + [Path.MOVETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.CLOSEPOLY]) + + _plus_filled_path_t = Path([(1, 1/2), (1, 2/3), (2/3, 2/3), + (2/3, 1), (1/3, 1), (1/3, 2/3), + (0, 2/3), (0, 1/2), (1, 1/2)], + [Path.MOVETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, + Path.CLOSEPOLY]) + + def _set_plus_filled(self): + self._transform = Affine2D().translate(-0.5, -0.5) + self._snap_threshold = 5.0 + self._joinstyle = 'miter' + fs = self.get_fillstyle() + if not self._half_fill(): + self._path = self._plus_filled_path + else: + # Rotate top half path to support all partitions + if fs == 'top': + rotate, rotate_alt = 0, 180 + elif fs == 'bottom': + rotate, rotate_alt = 180, 0 + elif fs == 'left': + rotate, rotate_alt = 90, 270 + else: + rotate, rotate_alt = 270, 90 + + self._path = self._plus_filled_path_t + self._alt_path = self._plus_filled_path_t + self._alt_transform = Affine2D().translate(-0.5, -0.5) + self._transform.rotate_deg(rotate) + self._alt_transform.rotate_deg(rotate_alt) + + _x_filled_path = Path([(0.25, 0), (0.5, 0.25), (0.75, 0), (1, 0.25), + (0.75, 0.5), (1, 0.75), (0.75, 1), (0.5, 0.75), + (0.25, 1), (0, 0.75), (0.25, 0.5), (0, 0.25), + (0.25, 0)], + [Path.MOVETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.CLOSEPOLY]) + + _x_filled_path_t = Path([(0.75, 0.5), (1, 0.75), (0.75, 1), + (0.5, 0.75), (0.25, 1), (0, 0.75), + (0.25, 0.5), (0.75, 0.5)], + [Path.MOVETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.LINETO, Path.LINETO, + Path.LINETO, Path.CLOSEPOLY]) + + def _set_x_filled(self): + self._transform = Affine2D().translate(-0.5, -0.5) + self._snap_threshold = 5.0 + self._joinstyle = 'miter' + fs = self.get_fillstyle() + if not self._half_fill(): + self._path = self._x_filled_path + else: + # Rotate top half path to support all partitions + if fs == 'top': + rotate, rotate_alt = 0, 180 + elif fs == 'bottom': + rotate, rotate_alt = 180, 0 + elif fs == 'left': + rotate, rotate_alt = 90, 270 + else: + rotate, rotate_alt = 270, 90 + + self._path = self._x_filled_path_t + self._alt_path = self._x_filled_path_t + self._alt_transform = Affine2D().translate(-0.5, -0.5) + self._transform.rotate_deg(rotate) + self._alt_transform.rotate_deg(rotate_alt) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/marker_styles.png b/lib/matplotlib/tests/baseline_images/test_axes/marker_styles.png index e8be480e4ca7..0bb2791b7207 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/marker_styles.png and b/lib/matplotlib/tests/baseline_images/test_axes/marker_styles.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/marker_fill_styles.png b/lib/matplotlib/tests/baseline_images/test_lines/marker_fill_styles.png index 547599f93d2e..fc251c4cadbe 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_lines/marker_fill_styles.png and b/lib/matplotlib/tests/baseline_images/test_lines/marker_fill_styles.png differ diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 194777cce5a6..2038706c30f3 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -180,7 +180,7 @@ def test_marker_fill_styles(): markeredgewidth=2) ax.set_ylim([0, 7.5]) - ax.set_xlim([-5, 135]) + ax.set_xlim([-5, 155]) @image_comparison(baseline_images=['scaled_lines'], style='default')