From d2de8a2dcaaa6539224e906baeeca554f7183d76 Mon Sep 17 00:00:00 2001 From: Bruno Beltran Date: Tue, 22 Sep 2020 13:57:56 -0500 Subject: [PATCH 1/4] REORG: JoinStyle and CapStyle classes Breaking API, use these class instances everywhere that we used the strings internally before. --- doc/api/_types.rst | 9 + doc/api/index.rst | 1 + examples/lines_bars_and_markers/joinstyle.py | 90 ---------- lib/matplotlib/_types.py | 170 +++++++++++++++++++ lib/matplotlib/backend_bases.py | 34 ++-- lib/matplotlib/backends/backend_cairo.py | 21 +-- lib/matplotlib/backends/backend_pdf.py | 20 ++- lib/matplotlib/backends/backend_pgf.py | 13 +- lib/matplotlib/backends/backend_ps.py | 12 +- lib/matplotlib/backends/backend_svg.py | 9 +- lib/matplotlib/backends/backend_wx.py | 13 +- lib/matplotlib/collections.py | 23 +-- lib/matplotlib/lines.py | 56 +++--- lib/matplotlib/markers.py | 34 ++-- lib/matplotlib/patches.py | 25 +-- lib/matplotlib/rcsetup.py | 48 +----- lib/matplotlib/tests/test_collections.py | 13 +- lib/matplotlib/tests/test_patches.py | 7 +- src/py_converters.cpp | 23 ++- 19 files changed, 359 insertions(+), 262 deletions(-) create mode 100644 doc/api/_types.rst delete mode 100644 examples/lines_bars_and_markers/joinstyle.py create mode 100644 lib/matplotlib/_types.py diff --git a/doc/api/_types.rst b/doc/api/_types.rst new file mode 100644 index 000000000000..de1be5cb95b4 --- /dev/null +++ b/doc/api/_types.rst @@ -0,0 +1,9 @@ +********************** +``matplotlib._types`` +********************** + +.. automodule:: matplotlib._types + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/api/index.rst b/doc/api/index.rst index dba86c35ad0a..10d6d600137d 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -124,6 +124,7 @@ Matplotlib consists of the following submodules: transformations.rst tri_api.rst type1font.rst + _types.rst units_api.rst widgets_api.rst _api_api.rst diff --git a/examples/lines_bars_and_markers/joinstyle.py b/examples/lines_bars_and_markers/joinstyle.py deleted file mode 100644 index dcc47105d12f..000000000000 --- a/examples/lines_bars_and_markers/joinstyle.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -========================== -Join styles and cap styles -========================== - -This example demonstrates the available join styles and cap styles. - -Both are used in `.Line2D` and various ``Collections`` from -`matplotlib.collections` as well as some functions that create these, e.g. -`~matplotlib.pyplot.plot`. - - -Join styles -=========== - -Join styles define how the connection between two line segments is drawn. - -See the respective ``solid_joinstyle``, ``dash_joinstyle`` or ``joinstyle`` -parameters. -""" - -import numpy as np -import matplotlib.pyplot as plt - - -def plot_angle(ax, x, y, angle, style): - phi = np.radians(angle) - xx = [x + .5, x, x + .5*np.cos(phi)] - yy = [y, y, y + .5*np.sin(phi)] - ax.plot(xx, yy, lw=12, color='tab:blue', solid_joinstyle=style) - ax.plot(xx, yy, lw=1, color='black') - ax.plot(xx[1], yy[1], 'o', color='tab:red', markersize=3) - - -fig, ax = plt.subplots(figsize=(8, 6)) -ax.set_title('Join style') - -for x, style in enumerate(['miter', 'round', 'bevel']): - ax.text(x, 5, style) - for y, angle in enumerate([20, 45, 60, 90, 120]): - plot_angle(ax, x, y, angle, style) - if x == 0: - ax.text(-1.3, y, f'{angle} degrees') -ax.text(1, 4.7, '(default)') - -ax.set_xlim(-1.5, 2.75) -ax.set_ylim(-.5, 5.5) -ax.set_axis_off() -plt.show() - - -############################################################################# -# -# Cap styles -# ========== -# -# Cap styles define how the the end of a line is drawn. -# -# See the respective ``solid_capstyle``, ``dash_capstyle`` or ``capstyle`` -# parameters. - -fig, ax = plt.subplots(figsize=(8, 2)) -ax.set_title('Cap style') - -for x, style in enumerate(['butt', 'round', 'projecting']): - ax.text(x+0.25, 1, style, ha='center') - xx = [x, x+0.5] - yy = [0, 0] - ax.plot(xx, yy, lw=12, color='tab:blue', solid_capstyle=style) - ax.plot(xx, yy, lw=1, color='black') - ax.plot(xx, yy, 'o', color='tab:red', markersize=3) -ax.text(2.25, 0.7, '(default)', ha='center') - -ax.set_ylim(-.5, 1.5) -ax.set_axis_off() - - -############################################################################# -# -# ------------ -# -# References -# """""""""" -# -# The use of the following functions, methods, classes and modules is shown -# in this example: - -import matplotlib -matplotlib.axes.Axes.plot -matplotlib.pyplot.plot diff --git a/lib/matplotlib/_types.py b/lib/matplotlib/_types.py new file mode 100644 index 000000000000..f32cde398d50 --- /dev/null +++ b/lib/matplotlib/_types.py @@ -0,0 +1,170 @@ +""" +Style description information that is shared across unrelated classses. +""" + +from enum import Enum, auto +from matplotlib import cbook + + +class _AutoStringNameEnum(Enum): + """Automate the ``name = 'name'`` part of making a (str, Enum).""" + def _generate_next_value_(name, start, count, last_values): + return name + + +def _deprecate_case_insensitive_join_cap(s): + s_low = s.lower() + if s != s_low: + if s_low in ['miter', 'round', 'bevel']: + cbook.warn_deprecated( + "3.3", message="Case-insensitive capstyles are deprecated " + "since %(since)s and support for them will be removed " + "%(removal)s; please pass them in lowercase.") + elif s_low in ['butt', 'round', 'projecting']: + cbook.warn_deprecated( + "3.3", message="Case-insensitive joinstyles are deprecated " + "since %(since)s and support for them will be removed " + "%(removal)s; please pass them in lowercase.") + # Else, error out at the check_in_list stage. + return s_low + + +class JoinStyle(str, _AutoStringNameEnum): + """ + Define how the connection between two line segments is drawn. + + For a visual impression of each *JoinStyle*, `view these docs online + `, or run `JoinStyle.demo`: + + .. plot:: + :alt: Demo of possible JoinStyle's + + from matplotlib._types import JoinStyle + JoinStyle.demo() + + Lines in Matplotlib are typically defined by a 1D `~.path.Path` and a + finite ``linewidth``, where the underlying 1D `~.path.Path` represents the + center of the stroked line. + + By default, `~.backend_bases.GraphicsContextBase` defines the boundaries of + a stroked line to simply be every point within some radius, + ``linewidth/2``, away from any point of the center line. However, this + results in corners appearing "rounded", which may not be the desired + behavior if you are drawing, for example, a polygon or pointed star. + + Matplotlib provides three options for drawing the corners between adjacent + segments. In short: + + - *miter* is the "arrow-tip" style. Each boundary of the filled-in area + will extend in a straight line parallel to the tangent vector of the + centerline at the point it meets the corner, until they meet in a + sharp point. + - *round* stokes every point within a radius of ``linewidth/2`` of the + center lines. + - *bevel* is the "squared-off" style. It can be thought of as a rounded + corner where the "circular" part of the corner has been cut off. + + .. note:: + + The *miter* option can be controlled further by specifying a "miter + limit", which specifies how long a miter tip can get before it is + automatically "bevel"ed off. Matplotlib does not currently expose a + ``miterlimit`` parameter to the user, and most backends simply use the + upstream default value. For example, the PDF backend assumes the + default value of 10 specified by the PDF standard, while the SVG + backend does not even specify the miter limit, resulting in a default + value of 4 per the SVG specification. + + A more detailed description of the effect of a miter limit can be found + in the `Mozilla Developer Docs + `_ + """ + + miter = auto() + round = auto() + bevel = auto() + + def __init__(self, s): + s = _deprecate_case_insensitive_join_cap(s) + Enum.__init__(self) + + @staticmethod + def demo(): + import numpy as np + import matplotlib.pyplot as plt + + def plot_angle(ax, x, y, angle, style): + phi = np.radians(angle) + xx = [x + .5, x, x + .5*np.cos(phi)] + yy = [y, y, y + .5*np.sin(phi)] + ax.plot(xx, yy, lw=12, color='tab:blue', solid_joinstyle=style) + ax.plot(xx, yy, lw=1, color='black') + ax.plot(xx[1], yy[1], 'o', color='tab:red', markersize=3) + + fig, ax = plt.subplots(figsize=(8, 6)) + ax.set_title('Join style') + for x, style in enumerate(['miter', 'round', 'bevel']): + ax.text(x, 5, style) + for y, angle in enumerate([20, 45, 60, 90, 120]): + plot_angle(ax, x, y, angle, style) + if x == 0: + ax.text(-1.3, y, f'{angle} degrees') + ax.set_xlim(-1.5, 2.75) + ax.set_ylim(-.5, 5.5) + ax.set_axis_off() + fig.show() + + +class CapStyle(str, _AutoStringNameEnum): + r""" + Define how the two endpoints (caps) of an unclosed line are drawn. + + How to draw the start and end points of lines that represent a closed curve + (i.e. that end in a `~.path.Path.CLOSEPOLY`) is controlled by the line's + `JoinStyle`. For all other lines, how the start and end points are drawn is + controlled by the *CapStyle*. + + For a visual impression of each *CapStyle*, `view these docs online + ` or run `CapStyle.demo`: + + .. plot:: + :alt: Demo of possible CapStyle's + + from matplotlib._types import CapStyle + CapStyle.demo() + + Available options: + + - *butt*: the line is squared off at its endpoint. + - *projecting*: the line is squared off as in *butt*, but the filled in + area extends beyond the endpoint a distance of ``linewidth/2``. + - *round*: like *butt*, but a semicircular cap is added to the end of + the line, of radius ``linewidth/2``. + """ + butt = 'butt' + projecting = 'projecting' + round = 'round' + + def __init__(self, s): + s = _deprecate_case_insensitive_join_cap(s) + Enum.__init__(self) + + @staticmethod + def demo(): + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(figsize=(8, 2)) + ax.set_title('Cap style') + + for x, style in enumerate(['butt', 'round', 'projecting']): + ax.text(x+0.25, 1, style, ha='center') + xx = [x, x+0.5] + yy = [0, 0] + ax.plot(xx, yy, lw=12, color='tab:blue', solid_capstyle=style) + ax.plot(xx, yy, lw=1, color='black') + ax.plot(xx, yy, 'o', color='tab:red', markersize=3) + ax.text(2.25, 0.7, '(default)', ha='center') + + ax.set_ylim(-.5, 1.5) + ax.set_axis_off() + fig.show() diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8c11f841cb42..ec42aa991069 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -50,8 +50,8 @@ from matplotlib.backend_managers import ToolManager from matplotlib.cbook import _setattr_cm from matplotlib.path import Path -from matplotlib.rcsetup import validate_joinstyle, validate_capstyle from matplotlib.transforms import Affine2D +from matplotlib._types import JoinStyle, CapStyle _log = logging.getLogger(__name__) @@ -765,11 +765,11 @@ def __init__(self): self._alpha = 1.0 self._forced_alpha = False # if True, _alpha overrides A from RGBA self._antialiased = 1 # use 0, 1 not True, False for extension code - self._capstyle = 'butt' + self._capstyle = CapStyle('butt') self._cliprect = None self._clippath = None self._dashes = 0, None - self._joinstyle = 'round' + self._joinstyle = JoinStyle('round') self._linestyle = 'solid' self._linewidth = 1 self._rgb = (0.0, 0.0, 0.0, 1.0) @@ -820,9 +820,7 @@ def get_antialiased(self): return self._antialiased def get_capstyle(self): - """ - Return the capstyle as a string in ('butt', 'round', 'projecting'). - """ + """Return the `.CapStyle`.""" return self._capstyle def get_clip_rectangle(self): @@ -867,7 +865,7 @@ def get_forced_alpha(self): return self._forced_alpha def get_joinstyle(self): - """Return the line join style as one of ('miter', 'round', 'bevel').""" + """Return the `.JoinStyle`.""" return self._joinstyle def get_linewidth(self): @@ -920,9 +918,14 @@ def set_antialiased(self, b): self._antialiased = int(bool(b)) def set_capstyle(self, cs): - """Set the capstyle to be one of ('butt', 'round', 'projecting').""" - validate_capstyle(cs) - self._capstyle = cs + """ + Set how to draw endpoints of lines. + + Parameters + ---------- + cs : `.CapStyle` or {'butt', 'round', 'projecting'} + """ + self._capstyle = CapStyle(cs) def set_clip_rectangle(self, rectangle): """Set the clip rectangle to a `.Bbox` or None.""" @@ -980,9 +983,14 @@ def set_foreground(self, fg, isRGBA=False): self._rgb = colors.to_rgba(fg) def set_joinstyle(self, js): - """Set the join style to be one of ('miter', 'round', 'bevel').""" - validate_joinstyle(js) - self._joinstyle = js + """ + Set how to draw connections between line segments. + + Parameters + ---------- + js : `.JoinStyle` or {'miter', 'round', 'bevel'}. + """ + self._joinstyle = JoinStyle(js) def set_linewidth(self, w): """Set the linewidth in points.""" diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index b05a5fc0967a..abefdebd4944 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -32,6 +32,7 @@ from matplotlib.mathtext import MathTextParser from matplotlib.path import Path from matplotlib.transforms import Affine2D +from matplotlib._types import JoinStyle, CapStyle backend_version = cairo.version @@ -321,15 +322,15 @@ def points_to_pixels(self, points): class GraphicsContextCairo(GraphicsContextBase): _joind = { - 'bevel': cairo.LINE_JOIN_BEVEL, - 'miter': cairo.LINE_JOIN_MITER, - 'round': cairo.LINE_JOIN_ROUND, + JoinStyle.bevel: cairo.LINE_JOIN_BEVEL, + JoinStyle.miter: cairo.LINE_JOIN_MITER, + JoinStyle.round: cairo.LINE_JOIN_ROUND, } _capd = { - 'butt': cairo.LINE_CAP_BUTT, - 'projecting': cairo.LINE_CAP_SQUARE, - 'round': cairo.LINE_CAP_ROUND, + CapStyle.butt: cairo.LINE_CAP_BUTT, + CapStyle.projecting: cairo.LINE_CAP_SQUARE, + CapStyle.round: cairo.LINE_CAP_ROUND, } def __init__(self, renderer): @@ -353,8 +354,8 @@ def set_alpha(self, alpha): # one for False. def set_capstyle(self, cs): - self.ctx.set_line_cap(_api.check_getitem(self._capd, capstyle=cs)) - self._capstyle = cs + super().set_capstyle(cs) + self.ctx.set_line_cap(self._capd[self.get_capstyle()]) def set_clip_rectangle(self, rectangle): if not rectangle: @@ -396,8 +397,8 @@ def get_rgb(self): return self.ctx.get_source().get_rgba()[:3] def set_joinstyle(self, js): - self.ctx.set_line_join(_api.check_getitem(self._joind, joinstyle=js)) - self._joinstyle = js + super().set_joinstyle(js) + self.ctx.set_line_join(self._joind[self.get_joinstyle()]) def set_linewidth(self, w): self._linewidth = float(w) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 636f87c3d97b..d0a390cff86b 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -26,22 +26,23 @@ import matplotlib as mpl from matplotlib import _api, _text_layout, cbook from matplotlib._pylab_helpers import Gcf +from matplotlib.afm import AFM from matplotlib.backend_bases import ( _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) from matplotlib.backends.backend_mixed import MixedModeRenderer +from matplotlib.dates import UTC +import matplotlib.dviread as dviread from matplotlib.figure import Figure from matplotlib.font_manager import findfont, get_font -from matplotlib.afm import AFM -import matplotlib.type1font as type1font -import matplotlib.dviread as dviread from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, LOAD_NO_HINTING, KERNING_UNFITTED) from matplotlib.mathtext import MathTextParser -from matplotlib.transforms import Affine2D, BboxBase -from matplotlib.path import Path -from matplotlib.dates import UTC from matplotlib import _path +from matplotlib.path import Path +from matplotlib._types import JoinStyle, CapStyle +import matplotlib.type1font as type1font +from matplotlib.transforms import Affine2D, BboxBase from . import _backend_pdf_ps _log = logging.getLogger(__name__) @@ -746,7 +747,8 @@ def newPage(self, width, height): self.reserveObject('length of content stream')) # Initialize the pdf graphics state to match the default mpl # graphics context: currently only the join style needs to be set - self.output(GraphicsContextPdf.joinstyles['round'], Op.setlinejoin) + self.output(GraphicsContextPdf.joinstyles[JoinStyle.round], + Op.setlinejoin) # Clear the list of annotations for the next page self.pageAnnotations = [] @@ -2414,8 +2416,8 @@ def paint(self): """ return Op.paint_path(self.fill(), self.stroke()) - capstyles = {'butt': 0, 'round': 1, 'projecting': 2} - joinstyles = {'miter': 0, 'round': 1, 'bevel': 2} + capstyles = {CapStyle.butt: 0, CapStyle.round: 1, CapStyle.projecting: 2} + joinstyles = {JoinStyle.miter: 0, JoinStyle.round: 1, JoinStyle.bevel: 2} def capstyle_cmd(self, style): return [self.capstyles[style], Op.setlinecap] diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 6cc27928733a..57d22dfe5de1 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -26,6 +26,7 @@ from matplotlib.path import Path from matplotlib.figure import Figure from matplotlib._pylab_helpers import Gcf +from matplotlib._types import JoinStyle, CapStyle _log = logging.getLogger(__name__) @@ -515,15 +516,15 @@ def _print_pgf_clip(self, gc): def _print_pgf_path_styles(self, gc, rgbFace): # cap style - capstyles = {"butt": r"\pgfsetbuttcap", - "round": r"\pgfsetroundcap", - "projecting": r"\pgfsetrectcap"} + capstyles = {CapStyle.butt: r"\pgfsetbuttcap", + CapStyle.round: r"\pgfsetroundcap", + CapStyle.projecting: r"\pgfsetrectcap"} writeln(self.fh, capstyles[gc.get_capstyle()]) # join style - joinstyles = {"miter": r"\pgfsetmiterjoin", - "round": r"\pgfsetroundjoin", - "bevel": r"\pgfsetbeveljoin"} + joinstyles = {JoinStyle.miter: r"\pgfsetmiterjoin", + JoinStyle.round: r"\pgfsetroundjoin", + JoinStyle.bevel: r"\pgfsetbeveljoin"} writeln(self.fh, joinstyles[gc.get_joinstyle()]) # filling diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 7b4acb3ad29a..3e8467adf5c6 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -24,6 +24,7 @@ from matplotlib.backend_bases import ( _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) +from matplotlib.backends.backend_mixed import MixedModeRenderer from matplotlib.cbook import is_writable_file_like, file_requires_unicode from matplotlib.font_manager import get_font from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE @@ -31,9 +32,9 @@ from matplotlib.mathtext import MathTextParser from matplotlib._mathtext_data import uni2type1 from matplotlib.path import Path +from matplotlib._types import JoinStyle, CapStyle from matplotlib.texmanager import TexManager from matplotlib.transforms import Affine2D -from matplotlib.backends.backend_mixed import MixedModeRenderer from . import _backend_pdf_ps _log = logging.getLogger(__name__) @@ -801,11 +802,16 @@ def _is_transparent(rgb_or_rgba): @_api.deprecated("3.4", alternative="GraphicsContextBase") class GraphicsContextPS(GraphicsContextBase): + + _capstyles = {CapStyle.butt: 0, CapStyle.round: 1, CapStyle.projecting: 2} + def get_capstyle(self): - return {'butt': 0, 'round': 1, 'projecting': 2}[super().get_capstyle()] + return self._capstyles[super().get_capstyle()] + + _joinstyles = {JoinStyle.miter: 0, JoinStyle.round: 1, JoinStyle.bevel: 2} def get_joinstyle(self): - return {'miter': 0, 'round': 1, 'bevel': 2}[super().get_joinstyle()] + return self._joinstyles[super().get_joinstyle()] class _Orientation(Enum): diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index ce6ad0c115f8..7b31170729c4 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -27,6 +27,7 @@ from matplotlib.path import Path from matplotlib import _path from matplotlib.transforms import Affine2D, Affine2DBase +from matplotlib._types import JoinStyle, CapStyle _log = logging.getLogger(__name__) @@ -571,10 +572,10 @@ def _get_style_dict(self, gc, rgbFace): attrib['stroke-opacity'] = short_float_fmt(rgb[3]) if linewidth != 1.0: attrib['stroke-width'] = short_float_fmt(linewidth) - if gc.get_joinstyle() != 'round': - attrib['stroke-linejoin'] = gc.get_joinstyle() - if gc.get_capstyle() != 'butt': - attrib['stroke-linecap'] = _capstyle_d[gc.get_capstyle()] + if gc.get_joinstyle() != JoinStyle.round: + attrib['stroke-linejoin'] = gc.get_joinstyle().name + if gc.get_capstyle() != CapStyle.butt: + attrib['stroke-linecap'] = _capstyle_d[gc.get_capstyle().name] return attrib diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index a5f335cbbc32..b431234bb212 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -28,6 +28,7 @@ from matplotlib.figure import Figure from matplotlib.path import Path from matplotlib.transforms import Affine2D +from matplotlib._types import JoinStyle, CapStyle from matplotlib.widgets import SubplotTool import wx @@ -326,13 +327,13 @@ class GraphicsContextWx(GraphicsContextBase): since wxPython colour management is rather simple, I have not chosen to implement a separate colour manager class. """ - _capd = {'butt': wx.CAP_BUTT, - 'projecting': wx.CAP_PROJECTING, - 'round': wx.CAP_ROUND} + _capd = {CapStyle.butt: wx.CAP_BUTT, + CapStyle.projecting: wx.CAP_PROJECTING, + CapStyle.round: wx.CAP_ROUND} - _joind = {'bevel': wx.JOIN_BEVEL, - 'miter': wx.JOIN_MITER, - 'round': wx.JOIN_ROUND} + _joind = {JoinStyle.bevel: wx.JOIN_BEVEL, + JoinStyle.miter: wx.JOIN_MITER, + JoinStyle.round: wx.JOIN_ROUND} _cache = weakref.WeakKeyDictionary() diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 3b8a0a5e1fc1..4db9615d1328 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -16,6 +16,7 @@ import matplotlib as mpl from . import (_api, _path, artist, cbook, cm, colors as mcolors, docstring, hatch as mhatch, lines as mlines, path as mpath, transforms) +from ._types import JoinStyle, CapStyle import warnings @@ -110,14 +111,10 @@ def __init__(self, where *onoffseq* is an even length tuple of on and off ink lengths in points. For examples, see :doc:`/gallery/lines_bars_and_markers/linestyles`. - capstyle : str, default: :rc:`patch.capstyle` + capstyle : `.CapStyle`-like, default: :rc:`patch.capstyle` Style to use for capping lines for all paths in the collection. - See :doc:`/gallery/lines_bars_and_markers/joinstyle` for - a demonstration of each of the allowed values. - joinstyle : str, default: :rc:`patch.joinstyle` + joinstyle : `.JoinStyle`-like, default: :rc:`patch.joinstyle` Style to use for joining lines for all paths in the collection. - See :doc:`/gallery/lines_bars_and_markers/joinstyle` for - a demonstration of each of the allowed values. antialiaseds : bool or list of bool, default: :rc:`patch.antialiased` Whether each patch in the collection should be drawn with antialiasing. @@ -657,30 +654,28 @@ def set_linestyle(self, ls): def set_capstyle(self, cs): """ - Set the capstyle for the collection (for all its elements). + Set the `.CapStyle` for the collection (for all its elements). Parameters ---------- - cs : {'butt', 'round', 'projecting'} + cs : `.CapStyle` or {'butt', 'round', 'projecting'} The capstyle. """ - mpl.rcsetup.validate_capstyle(cs) - self._capstyle = cs + self._capstyle = CapStyle(cs) def get_capstyle(self): return self._capstyle def set_joinstyle(self, js): """ - Set the joinstyle for the collection (for all its elements). + Set the `.JoinStyle` for the collection (for all its elements). Parameters ---------- - js : {'miter', 'round', 'bevel'} + js : `.JoinStyle` or {'miter', 'round', 'bevel'} The joinstyle. """ - mpl.rcsetup.validate_joinstyle(js) - self._joinstyle = js + self._joinstyle = JoinStyle(js) def get_joinstyle(self): return self._joinstyle diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 6d2d196a3e32..d846e2e20eac 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -17,6 +17,7 @@ from .markers import MarkerStyle from .path import Path from .transforms import Bbox, BboxTransformTo, TransformedPath +from ._types import JoinStyle, CapStyle # Imported here for backward compatibility, even though they don't # really belong. @@ -1313,35 +1314,33 @@ def update_from(self, other): def set_dash_joinstyle(self, s): """ - Set the join style for dashed lines. + How to join segments of the line if it `~Line2D.is dashed`. Parameters ---------- - s : {'miter', 'round', 'bevel'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.JoinStyle` or {'miter', 'round', 'bevel'} """ - mpl.rcsetup.validate_joinstyle(s) - if self._dashjoinstyle != s: + js = JoinStyle(s) + if self._dashjoinstyle != js: self.stale = True - self._dashjoinstyle = s + self._dashjoinstyle = js def set_solid_joinstyle(self, s): """ - Set the join style for solid lines. + How to join segments if the line is solid (not `~Line2D.is_dashed`). Parameters ---------- - s : {'miter', 'round', 'bevel'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.JoinStyle` or {'miter', 'round', 'bevel'} """ - mpl.rcsetup.validate_joinstyle(s) - if self._solidjoinstyle != s: + js = JoinStyle(s) + if self._solidjoinstyle != js: self.stale = True - self._solidjoinstyle = s + self._solidjoinstyle = js def get_dash_joinstyle(self): """ - Return the join style for dashed lines. + Return the `.JoinStyle` for dashed lines. See also `~.Line2D.set_dash_joinstyle`. """ @@ -1349,7 +1348,7 @@ def get_dash_joinstyle(self): def get_solid_joinstyle(self): """ - Return the join style for solid lines. + Return the `.JoinStyle` for solid lines. See also `~.Line2D.set_solid_joinstyle`. """ @@ -1357,35 +1356,33 @@ def get_solid_joinstyle(self): def set_dash_capstyle(self, s): """ - Set the cap style for dashed lines. + How to draw the end caps if the line is `~Line2D.is_dashed`. Parameters ---------- - s : {'butt', 'round', 'projecting'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.CapStyle` or {'butt', 'round', 'projecting'} """ - mpl.rcsetup.validate_capstyle(s) - if self._dashcapstyle != s: + cs = CapStyle(s) + if self._dashcapstyle != cs: self.stale = True - self._dashcapstyle = s + self._dashcapstyle = cs def set_solid_capstyle(self, s): """ - Set the cap style for solid lines. + How to draw the end caps if the line is solid (not `~Line2D.is_dashed`) Parameters ---------- - s : {'butt', 'round', 'projecting'} - For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. + s : `.CapStyle` or {'butt', 'round', 'projecting'} """ - mpl.rcsetup.validate_capstyle(s) - if self._solidcapstyle != s: + cs = CapStyle(s) + if self._solidcapstyle != cs: self.stale = True - self._solidcapstyle = s + self._solidcapstyle = cs def get_dash_capstyle(self): """ - Return the cap style for dashed lines. + Return the `.CapStyle` for dashed lines. See also `~.Line2D.set_dash_capstyle`. """ @@ -1393,7 +1390,7 @@ def get_dash_capstyle(self): def get_solid_capstyle(self): """ - Return the cap style for solid lines. + Return the `.CapStyle` for solid lines. See also `~.Line2D.set_solid_capstyle`. """ @@ -1401,7 +1398,8 @@ def get_solid_capstyle(self): def is_dashed(self): """ - Return whether line has a dashed linestyle. + Return whether line has a dashed linestyle. A custom linestyle is + assumed to be dashed, we do not inspect the ``onoffseq`` directly. See also `~.Line2D.set_linestyle`. """ diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 7118051acdd3..30733e8faf0b 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -134,6 +134,7 @@ from . import _api, cbook, rcParams from .path import Path from .transforms import IdentityTransform, Affine2D +from ._types import JoinStyle, CapStyle # special-purpose marker identifiers: (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN, @@ -242,8 +243,8 @@ def _recache(self): self._alt_path = None self._alt_transform = None self._snap_threshold = None - self._joinstyle = 'round' - self._capstyle = 'butt' + self._joinstyle = JoinStyle.round + self._capstyle = CapStyle.butt # Initial guess: Assume the marker is filled unless the fillstyle is # set to 'none'. The marker function will override this for unfilled # markers. @@ -391,14 +392,14 @@ def _set_tuple_marker(self): symstyle = marker[1] if symstyle == 0: self._path = Path.unit_regular_polygon(numsides) - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter elif symstyle == 1: self._path = Path.unit_regular_star(numsides) - self._joinstyle = 'bevel' + self._joinstyle = JoinStyle.bevel elif symstyle == 2: self._path = Path.unit_regular_asterisk(numsides) self._filled = False - self._joinstyle = 'bevel' + self._joinstyle = JoinStyle.bevel else: raise ValueError(f"Unexpected tuple marker: {marker}") self._transform = Affine2D().scale(0.5).rotate_deg(rotation) @@ -499,7 +500,7 @@ def _set_triangle(self, rot, skip): self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter def _set_triangle_up(self): return self._set_triangle(0.0, 0) @@ -529,7 +530,7 @@ def _set_square(self): self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter def _set_diamond(self): self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45) @@ -543,7 +544,7 @@ def _set_diamond(self): rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs] self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter def _set_thin_diamond(self): self._set_diamond() @@ -570,7 +571,7 @@ def _set_pentagon(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter def _set_star(self): self._transform = Affine2D().scale(0.5) @@ -592,7 +593,7 @@ def _set_star(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'bevel' + self._joinstyle = JoinStyle.bevel def _set_hexagon1(self): self._transform = Affine2D().scale(0.5) @@ -616,7 +617,7 @@ def _set_hexagon1(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter def _set_hexagon2(self): self._transform = Affine2D().scale(0.5).rotate_deg(30) @@ -642,7 +643,7 @@ def _set_hexagon2(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter def _set_octagon(self): self._transform = Affine2D().scale(0.5) @@ -663,7 +664,7 @@ def _set_octagon(self): {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180.0) - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]]) @@ -737,7 +738,7 @@ def _set_caretdown(self): self._snap_threshold = 3.0 self._filled = False self._path = self._caret_path - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter def _set_caretup(self): self._set_caretdown() @@ -803,7 +804,8 @@ def _set_x(self): def _set_plus_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter + fs = self.get_fillstyle() if not self._half_fill(): self._path = self._plus_filled_path else: @@ -827,7 +829,7 @@ def _set_plus_filled(self): def _set_x_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = 'miter' + self._joinstyle = JoinStyle.miter if not self._half_fill(): self._path = self._x_filled_path else: diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 98eada4c7d5e..86edaf667b72 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -16,6 +16,7 @@ get_parallels, inside_circle, make_wedged_bezier2, split_bezier_intersecting_with_closedpath, split_path_inout) from .path import Path +from ._types import JoinStyle, CapStyle @cbook._define_aliases({ @@ -74,9 +75,9 @@ def __init__(self, if linestyle is None: linestyle = "solid" if capstyle is None: - capstyle = 'butt' + capstyle = CapStyle.butt if joinstyle is None: - joinstyle = 'miter' + joinstyle = JoinStyle.miter if antialiased is None: antialiased = mpl.rcParams['patch.antialiased'] @@ -473,14 +474,14 @@ def get_fill(self): def set_capstyle(self, s): """ - Set the capstyle. + Set the `.CapStyle`. Parameters ---------- - s : {'butt', 'round', 'projecting'} + s : `.CapStyle` or {'butt', 'round', 'projecting'} """ - mpl.rcsetup.validate_capstyle(s) - self._capstyle = s + cs = CapStyle(s) + self._capstyle = cs self.stale = True def get_capstyle(self): @@ -489,14 +490,14 @@ def get_capstyle(self): def set_joinstyle(self, s): """ - Set the joinstyle. + Set the `.JoinStyle`. Parameters ---------- - s : {'miter', 'round', 'bevel'} + s : `.JoinStyle` or {'miter', 'round', 'bevel'} """ - mpl.rcsetup.validate_joinstyle(s) - self._joinstyle = s + js = JoinStyle(s) + self._joinstyle = js self.stale = True def get_joinstyle(self): @@ -3973,8 +3974,8 @@ def __init__(self, posA=None, posB=None, path=None, ``joinstyle`` for `FancyArrowPatch` are set to ``"round"``. """ # Traditionally, the cap- and joinstyle for FancyArrowPatch are round - kwargs.setdefault("joinstyle", "round") - kwargs.setdefault("capstyle", "round") + kwargs.setdefault("joinstyle", JoinStyle.round) + kwargs.setdefault("capstyle", CapStyle.round) super().__init__(**kwargs) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index f9b68e0924af..d31f4ba50106 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -22,10 +22,11 @@ import numpy as np -from matplotlib import _api, animation, cbook +from matplotlib import animation, cbook from matplotlib.cbook import ls_mapper -from matplotlib.fontconfig_pattern import parse_fontconfig_pattern from matplotlib.colors import Colormap, is_color_like +from matplotlib.fontconfig_pattern import parse_fontconfig_pattern +from matplotlib._types import JoinStyle, CapStyle # Don't let the original cycler collide with our validating cycler from cycler import Cycler, cycler as ccycler @@ -577,41 +578,10 @@ def _is_iterable_not_string_like(x): raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.") -def _deprecate_case_insensitive_join_cap(s): - s_low = s.lower() - if s != s_low: - if s_low in ['miter', 'round', 'bevel']: - _api.warn_deprecated( - "3.3", message="Case-insensitive capstyles are deprecated " - "since %(since)s and support for them will be removed " - "%(removal)s; please pass them in lowercase.") - elif s_low in ['butt', 'round', 'projecting']: - _api.warn_deprecated( - "3.3", message="Case-insensitive joinstyles are deprecated " - "since %(since)s and support for them will be removed " - "%(removal)s; please pass them in lowercase.") - # Else, error out at the check_in_list stage. - return s_low - - -def validate_joinstyle(s): - s = _deprecate_case_insensitive_join_cap(s) - _api.check_in_list(['miter', 'round', 'bevel'], joinstyle=s) - return s - - -def validate_capstyle(s): - s = _deprecate_case_insensitive_join_cap(s) - _api.check_in_list(['butt', 'round', 'projecting'], capstyle=s) - return s - - validate_fillstyle = ValidateInStrings( 'markers.fillstyle', ['full', 'left', 'right', 'bottom', 'top', 'none']) -validate_joinstylelist = _listify_validator(validate_joinstyle) -validate_capstylelist = _listify_validator(validate_capstyle) validate_fillstylelist = _listify_validator(validate_fillstyle) @@ -788,8 +758,8 @@ def validate_hatch(s): 'linestyle': _listify_validator(_validate_linestyle), 'facecolor': validate_colorlist, 'edgecolor': validate_colorlist, - 'joinstyle': validate_joinstylelist, - 'capstyle': validate_capstylelist, + 'joinstyle': _listify_validator(JoinStyle), + 'capstyle': _listify_validator(CapStyle), 'fillstyle': validate_fillstylelist, 'markerfacecolor': validate_colorlist, 'markersize': validate_floatlist, @@ -1033,10 +1003,10 @@ def _convert_validator_spec(key, conv): "lines.markeredgewidth": validate_float, "lines.markersize": validate_float, # markersize, in points "lines.antialiased": validate_bool, # antialiased (no jaggies) - "lines.dash_joinstyle": validate_joinstyle, - "lines.solid_joinstyle": validate_joinstyle, - "lines.dash_capstyle": validate_capstyle, - "lines.solid_capstyle": validate_capstyle, + "lines.dash_joinstyle": JoinStyle, + "lines.solid_joinstyle": JoinStyle, + "lines.dash_capstyle": CapStyle, + "lines.solid_capstyle": CapStyle, "lines.dashed_pattern": validate_floatlist, "lines.dashdot_pattern": validate_floatlist, "lines.dotted_pattern": validate_floatlist, diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 1f5deddda1ff..db75a094ac62 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -6,12 +6,13 @@ import pytest import matplotlib as mpl -import matplotlib.pyplot as plt import matplotlib.collections as mcollections -import matplotlib.transforms as mtransforms from matplotlib.collections import (Collection, LineCollection, EventCollection, PolyCollection) +import matplotlib.pyplot as plt +from matplotlib._types import JoinStyle, CapStyle from matplotlib.testing.decorators import check_figures_equal, image_comparison +import matplotlib.transforms as mtransforms def generate_EventCollection_plot(): @@ -529,17 +530,17 @@ def test_lslw_bcast(): @pytest.mark.style('default') def test_capstyle(): col = mcollections.PathCollection([], capstyle='round') - assert col.get_capstyle() == 'round' + assert col.get_capstyle() == CapStyle.round col.set_capstyle('butt') - assert col.get_capstyle() == 'butt' + assert col.get_capstyle() == CapStyle.butt @pytest.mark.style('default') def test_joinstyle(): col = mcollections.PathCollection([], joinstyle='round') - assert col.get_joinstyle() == 'round' + assert col.get_joinstyle() == JoinStyle.round col.set_joinstyle('miter') - assert col.get_joinstyle() == 'miter' + assert col.get_joinstyle() == JoinStyle.miter @image_comparison(['cap_and_joinstyle.png']) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 2a908098364e..a6001752bfe5 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -6,9 +6,10 @@ import pytest from matplotlib.patches import Patch, Polygon, Rectangle, FancyArrowPatch +import matplotlib.pyplot as plt from matplotlib.testing.decorators import image_comparison, check_figures_equal from matplotlib.transforms import Bbox -import matplotlib.pyplot as plt +from matplotlib._types import JoinStyle, CapStyle from matplotlib import ( collections as mcollections, colors as mcolors, patches as mpatches, path as mpath, style as mstyle, transforms as mtransforms, rcParams) @@ -620,9 +621,9 @@ def test_default_linestyle(): def test_default_capstyle(): patch = Patch() - assert patch.get_capstyle() == 'butt' + assert patch.get_capstyle() == CapStyle.butt def test_default_joinstyle(): patch = Patch() - assert patch.get_joinstyle() == 'miter' + assert patch.get_joinstyle() == JoinStyle.miter diff --git a/src/py_converters.cpp b/src/py_converters.cpp index a4c0b1909940..208809328809 100644 --- a/src/py_converters.cpp +++ b/src/py_converters.cpp @@ -123,7 +123,17 @@ int convert_cap(PyObject *capobj, void *capp) int values[] = {agg::butt_cap, agg::round_cap, agg::square_cap}; int result = agg::butt_cap; - if (!convert_string_enum(capobj, "capstyle", names, values, &result)) { + PyObject *name = PyUnicode_FromString("name"); + PyObject *cap_string = PyObject_GetAttr(capobj, name); + if (cap_string == NULL) { + Py_DECREF(name); + PyErr_SetString(PyExc_TypeError, "capstyle must be a CapStyle (Enum) object."); + } + + int success = convert_string_enum(cap_string, "capstyle", names, values, &result); + Py_DECREF(name); + Py_DECREF(cap_string); + if (!success) { return 0; } @@ -136,8 +146,17 @@ int convert_join(PyObject *joinobj, void *joinp) const char *names[] = {"miter", "round", "bevel", NULL}; int values[] = {agg::miter_join_revert, agg::round_join, agg::bevel_join}; int result = agg::miter_join_revert; + PyObject *name = PyUnicode_FromString("name"); + PyObject *join_string = PyObject_GetAttr(joinobj, name); + if (join_string == NULL) { + Py_DECREF(name); + PyErr_SetString(PyExc_TypeError, "joinstyle must be a JoinStyle (Enum) object."); + } - if (!convert_string_enum(joinobj, "joinstyle", names, values, &result)) { + int success = convert_string_enum(join_string, "joinstyle", names, values, &result); + Py_DECREF(name); + Py_DECREF(join_string); + if (!success) { return 0; } From 56f37c3f0f9246ef8108799ac117c43eb42a274b Mon Sep 17 00:00:00 2001 From: Bruno Beltran Date: Tue, 20 Oct 2020 10:32:34 -0700 Subject: [PATCH 2/4] REVERT: Do not expose JoinStyle/CapStyle at all --- lib/matplotlib/backend_bases.py | 4 ++-- lib/matplotlib/backends/backend_cairo.py | 21 ++++++++++----------- lib/matplotlib/backends/backend_pdf.py | 20 +++++++++----------- lib/matplotlib/backends/backend_pgf.py | 13 ++++++------- lib/matplotlib/backends/backend_ps.py | 12 +++--------- lib/matplotlib/backends/backend_svg.py | 9 ++++----- lib/matplotlib/backends/backend_wx.py | 13 ++++++------- lib/matplotlib/collections.py | 6 ++---- lib/matplotlib/lines.py | 12 ++++++------ lib/matplotlib/tests/test_collections.py | 11 ++++++----- lib/matplotlib/tests/test_patches.py | 7 +++---- src/py_converters.cpp | 23 ++--------------------- 12 files changed, 59 insertions(+), 92 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index ec42aa991069..0f5a72bd4015 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -821,7 +821,7 @@ def get_antialiased(self): def get_capstyle(self): """Return the `.CapStyle`.""" - return self._capstyle + return self._capstyle.name def get_clip_rectangle(self): """ @@ -866,7 +866,7 @@ def get_forced_alpha(self): def get_joinstyle(self): """Return the `.JoinStyle`.""" - return self._joinstyle + return self._joinstyle.name def get_linewidth(self): """Return the line width in points.""" diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index abefdebd4944..b05a5fc0967a 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -32,7 +32,6 @@ from matplotlib.mathtext import MathTextParser from matplotlib.path import Path from matplotlib.transforms import Affine2D -from matplotlib._types import JoinStyle, CapStyle backend_version = cairo.version @@ -322,15 +321,15 @@ def points_to_pixels(self, points): class GraphicsContextCairo(GraphicsContextBase): _joind = { - JoinStyle.bevel: cairo.LINE_JOIN_BEVEL, - JoinStyle.miter: cairo.LINE_JOIN_MITER, - JoinStyle.round: cairo.LINE_JOIN_ROUND, + 'bevel': cairo.LINE_JOIN_BEVEL, + 'miter': cairo.LINE_JOIN_MITER, + 'round': cairo.LINE_JOIN_ROUND, } _capd = { - CapStyle.butt: cairo.LINE_CAP_BUTT, - CapStyle.projecting: cairo.LINE_CAP_SQUARE, - CapStyle.round: cairo.LINE_CAP_ROUND, + 'butt': cairo.LINE_CAP_BUTT, + 'projecting': cairo.LINE_CAP_SQUARE, + 'round': cairo.LINE_CAP_ROUND, } def __init__(self, renderer): @@ -354,8 +353,8 @@ def set_alpha(self, alpha): # one for False. def set_capstyle(self, cs): - super().set_capstyle(cs) - self.ctx.set_line_cap(self._capd[self.get_capstyle()]) + self.ctx.set_line_cap(_api.check_getitem(self._capd, capstyle=cs)) + self._capstyle = cs def set_clip_rectangle(self, rectangle): if not rectangle: @@ -397,8 +396,8 @@ def get_rgb(self): return self.ctx.get_source().get_rgba()[:3] def set_joinstyle(self, js): - super().set_joinstyle(js) - self.ctx.set_line_join(self._joind[self.get_joinstyle()]) + self.ctx.set_line_join(_api.check_getitem(self._joind, joinstyle=js)) + self._joinstyle = js def set_linewidth(self, w): self._linewidth = float(w) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index d0a390cff86b..636f87c3d97b 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -26,23 +26,22 @@ import matplotlib as mpl from matplotlib import _api, _text_layout, cbook from matplotlib._pylab_helpers import Gcf -from matplotlib.afm import AFM from matplotlib.backend_bases import ( _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) from matplotlib.backends.backend_mixed import MixedModeRenderer -from matplotlib.dates import UTC -import matplotlib.dviread as dviread from matplotlib.figure import Figure from matplotlib.font_manager import findfont, get_font +from matplotlib.afm import AFM +import matplotlib.type1font as type1font +import matplotlib.dviread as dviread from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, LOAD_NO_HINTING, KERNING_UNFITTED) from matplotlib.mathtext import MathTextParser -from matplotlib import _path -from matplotlib.path import Path -from matplotlib._types import JoinStyle, CapStyle -import matplotlib.type1font as type1font from matplotlib.transforms import Affine2D, BboxBase +from matplotlib.path import Path +from matplotlib.dates import UTC +from matplotlib import _path from . import _backend_pdf_ps _log = logging.getLogger(__name__) @@ -747,8 +746,7 @@ def newPage(self, width, height): self.reserveObject('length of content stream')) # Initialize the pdf graphics state to match the default mpl # graphics context: currently only the join style needs to be set - self.output(GraphicsContextPdf.joinstyles[JoinStyle.round], - Op.setlinejoin) + self.output(GraphicsContextPdf.joinstyles['round'], Op.setlinejoin) # Clear the list of annotations for the next page self.pageAnnotations = [] @@ -2416,8 +2414,8 @@ def paint(self): """ return Op.paint_path(self.fill(), self.stroke()) - capstyles = {CapStyle.butt: 0, CapStyle.round: 1, CapStyle.projecting: 2} - joinstyles = {JoinStyle.miter: 0, JoinStyle.round: 1, JoinStyle.bevel: 2} + capstyles = {'butt': 0, 'round': 1, 'projecting': 2} + joinstyles = {'miter': 0, 'round': 1, 'bevel': 2} def capstyle_cmd(self, style): return [self.capstyles[style], Op.setlinecap] diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 57d22dfe5de1..6cc27928733a 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -26,7 +26,6 @@ from matplotlib.path import Path from matplotlib.figure import Figure from matplotlib._pylab_helpers import Gcf -from matplotlib._types import JoinStyle, CapStyle _log = logging.getLogger(__name__) @@ -516,15 +515,15 @@ def _print_pgf_clip(self, gc): def _print_pgf_path_styles(self, gc, rgbFace): # cap style - capstyles = {CapStyle.butt: r"\pgfsetbuttcap", - CapStyle.round: r"\pgfsetroundcap", - CapStyle.projecting: r"\pgfsetrectcap"} + capstyles = {"butt": r"\pgfsetbuttcap", + "round": r"\pgfsetroundcap", + "projecting": r"\pgfsetrectcap"} writeln(self.fh, capstyles[gc.get_capstyle()]) # join style - joinstyles = {JoinStyle.miter: r"\pgfsetmiterjoin", - JoinStyle.round: r"\pgfsetroundjoin", - JoinStyle.bevel: r"\pgfsetbeveljoin"} + joinstyles = {"miter": r"\pgfsetmiterjoin", + "round": r"\pgfsetroundjoin", + "bevel": r"\pgfsetbeveljoin"} writeln(self.fh, joinstyles[gc.get_joinstyle()]) # filling diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 3e8467adf5c6..7b4acb3ad29a 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -24,7 +24,6 @@ from matplotlib.backend_bases import ( _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase) -from matplotlib.backends.backend_mixed import MixedModeRenderer from matplotlib.cbook import is_writable_file_like, file_requires_unicode from matplotlib.font_manager import get_font from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE @@ -32,9 +31,9 @@ from matplotlib.mathtext import MathTextParser from matplotlib._mathtext_data import uni2type1 from matplotlib.path import Path -from matplotlib._types import JoinStyle, CapStyle from matplotlib.texmanager import TexManager from matplotlib.transforms import Affine2D +from matplotlib.backends.backend_mixed import MixedModeRenderer from . import _backend_pdf_ps _log = logging.getLogger(__name__) @@ -802,16 +801,11 @@ def _is_transparent(rgb_or_rgba): @_api.deprecated("3.4", alternative="GraphicsContextBase") class GraphicsContextPS(GraphicsContextBase): - - _capstyles = {CapStyle.butt: 0, CapStyle.round: 1, CapStyle.projecting: 2} - def get_capstyle(self): - return self._capstyles[super().get_capstyle()] - - _joinstyles = {JoinStyle.miter: 0, JoinStyle.round: 1, JoinStyle.bevel: 2} + return {'butt': 0, 'round': 1, 'projecting': 2}[super().get_capstyle()] def get_joinstyle(self): - return self._joinstyles[super().get_joinstyle()] + return {'miter': 0, 'round': 1, 'bevel': 2}[super().get_joinstyle()] class _Orientation(Enum): diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 7b31170729c4..ce6ad0c115f8 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -27,7 +27,6 @@ from matplotlib.path import Path from matplotlib import _path from matplotlib.transforms import Affine2D, Affine2DBase -from matplotlib._types import JoinStyle, CapStyle _log = logging.getLogger(__name__) @@ -572,10 +571,10 @@ def _get_style_dict(self, gc, rgbFace): attrib['stroke-opacity'] = short_float_fmt(rgb[3]) if linewidth != 1.0: attrib['stroke-width'] = short_float_fmt(linewidth) - if gc.get_joinstyle() != JoinStyle.round: - attrib['stroke-linejoin'] = gc.get_joinstyle().name - if gc.get_capstyle() != CapStyle.butt: - attrib['stroke-linecap'] = _capstyle_d[gc.get_capstyle().name] + if gc.get_joinstyle() != 'round': + attrib['stroke-linejoin'] = gc.get_joinstyle() + if gc.get_capstyle() != 'butt': + attrib['stroke-linecap'] = _capstyle_d[gc.get_capstyle()] return attrib diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index b431234bb212..a5f335cbbc32 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -28,7 +28,6 @@ from matplotlib.figure import Figure from matplotlib.path import Path from matplotlib.transforms import Affine2D -from matplotlib._types import JoinStyle, CapStyle from matplotlib.widgets import SubplotTool import wx @@ -327,13 +326,13 @@ class GraphicsContextWx(GraphicsContextBase): since wxPython colour management is rather simple, I have not chosen to implement a separate colour manager class. """ - _capd = {CapStyle.butt: wx.CAP_BUTT, - CapStyle.projecting: wx.CAP_PROJECTING, - CapStyle.round: wx.CAP_ROUND} + _capd = {'butt': wx.CAP_BUTT, + 'projecting': wx.CAP_PROJECTING, + 'round': wx.CAP_ROUND} - _joind = {JoinStyle.bevel: wx.JOIN_BEVEL, - JoinStyle.miter: wx.JOIN_MITER, - JoinStyle.round: wx.JOIN_ROUND} + _joind = {'bevel': wx.JOIN_BEVEL, + 'miter': wx.JOIN_MITER, + 'round': wx.JOIN_ROUND} _cache = weakref.WeakKeyDictionary() diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 4db9615d1328..6463cab6ff2c 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -659,12 +659,11 @@ def set_capstyle(self, cs): Parameters ---------- cs : `.CapStyle` or {'butt', 'round', 'projecting'} - The capstyle. """ self._capstyle = CapStyle(cs) def get_capstyle(self): - return self._capstyle + return self._capstyle.name def set_joinstyle(self, js): """ @@ -673,12 +672,11 @@ def set_joinstyle(self, js): Parameters ---------- js : `.JoinStyle` or {'miter', 'round', 'bevel'} - The joinstyle. """ self._joinstyle = JoinStyle(js) def get_joinstyle(self): - return self._joinstyle + return self._joinstyle.name @staticmethod def _bcast_lwls(linewidths, dashes): diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index d846e2e20eac..0d288b84dd94 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -254,12 +254,12 @@ class Line2D(Artist): @_api.deprecated("3.4") @_api.classproperty def validCap(cls): - return ('butt', 'round', 'projecting') + return tuple(cs.value for cs in CapStyle) @_api.deprecated("3.4") @_api.classproperty def validJoin(cls): - return ('miter', 'round', 'bevel') + return tuple(js.value for js in JoinStyle) def __str__(self): if self._label != "": @@ -1344,7 +1344,7 @@ def get_dash_joinstyle(self): See also `~.Line2D.set_dash_joinstyle`. """ - return self._dashjoinstyle + return self._dashjoinstyle.name def get_solid_joinstyle(self): """ @@ -1352,7 +1352,7 @@ def get_solid_joinstyle(self): See also `~.Line2D.set_solid_joinstyle`. """ - return self._solidjoinstyle + return self._solidjoinstyle.name def set_dash_capstyle(self, s): """ @@ -1386,7 +1386,7 @@ def get_dash_capstyle(self): See also `~.Line2D.set_dash_capstyle`. """ - return self._dashcapstyle + return self._dashcapstyle.name def get_solid_capstyle(self): """ @@ -1394,7 +1394,7 @@ def get_solid_capstyle(self): See also `~.Line2D.set_solid_capstyle`. """ - return self._solidcapstyle + return self._solidcapstyle.name def is_dashed(self): """ diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index db75a094ac62..64a58cbc289b 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -6,11 +6,12 @@ import pytest import matplotlib as mpl +import matplotlib.pyplot as plt import matplotlib.collections as mcollections +import matplotlib.transforms as mtransforms from matplotlib.collections import (Collection, LineCollection, EventCollection, PolyCollection) import matplotlib.pyplot as plt -from matplotlib._types import JoinStyle, CapStyle from matplotlib.testing.decorators import check_figures_equal, image_comparison import matplotlib.transforms as mtransforms @@ -530,17 +531,17 @@ def test_lslw_bcast(): @pytest.mark.style('default') def test_capstyle(): col = mcollections.PathCollection([], capstyle='round') - assert col.get_capstyle() == CapStyle.round + assert col.get_capstyle() == 'round' col.set_capstyle('butt') - assert col.get_capstyle() == CapStyle.butt + assert col.get_capstyle() == 'butt' @pytest.mark.style('default') def test_joinstyle(): col = mcollections.PathCollection([], joinstyle='round') - assert col.get_joinstyle() == JoinStyle.round + assert col.get_joinstyle() == 'round' col.set_joinstyle('miter') - assert col.get_joinstyle() == JoinStyle.miter + assert col.get_joinstyle() == 'miter' @image_comparison(['cap_and_joinstyle.png']) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index a6001752bfe5..2a908098364e 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -6,10 +6,9 @@ import pytest from matplotlib.patches import Patch, Polygon, Rectangle, FancyArrowPatch -import matplotlib.pyplot as plt from matplotlib.testing.decorators import image_comparison, check_figures_equal from matplotlib.transforms import Bbox -from matplotlib._types import JoinStyle, CapStyle +import matplotlib.pyplot as plt from matplotlib import ( collections as mcollections, colors as mcolors, patches as mpatches, path as mpath, style as mstyle, transforms as mtransforms, rcParams) @@ -621,9 +620,9 @@ def test_default_linestyle(): def test_default_capstyle(): patch = Patch() - assert patch.get_capstyle() == CapStyle.butt + assert patch.get_capstyle() == 'butt' def test_default_joinstyle(): patch = Patch() - assert patch.get_joinstyle() == JoinStyle.miter + assert patch.get_joinstyle() == 'miter' diff --git a/src/py_converters.cpp b/src/py_converters.cpp index 208809328809..a4c0b1909940 100644 --- a/src/py_converters.cpp +++ b/src/py_converters.cpp @@ -123,17 +123,7 @@ int convert_cap(PyObject *capobj, void *capp) int values[] = {agg::butt_cap, agg::round_cap, agg::square_cap}; int result = agg::butt_cap; - PyObject *name = PyUnicode_FromString("name"); - PyObject *cap_string = PyObject_GetAttr(capobj, name); - if (cap_string == NULL) { - Py_DECREF(name); - PyErr_SetString(PyExc_TypeError, "capstyle must be a CapStyle (Enum) object."); - } - - int success = convert_string_enum(cap_string, "capstyle", names, values, &result); - Py_DECREF(name); - Py_DECREF(cap_string); - if (!success) { + if (!convert_string_enum(capobj, "capstyle", names, values, &result)) { return 0; } @@ -146,17 +136,8 @@ int convert_join(PyObject *joinobj, void *joinp) const char *names[] = {"miter", "round", "bevel", NULL}; int values[] = {agg::miter_join_revert, agg::round_join, agg::bevel_join}; int result = agg::miter_join_revert; - PyObject *name = PyUnicode_FromString("name"); - PyObject *join_string = PyObject_GetAttr(joinobj, name); - if (join_string == NULL) { - Py_DECREF(name); - PyErr_SetString(PyExc_TypeError, "joinstyle must be a JoinStyle (Enum) object."); - } - int success = convert_string_enum(join_string, "joinstyle", names, values, &result); - Py_DECREF(name); - Py_DECREF(join_string); - if (!success) { + if (!convert_string_enum(joinobj, "joinstyle", names, values, &result)) { return 0; } From 3c19b1f5fa5937531cd54a267a2fa6e4f52a6bdc Mon Sep 17 00:00:00 2001 From: Bruno Beltran Date: Tue, 20 Oct 2020 12:02:43 -0700 Subject: [PATCH 3/4] DOCS: Centralize JoinStyle/CapStyle docs --- doc/_static/mpl.css | 21 +++- doc/api/_types.rst | 12 +- examples/lines_bars_and_markers/capstyle.py | 15 +++ examples/lines_bars_and_markers/joinstyle.py | 15 +++ lib/matplotlib/_types.py | 116 ++++++++++++------- lib/matplotlib/backend_bases.py | 10 +- lib/matplotlib/collections.py | 11 +- lib/matplotlib/lines.py | 24 ++-- lib/matplotlib/patches.py | 6 +- lib/matplotlib/rcsetup.py | 2 +- 10 files changed, 168 insertions(+), 64 deletions(-) create mode 100644 examples/lines_bars_and_markers/capstyle.py create mode 100644 examples/lines_bars_and_markers/joinstyle.py diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 0d99cf930ef2..a6499284b64e 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -89,7 +89,7 @@ table.highlighttable td { padding: 0 0.5em 0 0.5em; } -cite, code, tt { +cite, code, tt, dl.value-list dt { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.95em; letter-spacing: 0.01em; @@ -730,7 +730,6 @@ td.field-body table.property-table tr:last-of-type td { border-bottom-color: #888; } - /* function and class description */ .descclassname { color: #aaa; @@ -806,6 +805,22 @@ dl.class > dd { font-size: 14px; } +/* custom tables for lists of allowed values in "mpl._types" */ +dl.value-list { + display: grid; +} + +dl.value-list dt { + grid-column: 1; + margin: 4px 0; +} + +dl.value-list dd { + grid-column: 2; + margin: 4px 0 4px 20px; + padding: 0; +} + /* parameter section table */ table.docutils.field-list { width: 100%; @@ -1257,4 +1272,4 @@ div.bullet-box li { div#gallery.section .sphx-glr-clear:first-of-type, div#tutorials.section .sphx-glr-clear:first-of-type{ display: none; -} \ No newline at end of file +} diff --git a/doc/api/_types.rst b/doc/api/_types.rst index de1be5cb95b4..88ded801768c 100644 --- a/doc/api/_types.rst +++ b/doc/api/_types.rst @@ -3,7 +3,13 @@ ********************** .. automodule:: matplotlib._types - :members: - :undoc-members: - :show-inheritance: + :no-members: + + .. autoclass:: JoinStyle + :members: demo + :exclude-members: bevel, miter, round, input_description + + .. autoclass:: CapStyle + :members: demo + :exclude-members: butt, round, projecting, input_description diff --git a/examples/lines_bars_and_markers/capstyle.py b/examples/lines_bars_and_markers/capstyle.py new file mode 100644 index 000000000000..6fda277a9a46 --- /dev/null +++ b/examples/lines_bars_and_markers/capstyle.py @@ -0,0 +1,15 @@ +""" +========= +CapStyle +========= + +The `matplotlib._types.CapStyle` controls how Matplotlib draws the corners +where two different line segments meet. For more details, see the +`~matplotlib._types.CapStyle` docs. +""" + +import matplotlib.pyplot as plt +from matplotlib._types import CapStyle + +CapStyle.demo() +plt.show() diff --git a/examples/lines_bars_and_markers/joinstyle.py b/examples/lines_bars_and_markers/joinstyle.py new file mode 100644 index 000000000000..7d2605db3798 --- /dev/null +++ b/examples/lines_bars_and_markers/joinstyle.py @@ -0,0 +1,15 @@ +""" +========= +JoinStyle +========= + +The `matplotlib._types.JoinStyle` controls how Matplotlib draws the corners +where two different line segments meet. For more details, see the +`~matplotlib._types.JoinStyle` docs. +""" + +import matplotlib.pyplot as plt +from matplotlib._types import JoinStyle + +JoinStyle.demo() +plt.show() diff --git a/lib/matplotlib/_types.py b/lib/matplotlib/_types.py index f32cde398d50..dd7a8126a35b 100644 --- a/lib/matplotlib/_types.py +++ b/lib/matplotlib/_types.py @@ -1,16 +1,28 @@ """ -Style description information that is shared across unrelated classses. +Matplotlib API concepts that would not otherwise merit a dedicated class. + +Matplotlib often uses simple data types like strings or tuples to define a +concept; e.g. the line capstyle can be specified as one of 'butt', 'round', +or 'projecting'. The classes in this module are used internally and serve to +document these concepts formally. + +As an end-user you will not use these classes directly, but only the values +they define. """ from enum import Enum, auto -from matplotlib import cbook +from matplotlib import cbook, docstring class _AutoStringNameEnum(Enum): """Automate the ``name = 'name'`` part of making a (str, Enum).""" + def _generate_next_value_(name, start, count, last_values): return name + def __hash__(self): + return str(self).__hash__() + def _deprecate_case_insensitive_join_cap(s): s_low = s.lower() @@ -34,13 +46,7 @@ class JoinStyle(str, _AutoStringNameEnum): Define how the connection between two line segments is drawn. For a visual impression of each *JoinStyle*, `view these docs online - `, or run `JoinStyle.demo`: - - .. plot:: - :alt: Demo of possible JoinStyle's - - from matplotlib._types import JoinStyle - JoinStyle.demo() + `, or run `JoinStyle.demo`. Lines in Matplotlib are typically defined by a 1D `~.path.Path` and a finite ``linewidth``, where the underlying 1D `~.path.Path` represents the @@ -52,32 +58,42 @@ class JoinStyle(str, _AutoStringNameEnum): results in corners appearing "rounded", which may not be the desired behavior if you are drawing, for example, a polygon or pointed star. - Matplotlib provides three options for drawing the corners between adjacent - segments. In short: + **Supported values:** + + .. rst-class:: value-list - - *miter* is the "arrow-tip" style. Each boundary of the filled-in area - will extend in a straight line parallel to the tangent vector of the - centerline at the point it meets the corner, until they meet in a - sharp point. - - *round* stokes every point within a radius of ``linewidth/2`` of the - center lines. - - *bevel* is the "squared-off" style. It can be thought of as a rounded - corner where the "circular" part of the corner has been cut off. + 'miter' + the "arrow-tip" style. Each boundary of the filled-in area will + extend in a straight line parallel to the tangent vector of the + centerline at the point it meets the corner, until they meet in a + sharp point. + 'round' + stokes every point within a radius of ``linewidth/2`` of the center + lines. + 'bevel' + the "squared-off" style. It can be thought of as a rounded corner + where the "circular" part of the corner has been cut off. .. note:: - The *miter* option can be controlled further by specifying a "miter - limit", which specifies how long a miter tip can get before it is - automatically "bevel"ed off. Matplotlib does not currently expose a - ``miterlimit`` parameter to the user, and most backends simply use the - upstream default value. For example, the PDF backend assumes the - default value of 10 specified by the PDF standard, while the SVG - backend does not even specify the miter limit, resulting in a default - value of 4 per the SVG specification. + Very long miter tips are cut off (to form a *bevel*) after a + backend-dependent limit called the "miter limit", which specifies the + maximum allowed ratio of miter length to line width. For example, the + PDF backend uses the default value of 10 specified by the PDF standard, + while the SVG backend does not even specify the miter limit, resulting + in a default value of 4 per the SVG specification. Matplotlib does not + currently allow the user to adjust this parameter. A more detailed description of the effect of a miter limit can be found in the `Mozilla Developer Docs `_ + + .. plot:: + :alt: Demo of possible JoinStyle's + + from matplotlib._types import JoinStyle + JoinStyle.demo() + """ miter = auto() @@ -90,6 +106,7 @@ def __init__(self, s): @staticmethod def demo(): + """Demonstrate how each JoinStyle looks for various join angles.""" import numpy as np import matplotlib.pyplot as plt @@ -101,7 +118,7 @@ def plot_angle(ax, x, y, angle, style): ax.plot(xx, yy, lw=1, color='black') ax.plot(xx[1], yy[1], 'o', color='tab:red', markersize=3) - fig, ax = plt.subplots(figsize=(8, 6)) + fig, ax = plt.subplots(figsize=(5, 4), constrained_layout=True) ax.set_title('Join style') for x, style in enumerate(['miter', 'round', 'bevel']): ax.text(x, 5, style) @@ -115,6 +132,11 @@ def plot_angle(ax, x, y, angle, style): fig.show() +JoinStyle.input_description = "{" \ + + ", ".join([f"'{js.name}'" for js in JoinStyle]) \ + + "}" + + class CapStyle(str, _AutoStringNameEnum): r""" Define how the two endpoints (caps) of an unclosed line are drawn. @@ -125,7 +147,20 @@ class CapStyle(str, _AutoStringNameEnum): controlled by the *CapStyle*. For a visual impression of each *CapStyle*, `view these docs online - ` or run `CapStyle.demo`: + ` or run `CapStyle.demo`. + + **Supported values:** + + .. rst-class:: value-list + + 'butt' + the line is squared off at its endpoint. + 'projecting' + the line is squared off as in *butt*, but the filled in area + extends beyond the endpoint a distance of ``linewidth/2``. + 'round' + like *butt*, but a semicircular cap is added to the end of the + line, of radius ``linewidth/2``. .. plot:: :alt: Demo of possible CapStyle's @@ -133,13 +168,6 @@ class CapStyle(str, _AutoStringNameEnum): from matplotlib._types import CapStyle CapStyle.demo() - Available options: - - - *butt*: the line is squared off at its endpoint. - - *projecting*: the line is squared off as in *butt*, but the filled in - area extends beyond the endpoint a distance of ``linewidth/2``. - - *round*: like *butt*, but a semicircular cap is added to the end of - the line, of radius ``linewidth/2``. """ butt = 'butt' projecting = 'projecting' @@ -151,20 +179,30 @@ def __init__(self, s): @staticmethod def demo(): + """Demonstrate how each CapStyle looks for a thick line segment.""" import matplotlib.pyplot as plt - fig, ax = plt.subplots(figsize=(8, 2)) + fig = plt.figure(figsize=(4, 1.2)) + ax = fig.add_axes([0, 0, 1, 0.8]) ax.set_title('Cap style') for x, style in enumerate(['butt', 'round', 'projecting']): - ax.text(x+0.25, 1, style, ha='center') + ax.text(x+0.25, 0.85, style, ha='center') xx = [x, x+0.5] yy = [0, 0] ax.plot(xx, yy, lw=12, color='tab:blue', solid_capstyle=style) ax.plot(xx, yy, lw=1, color='black') ax.plot(xx, yy, 'o', color='tab:red', markersize=3) - ax.text(2.25, 0.7, '(default)', ha='center') + ax.text(2.25, 0.55, '(default)', ha='center') ax.set_ylim(-.5, 1.5) ax.set_axis_off() fig.show() + + +CapStyle.input_description = "{" \ + + ", ".join([f"'{cs.name}'" for cs in CapStyle]) \ + + "}" + +docstring.interpd.update({'JoinStyle': JoinStyle.input_description, + 'CapStyle': CapStyle.input_description}) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 0f5a72bd4015..b46f78296b45 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -44,8 +44,8 @@ import matplotlib as mpl from matplotlib import ( - _api, backend_tools as tools, cbook, colors, textpath, tight_bbox, - transforms, widgets, get_backend, is_interactive, rcParams) + _api, backend_tools as tools, cbook, colors, docstring, textpath, + tight_bbox, transforms, widgets, get_backend, is_interactive, rcParams) from matplotlib._pylab_helpers import Gcf from matplotlib.backend_managers import ToolManager from matplotlib.cbook import _setattr_cm @@ -917,13 +917,14 @@ def set_antialiased(self, b): # Use ints to make life easier on extension code trying to read the gc. self._antialiased = int(bool(b)) + @docstring.interpd def set_capstyle(self, cs): """ Set how to draw endpoints of lines. Parameters ---------- - cs : `.CapStyle` or {'butt', 'round', 'projecting'} + cs : `.CapStyle` or %(CapStyle)s """ self._capstyle = CapStyle(cs) @@ -982,13 +983,14 @@ def set_foreground(self, fg, isRGBA=False): else: self._rgb = colors.to_rgba(fg) + @docstring.interpd def set_joinstyle(self, js): """ Set how to draw connections between line segments. Parameters ---------- - js : `.JoinStyle` or {'miter', 'round', 'bevel'}. + js : `.JoinStyle` or %(JoinStyle)s """ self._joinstyle = JoinStyle(js) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 6463cab6ff2c..0b445d66b070 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -72,6 +72,7 @@ class Collection(artist.Artist, cm.ScalarMappable): _edge_default = False @_api.delete_parameter("3.3", "offset_position") + @docstring.interpd def __init__(self, edgecolors=None, facecolors=None, @@ -113,8 +114,10 @@ def __init__(self, :doc:`/gallery/lines_bars_and_markers/linestyles`. capstyle : `.CapStyle`-like, default: :rc:`patch.capstyle` Style to use for capping lines for all paths in the collection. + Allowed values are %(CapStyle)s. joinstyle : `.JoinStyle`-like, default: :rc:`patch.joinstyle` Style to use for joining lines for all paths in the collection. + Allowed values are %(JoinStyle)s. antialiaseds : bool or list of bool, default: :rc:`patch.antialiased` Whether each patch in the collection should be drawn with antialiasing. @@ -125,7 +128,7 @@ def __init__(self, transOffset : `~.transforms.Transform`, default: `.IdentityTransform` A single transform which will be applied to each *offsets* vector before it is used. - offset_position : {'screen' (default), 'data' (deprecated)} + offset_position : {{'screen' (default), 'data' (deprecated)}} If set to 'data' (deprecated), *offsets* will be treated as if it is in data coordinates instead of in screen coordinates. norm : `~.colors.Normalize`, optional @@ -652,26 +655,28 @@ def set_linestyle(self, ls): self._linewidths, self._linestyles = self._bcast_lwls( self._us_lw, self._us_linestyles) + @docstring.interpd def set_capstyle(self, cs): """ Set the `.CapStyle` for the collection (for all its elements). Parameters ---------- - cs : `.CapStyle` or {'butt', 'round', 'projecting'} + cs : `.CapStyle` or %(CapStyle)s """ self._capstyle = CapStyle(cs) def get_capstyle(self): return self._capstyle.name + @docstring.interpd def set_joinstyle(self, js): """ Set the `.JoinStyle` for the collection (for all its elements). Parameters ---------- - js : `.JoinStyle` or {'miter', 'round', 'bevel'} + js : `.JoinStyle` or %(JoinStyle)s """ self._joinstyle = JoinStyle(js) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 0d288b84dd94..121e9b7e15f6 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1157,7 +1157,7 @@ def set_linestyle(self, ls): self._dashOffset, self._dashSeq = _scale_dashes( self._us_dashOffset, self._us_dashSeq, self._linewidth) - @docstring.dedent_interpd + @docstring.interpd def set_marker(self, marker): """ Set the line marker. @@ -1312,26 +1312,28 @@ def update_from(self, other): self._marker = MarkerStyle(marker=other._marker) self._drawstyle = other._drawstyle + @docstring.interpd def set_dash_joinstyle(self, s): """ - How to join segments of the line if it `~Line2D.is dashed`. + How to join segments of the line if it `~Line2D.is_dashed`. Parameters ---------- - s : `.JoinStyle` or {'miter', 'round', 'bevel'} + s : `.JoinStyle` or %(JoinStyle)s """ js = JoinStyle(s) if self._dashjoinstyle != js: self.stale = True self._dashjoinstyle = js + @docstring.interpd def set_solid_joinstyle(self, s): """ How to join segments if the line is solid (not `~Line2D.is_dashed`). Parameters ---------- - s : `.JoinStyle` or {'miter', 'round', 'bevel'} + s : `.JoinStyle` or %(JoinStyle)s """ js = JoinStyle(s) if self._solidjoinstyle != js: @@ -1354,26 +1356,28 @@ def get_solid_joinstyle(self): """ return self._solidjoinstyle.name + @docstring.interpd def set_dash_capstyle(self, s): """ How to draw the end caps if the line is `~Line2D.is_dashed`. Parameters ---------- - s : `.CapStyle` or {'butt', 'round', 'projecting'} + s : `.CapStyle` or %(CapStyle)s """ cs = CapStyle(s) if self._dashcapstyle != cs: self.stale = True self._dashcapstyle = cs + @docstring.interpd def set_solid_capstyle(self, s): """ How to draw the end caps if the line is solid (not `~Line2D.is_dashed`) Parameters ---------- - s : `.CapStyle` or {'butt', 'round', 'projecting'} + s : `.CapStyle` or %(CapStyle)s """ cs = CapStyle(s) if self._solidcapstyle != cs: @@ -1398,8 +1402,10 @@ def get_solid_capstyle(self): def is_dashed(self): """ - Return whether line has a dashed linestyle. A custom linestyle is - assumed to be dashed, we do not inspect the ``onoffseq`` directly. + Return whether line has a dashed linestyle. + + A custom linestyle is assumed to be dashed, we do not inspect the + ``onoffseq`` directly. See also `~.Line2D.set_linestyle`. """ @@ -1554,4 +1560,4 @@ def onpick(self, event): # You can not set the docstring of an instancemethod, # but you can on the underlying function. Go figure. -docstring.dedent_interpd(Line2D.__init__) +docstring.interpd(Line2D.__init__) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 86edaf667b72..8b4e1d34fac3 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -472,13 +472,14 @@ def get_fill(self): # attribute. fill = property(get_fill, set_fill) + @docstring.interpd def set_capstyle(self, s): """ Set the `.CapStyle`. Parameters ---------- - s : `.CapStyle` or {'butt', 'round', 'projecting'} + s : `.CapStyle` or %(CapStyle)s """ cs = CapStyle(s) self._capstyle = cs @@ -488,13 +489,14 @@ def get_capstyle(self): """Return the capstyle.""" return self._capstyle + @docstring.interpd def set_joinstyle(self, s): """ Set the `.JoinStyle`. Parameters ---------- - s : `.JoinStyle` or {'miter', 'round', 'bevel'} + s : `.JoinStyle` or %(JoinStyle)s """ js = JoinStyle(s) self._joinstyle = js diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index d31f4ba50106..20d6216efc28 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -22,7 +22,7 @@ import numpy as np -from matplotlib import animation, cbook +from matplotlib import _api, animation, cbook from matplotlib.cbook import ls_mapper from matplotlib.colors import Colormap, is_color_like from matplotlib.fontconfig_pattern import parse_fontconfig_pattern From 4894099e025f0006462d0b1392701ef49876a84d Mon Sep 17 00:00:00 2001 From: Bruno Beltran Date: Mon, 14 Dec 2020 12:51:15 -0800 Subject: [PATCH 4/4] REORG: _types -> _enums --- doc/api/{_types.rst => _enums.rst} | 4 ++-- doc/api/index.rst | 2 +- examples/lines_bars_and_markers/capstyle.py | 6 +++--- examples/lines_bars_and_markers/joinstyle.py | 6 +++--- lib/matplotlib/{_types.py => _enums.py} | 6 +++--- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/collections.py | 2 +- lib/matplotlib/lines.py | 2 +- lib/matplotlib/markers.py | 2 +- lib/matplotlib/patches.py | 2 +- lib/matplotlib/rcsetup.py | 2 +- lib/matplotlib/tests/test_collections.py | 4 +--- 12 files changed, 19 insertions(+), 21 deletions(-) rename doc/api/{_types.rst => _enums.rst} (83%) rename lib/matplotlib/{_types.py => _enums.py} (97%) diff --git a/doc/api/_types.rst b/doc/api/_enums.rst similarity index 83% rename from doc/api/_types.rst rename to doc/api/_enums.rst index 88ded801768c..c9e283305967 100644 --- a/doc/api/_types.rst +++ b/doc/api/_enums.rst @@ -1,8 +1,8 @@ ********************** -``matplotlib._types`` +``matplotlib._enums`` ********************** -.. automodule:: matplotlib._types +.. automodule:: matplotlib._enums :no-members: .. autoclass:: JoinStyle diff --git a/doc/api/index.rst b/doc/api/index.rst index 10d6d600137d..e783ea39a35d 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -89,6 +89,7 @@ Matplotlib consists of the following submodules: dates_api.rst docstring_api.rst dviread.rst + _enums.rst figure_api.rst font_manager_api.rst fontconfig_pattern_api.rst @@ -124,7 +125,6 @@ Matplotlib consists of the following submodules: transformations.rst tri_api.rst type1font.rst - _types.rst units_api.rst widgets_api.rst _api_api.rst diff --git a/examples/lines_bars_and_markers/capstyle.py b/examples/lines_bars_and_markers/capstyle.py index 6fda277a9a46..05bb1e96585c 100644 --- a/examples/lines_bars_and_markers/capstyle.py +++ b/examples/lines_bars_and_markers/capstyle.py @@ -3,13 +3,13 @@ CapStyle ========= -The `matplotlib._types.CapStyle` controls how Matplotlib draws the corners +The `matplotlib._enums.CapStyle` controls how Matplotlib draws the corners where two different line segments meet. For more details, see the -`~matplotlib._types.CapStyle` docs. +`~matplotlib._enums.CapStyle` docs. """ import matplotlib.pyplot as plt -from matplotlib._types import CapStyle +from matplotlib._enums import CapStyle CapStyle.demo() plt.show() diff --git a/examples/lines_bars_and_markers/joinstyle.py b/examples/lines_bars_and_markers/joinstyle.py index 7d2605db3798..0f289d7ac393 100644 --- a/examples/lines_bars_and_markers/joinstyle.py +++ b/examples/lines_bars_and_markers/joinstyle.py @@ -3,13 +3,13 @@ JoinStyle ========= -The `matplotlib._types.JoinStyle` controls how Matplotlib draws the corners +The `matplotlib._enums.JoinStyle` controls how Matplotlib draws the corners where two different line segments meet. For more details, see the -`~matplotlib._types.JoinStyle` docs. +`~matplotlib._enums.JoinStyle` docs. """ import matplotlib.pyplot as plt -from matplotlib._types import JoinStyle +from matplotlib._enums import JoinStyle JoinStyle.demo() plt.show() diff --git a/lib/matplotlib/_types.py b/lib/matplotlib/_enums.py similarity index 97% rename from lib/matplotlib/_types.py rename to lib/matplotlib/_enums.py index dd7a8126a35b..35fe82482869 100644 --- a/lib/matplotlib/_types.py +++ b/lib/matplotlib/_enums.py @@ -1,5 +1,5 @@ """ -Matplotlib API concepts that would not otherwise merit a dedicated class. +Enums representing sets of strings that Matplotlib uses as input parameters. Matplotlib often uses simple data types like strings or tuples to define a concept; e.g. the line capstyle can be specified as one of 'butt', 'round', @@ -91,7 +91,7 @@ class JoinStyle(str, _AutoStringNameEnum): .. plot:: :alt: Demo of possible JoinStyle's - from matplotlib._types import JoinStyle + from matplotlib._enums import JoinStyle JoinStyle.demo() """ @@ -165,7 +165,7 @@ class CapStyle(str, _AutoStringNameEnum): .. plot:: :alt: Demo of possible CapStyle's - from matplotlib._types import CapStyle + from matplotlib._enums import CapStyle CapStyle.demo() """ diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index b46f78296b45..b249f5e2a85a 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -51,7 +51,7 @@ from matplotlib.cbook import _setattr_cm from matplotlib.path import Path from matplotlib.transforms import Affine2D -from matplotlib._types import JoinStyle, CapStyle +from matplotlib._enums import JoinStyle, CapStyle _log = logging.getLogger(__name__) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 0b445d66b070..148665f5a410 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -16,7 +16,7 @@ import matplotlib as mpl from . import (_api, _path, artist, cbook, cm, colors as mcolors, docstring, hatch as mhatch, lines as mlines, path as mpath, transforms) -from ._types import JoinStyle, CapStyle +from ._enums import JoinStyle, CapStyle import warnings diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 121e9b7e15f6..41b2f98c314e 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -17,7 +17,7 @@ from .markers import MarkerStyle from .path import Path from .transforms import Bbox, BboxTransformTo, TransformedPath -from ._types import JoinStyle, CapStyle +from ._enums import JoinStyle, CapStyle # Imported here for backward compatibility, even though they don't # really belong. diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 30733e8faf0b..ae2bb3da83ac 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -134,7 +134,7 @@ from . import _api, cbook, rcParams from .path import Path from .transforms import IdentityTransform, Affine2D -from ._types import JoinStyle, CapStyle +from ._enums import JoinStyle, CapStyle # special-purpose marker identifiers: (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN, diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 8b4e1d34fac3..81df8568f243 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -16,7 +16,7 @@ get_parallels, inside_circle, make_wedged_bezier2, split_bezier_intersecting_with_closedpath, split_path_inout) from .path import Path -from ._types import JoinStyle, CapStyle +from ._enums import JoinStyle, CapStyle @cbook._define_aliases({ diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 20d6216efc28..535649b03f9f 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -26,7 +26,7 @@ from matplotlib.cbook import ls_mapper from matplotlib.colors import Colormap, is_color_like from matplotlib.fontconfig_pattern import parse_fontconfig_pattern -from matplotlib._types import JoinStyle, CapStyle +from matplotlib._enums import JoinStyle, CapStyle # Don't let the original cycler collide with our validating cycler from cycler import Cycler, cycler as ccycler diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 64a58cbc289b..9fe73e3b892a 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -8,12 +8,10 @@ import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.collections as mcollections -import matplotlib.transforms as mtransforms from matplotlib.collections import (Collection, LineCollection, EventCollection, PolyCollection) -import matplotlib.pyplot as plt -from matplotlib.testing.decorators import check_figures_equal, image_comparison import matplotlib.transforms as mtransforms +from matplotlib.testing.decorators import check_figures_equal, image_comparison def generate_EventCollection_plot():