Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e7353a1

Browse filesBrowse files
committed
REORG: JoinStyle and CapStyle classes
Centralize docs and validation for JoinStyle and CapStyle in one place.
1 parent c5ab728 commit e7353a1
Copy full SHA for e7353a1

18 files changed

+354
-167
lines changed

‎doc/api/_types.rst

Copy file name to clipboard
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
**********************
2+
``matplotlib._types``
3+
**********************
4+
5+
.. automodule:: matplotlib._types
6+
:members:
7+
:undoc-members:
8+
:show-inheritance:
9+

‎doc/api/index.rst

Copy file name to clipboardExpand all lines: doc/api/index.rst
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ Matplotlib consists of the following submodules:
123123
transformations.rst
124124
tri_api.rst
125125
type1font.rst
126+
_types.rst
126127
units_api.rst
127128
widgets_api.rst
128129

‎lib/matplotlib/_types.py

Copy file name to clipboard
+165Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""
2+
Style desription information that is shared across unrelated classses.
3+
"""
4+
5+
from enum import Enum
6+
from matplotlib import cbook
7+
8+
9+
def _deprecate_case_insensitive_join_cap(s):
10+
s_low = s.lower()
11+
if s != s_low:
12+
if s_low in ['miter', 'round', 'bevel']:
13+
cbook.warn_deprecated(
14+
"3.3", message="Case-insensitive capstyles are deprecated "
15+
"since %(since)s and support for them will be removed "
16+
"%(removal)s; please pass them in lowercase.")
17+
elif s_low in ['butt', 'round', 'projecting']:
18+
cbook.warn_deprecated(
19+
"3.3", message="Case-insensitive joinstyles are deprecated "
20+
"since %(since)s and support for them will be removed "
21+
"%(removal)s; please pass them in lowercase.")
22+
# Else, error out at the check_in_list stage.
23+
return s_low
24+
25+
26+
class JoinStyle(Enum):
27+
"""
28+
Define how the connection between two line segments is drawn.
29+
30+
For a simple visual description of each *JoinStyle*, `view these docs
31+
online <JoinStyle>`, or simply run `JoinStyle.demo`.
32+
33+
.. plot::
34+
:alt: Demo of possible JoinStyle's
35+
36+
from matplotlib._types import JoinStyle
37+
JoinStyle.demo()
38+
39+
For a more precise description, we first recall that lines in Matplotlib
40+
are typically defined by a 1D `~.path.Path` and a finite ``linewidth``,
41+
where the underlying 1D `~.path.Path` represents the center of the stroked
42+
line.
43+
44+
By default, `~.backend_bases.GraphicsContextBase` defines the boundaries of
45+
a stroked line to simply be every point within some radius,
46+
``linewidth/2``, away from any point of the center line. However, this
47+
results in corners appearing "rounded", which may not be the desired
48+
behavior if you are drawing, for example, a polygon or pointed star.
49+
50+
Matplotlib provides three options for defining how the corners where two
51+
segments meet should be drawn. In short:
52+
53+
- *miter* is the "arrow-tip" style. Each boundary of the filled-in area
54+
will extend in a straight line parallel to the tangent vector of the
55+
centerline at the point it meets the corner, until they meet in a
56+
sharp point.
57+
- *round* stokes every point within a radius of ``linewidth/2`` of the
58+
center lines.
59+
- *bevel* is the "squared-off" style. It can be thought of as a rounded
60+
corner where the "circular" part of the corner has been cut off.
61+
62+
.. note::
63+
64+
The *miter* option can be controller further by specifying a "miter
65+
limit", which specifies how long a miter tip can get before it is
66+
automatically "bevel"ed off. Matplotlib does not currently expose a
67+
``miterlimit`` parameter to the user, and most backends simply use the
68+
upstream default value. For example, the PDF backend assumes the
69+
default value of 10 specified by the PDF standard, while the SVG
70+
backend does not even specify the miter limit, resulting in a default
71+
value of 4 per the SVG specification.
72+
73+
A more detailed description of the effect of a miter limit can be found
74+
in the `Mozilla Developer Docs
75+
<https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit>`_
76+
"""
77+
78+
miter = 'miter'
79+
round = 'round'
80+
bevel = 'bevel'
81+
82+
def __init__(self, s):
83+
s = _deprecate_case_insensitive_join_cap(s)
84+
Enum.__init__(self)
85+
86+
@staticmethod
87+
def demo():
88+
import numpy as np
89+
import matplotlib.pyplot as plt
90+
91+
def plot_angle(ax, x, y, angle, style):
92+
phi = np.radians(angle)
93+
xx = [x + .5, x, x + .5*np.cos(phi)]
94+
yy = [y, y, y + .5*np.sin(phi)]
95+
ax.plot(xx, yy, lw=12, color='tab:blue', solid_joinstyle=style)
96+
ax.plot(xx, yy, lw=1, color='black')
97+
ax.plot(xx[1], yy[1], 'o', color='tab:red', markersize=3)
98+
99+
fig, ax = plt.subplots(figsize=(8, 6))
100+
ax.set_title('Join style')
101+
for x, style in enumerate(['miter', 'round', 'bevel']):
102+
ax.text(x, 5, style)
103+
for y, angle in enumerate([20, 45, 60, 90, 120]):
104+
plot_angle(ax, x, y, angle, style)
105+
if x == 0:
106+
ax.text(-1.3, y, f'{angle} degrees')
107+
ax.set_xlim(-1.5, 2.75)
108+
ax.set_ylim(-.5, 5.5)
109+
ax.set_axis_off()
110+
111+
plt.show()
112+
113+
114+
class CapStyle(Enum):
115+
"""
116+
Define how the the end of a line is drawn.
117+
118+
How to draw the start and end points of lines that represent a closed curve
119+
(i.e. that end in a `~.path.Path.CLOSEPOLY`) is controlled by the line's
120+
`JoinStyle`. For all other lines, how the start and end points are drawn is
121+
controlled by the *CapStyle*.
122+
123+
For a simple visual description of each *CapStyle*, `view these docs
124+
online <CapStyle>` or simply run `CapStyle.demo`.
125+
126+
.. plot::
127+
:alt: Demo of possible CapStyle's
128+
129+
from matplotlib._types import CapStyle
130+
CapStyle.demo()
131+
132+
Briefly, the three options available are:
133+
134+
- *butt*: the line is squared off at its endpoint.
135+
- *projecting*: the line is squared off as in *butt*, but the filled in
136+
area extends beyond the endpoint a distance of ``linewidth/2``.
137+
- *round*: like *butt*, but a semicircular cap is added to the end of
138+
the line, of radius ``linewidth/2``.
139+
"""
140+
butt = 'butt'
141+
projecting = 'projecting'
142+
round = 'round'
143+
144+
def __init__(self, s):
145+
s = _deprecate_case_insensitive_join_cap(s)
146+
Enum.__init__(self)
147+
148+
@staticmethod
149+
def demo():
150+
import matplotlib.pyplot as plt
151+
152+
fig, ax = plt.subplots(figsize=(8, 2))
153+
ax.set_title('Cap style')
154+
155+
for x, style in enumerate(['butt', 'round', 'projecting']):
156+
ax.text(x+0.25, 1, style, ha='center')
157+
xx = [x, x+0.5]
158+
yy = [0, 0]
159+
ax.plot(xx, yy, lw=12, color='tab:blue', solid_capstyle=style)
160+
ax.plot(xx, yy, lw=1, color='black')
161+
ax.plot(xx, yy, 'o', color='tab:red', markersize=3)
162+
ax.text(2.25, 0.7, '(default)', ha='center')
163+
164+
ax.set_ylim(-.5, 1.5)
165+
ax.set_axis_off()

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+22-11Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
from matplotlib.backend_managers import ToolManager
5050
from matplotlib.cbook import _setattr_cm
5151
from matplotlib.path import Path
52-
from matplotlib.rcsetup import validate_joinstyle, validate_capstyle
5352
from matplotlib.transforms import Affine2D
53+
from matplotlib._types import JoinStyle, CapStyle
5454

5555

5656
_log = logging.getLogger(__name__)
@@ -764,11 +764,11 @@ def __init__(self):
764764
self._alpha = 1.0
765765
self._forced_alpha = False # if True, _alpha overrides A from RGBA
766766
self._antialiased = 1 # use 0, 1 not True, False for extension code
767-
self._capstyle = 'butt'
767+
self._capstyle = CapStyle('butt')
768768
self._cliprect = None
769769
self._clippath = None
770770
self._dashes = 0, None
771-
self._joinstyle = 'round'
771+
self._joinstyle = JoinStyle('round')
772772
self._linestyle = 'solid'
773773
self._linewidth = 1
774774
self._rgb = (0.0, 0.0, 0.0, 1.0)
@@ -820,7 +820,7 @@ def get_antialiased(self):
820820

821821
def get_capstyle(self):
822822
"""
823-
Return the capstyle as a string in ('butt', 'round', 'projecting').
823+
Return the default `.CapStyle`.
824824
"""
825825
return self._capstyle
826826

@@ -866,7 +866,7 @@ def get_forced_alpha(self):
866866
return self._forced_alpha
867867

868868
def get_joinstyle(self):
869-
"""Return the line join style as one of ('miter', 'round', 'bevel')."""
869+
"""Return the `.JoinStyle`."""
870870
return self._joinstyle
871871

872872
def get_linewidth(self):
@@ -919,9 +919,15 @@ def set_antialiased(self, b):
919919
self._antialiased = int(bool(b))
920920

921921
def set_capstyle(self, cs):
922-
"""Set the capstyle to be one of ('butt', 'round', 'projecting')."""
923-
validate_capstyle(cs)
924-
self._capstyle = cs
922+
"""
923+
Set the `.CapStyle`.
924+
925+
Parameters
926+
----------
927+
cs : `.CapStyle` or {'butt', 'round', 'projecting'}
928+
How to draw end points of lines.
929+
"""
930+
self._capstyle = CapStyle(cs)
925931

926932
def set_clip_rectangle(self, rectangle):
927933
"""
@@ -987,9 +993,14 @@ def set_foreground(self, fg, isRGBA=False):
987993
self._rgb = colors.to_rgba(fg)
988994

989995
def set_joinstyle(self, js):
990-
"""Set the join style to be one of ('miter', 'round', 'bevel')."""
991-
validate_joinstyle(js)
992-
self._joinstyle = js
996+
"""
997+
Set the `.JoinStyle`.
998+
999+
Parameters
1000+
----------
1001+
js : `.JoinStyle` or {'miter', 'round', 'bevel'}.
1002+
"""
1003+
self._joinstyle = JoinStyle(js)
9931004

9941005
def set_linewidth(self, w):
9951006
"""Set the linewidth in points."""

‎lib/matplotlib/backends/backend_cairo.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_cairo.py
+11-10Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from matplotlib.mathtext import MathTextParser
3333
from matplotlib.path import Path
3434
from matplotlib.transforms import Affine2D
35+
from matplotlib._types import JoinStyle, CapStyle
3536

3637

3738
backend_version = cairo.version
@@ -326,15 +327,15 @@ def points_to_pixels(self, points):
326327

327328
class GraphicsContextCairo(GraphicsContextBase):
328329
_joind = {
329-
'bevel': cairo.LINE_JOIN_BEVEL,
330-
'miter': cairo.LINE_JOIN_MITER,
331-
'round': cairo.LINE_JOIN_ROUND,
330+
JoinStyle.bevel: cairo.LINE_JOIN_BEVEL,
331+
JoinStyle.miter: cairo.LINE_JOIN_MITER,
332+
JoinStyle.round: cairo.LINE_JOIN_ROUND,
332333
}
333334

334335
_capd = {
335-
'butt': cairo.LINE_CAP_BUTT,
336-
'projecting': cairo.LINE_CAP_SQUARE,
337-
'round': cairo.LINE_CAP_ROUND,
336+
CapStyle.butt: cairo.LINE_CAP_BUTT,
337+
CapStyle.projecting: cairo.LINE_CAP_SQUARE,
338+
CapStyle.round: cairo.LINE_CAP_ROUND,
338339
}
339340

340341
def __init__(self, renderer):
@@ -358,8 +359,8 @@ def set_alpha(self, alpha):
358359
# one for False.
359360

360361
def set_capstyle(self, cs):
361-
self.ctx.set_line_cap(_api.check_getitem(self._capd, capstyle=cs))
362-
self._capstyle = cs
362+
super().set_capstyle(cs)
363+
self.ctx.set_line_cap(self._capd[self.get_capstyle()])
363364

364365
def set_clip_rectangle(self, rectangle):
365366
if not rectangle:
@@ -401,8 +402,8 @@ def get_rgb(self):
401402
return self.ctx.get_source().get_rgba()[:3]
402403

403404
def set_joinstyle(self, js):
404-
self.ctx.set_line_join(_api.check_getitem(self._joind, joinstyle=js))
405-
self._joinstyle = js
405+
super().set_joinstyle(js)
406+
self.ctx.set_line_join(self._joind[self.get_joinstyle()])
406407

407408
def set_linewidth(self, w):
408409
self._linewidth = float(w)

‎lib/matplotlib/backends/backend_pdf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pdf.py
+11-9Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,23 @@
2626
import matplotlib as mpl
2727
from matplotlib import _text_layout, cbook
2828
from matplotlib._pylab_helpers import Gcf
29+
from matplotlib.afm import AFM
2930
from matplotlib.backend_bases import (
3031
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
3132
GraphicsContextBase, RendererBase)
3233
from matplotlib.backends.backend_mixed import MixedModeRenderer
34+
from matplotlib.dates import UTC
35+
import matplotlib.dviread as dviread
3336
from matplotlib.figure import Figure
3437
from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font
35-
from matplotlib.afm import AFM
36-
import matplotlib.type1font as type1font
37-
import matplotlib.dviread as dviread
3838
from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE,
3939
LOAD_NO_HINTING, KERNING_UNFITTED)
4040
from matplotlib.mathtext import MathTextParser
41-
from matplotlib.transforms import Affine2D, BboxBase
42-
from matplotlib.path import Path
43-
from matplotlib.dates import UTC
4441
from matplotlib import _path
42+
from matplotlib.path import Path
43+
from matplotlib._types import JoinStyle, CapStyle
44+
import matplotlib.type1font as type1font
45+
from matplotlib.transforms import Affine2D, BboxBase
4546
from . import _backend_pdf_ps
4647

4748
_log = logging.getLogger(__name__)
@@ -738,7 +739,8 @@ def newPage(self, width, height):
738739
self.reserveObject('length of content stream'))
739740
# Initialize the pdf graphics state to match the default mpl
740741
# graphics context: currently only the join style needs to be set
741-
self.output(GraphicsContextPdf.joinstyles['round'], Op.setlinejoin)
742+
self.output(GraphicsContextPdf.joinstyles[JoinStyle.round],
743+
Op.setlinejoin)
742744

743745
# Clear the list of annotations for the next page
744746
self.pageAnnotations = []
@@ -2377,8 +2379,8 @@ def paint(self):
23772379
"""
23782380
return Op.paint_path(self.fill(), self.stroke())
23792381

2380-
capstyles = {'butt': 0, 'round': 1, 'projecting': 2}
2381-
joinstyles = {'miter': 0, 'round': 1, 'bevel': 2}
2382+
capstyles = {CapStyle.butt: 0, CapStyle.round: 1, CapStyle.projecting: 2}
2383+
joinstyles = {JoinStyle.miter: 0, JoinStyle.round: 1, JoinStyle.bevel: 2}
23822384

23832385
def capstyle_cmd(self, style):
23842386
return [self.capstyles[style], Op.setlinecap]

‎lib/matplotlib/backends/backend_pgf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pgf.py
+7-6Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from matplotlib.path import Path
2727
from matplotlib.figure import Figure
2828
from matplotlib._pylab_helpers import Gcf
29+
from matplotlib._types import JoinStyle, CapStyle
2930

3031
_log = logging.getLogger(__name__)
3132

@@ -531,15 +532,15 @@ def _print_pgf_clip(self, gc):
531532

532533
def _print_pgf_path_styles(self, gc, rgbFace):
533534
# cap style
534-
capstyles = {"butt": r"\pgfsetbuttcap",
535-
"round": r"\pgfsetroundcap",
536-
"projecting": r"\pgfsetrectcap"}
535+
capstyles = {CapStyle.butt: r"\pgfsetbuttcap",
536+
CapStyle.round: r"\pgfsetroundcap",
537+
CapStyle.projecting: r"\pgfsetrectcap"}
537538
writeln(self.fh, capstyles[gc.get_capstyle()])
538539

539540
# join style
540-
joinstyles = {"miter": r"\pgfsetmiterjoin",
541-
"round": r"\pgfsetroundjoin",
542-
"bevel": r"\pgfsetbeveljoin"}
541+
joinstyles = {JoinStyle.miter: r"\pgfsetmiterjoin",
542+
JoinStyle.round: r"\pgfsetroundjoin",
543+
JoinStyle.bevel: r"\pgfsetbeveljoin"}
543544
writeln(self.fh, joinstyles[gc.get_joinstyle()])
544545

545546
# filling

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.