Skip to content

Commit ce9a7b1

Browse files
authored
Merge pull request #20518 from takimata/pgf_sketch_path
ENH: Support sketch_params in pgf backend
2 parents 971d110 + c3503a2 commit ce9a7b1

File tree

3 files changed

+54
-0
lines changed

3 files changed

+54
-0
lines changed

lib/matplotlib/artist.py

+3
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,9 @@ def set_sketch_params(self, scale=None, length=None, randomness=None):
690690
The scale factor by which the length is shrunken or
691691
expanded (default 16.0)
692692
693+
The PGF backend uses this argument as an RNG seed and not as
694+
described above. Using the same seed yields the same random shape.
695+
693696
.. ACCEPTS: (scale: float, length: float, randomness: float)
694697
"""
695698
if scale is None:

lib/matplotlib/backends/backend_pgf.py

+24
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,30 @@ def _print_pgf_path(self, gc, path, transform, rgbFace=None):
600600
r"{\pgfqpoint{%fin}{%fin}}"
601601
% coords)
602602

603+
# apply pgf decorators
604+
sketch_params = gc.get_sketch_params() if gc else None
605+
if sketch_params is not None:
606+
# Only "length" directly maps to "segment length" in PGF's API.
607+
# PGF uses "amplitude" to pass the combined deviation in both x-
608+
# and y-direction, while matplotlib only varies the length of the
609+
# wiggle along the line ("randomness" and "length" parameters)
610+
# and has a separate "scale" argument for the amplitude.
611+
# -> Use "randomness" as PRNG seed to allow the user to force the
612+
# same shape on multiple sketched lines
613+
scale, length, randomness = sketch_params
614+
if scale is not None:
615+
# make matplotlib and PGF rendering visually similar
616+
length *= 0.5
617+
scale *= 2
618+
# PGF guarantees that repeated loading is a no-op
619+
writeln(self.fh, r"\usepgfmodule{decorations}")
620+
writeln(self.fh, r"\usepgflibrary{decorations.pathmorphing}")
621+
writeln(self.fh, r"\pgfkeys{/pgf/decoration/.cd, "
622+
f"segment length = {(length * f):f}in, "
623+
f"amplitude = {(scale * f):f}in}}")
624+
writeln(self.fh, f"\\pgfmathsetseed{{{int(randomness)}}}")
625+
writeln(self.fh, r"\pgfdecoratecurrentpath{random steps}")
626+
603627
def _pgf_path_draw(self, stroke=True, fill=False):
604628
actions = []
605629
if stroke:

lib/matplotlib/tests/test_backend_pgf.py

+27
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,30 @@ def test_minus_signs_with_tex(fig_test, fig_ref, texsystem):
337337
mpl.rcParams["pgf.texsystem"] = texsystem
338338
fig_test.text(.5, .5, "$-1$")
339339
fig_ref.text(.5, .5, "$\N{MINUS SIGN}1$")
340+
341+
342+
@pytest.mark.backend("pgf")
343+
def test_sketch_params():
344+
fig, ax = plt.subplots(figsize=(3, 3))
345+
ax.set_xticks([])
346+
ax.set_yticks([])
347+
ax.set_frame_on(False)
348+
handle, = ax.plot([0, 1])
349+
handle.set_sketch_params(scale=5, length=30, randomness=42)
350+
351+
with BytesIO() as fd:
352+
fig.savefig(fd, format='pgf')
353+
buf = fd.getvalue().decode()
354+
355+
baseline = r"""\pgfpathmoveto{\pgfqpoint{0.375000in}{0.300000in}}%
356+
\pgfpathlineto{\pgfqpoint{2.700000in}{2.700000in}}%
357+
\usepgfmodule{decorations}%
358+
\usepgflibrary{decorations.pathmorphing}%
359+
\pgfkeys{/pgf/decoration/.cd, """ \
360+
r"""segment length = 0.150000in, amplitude = 0.100000in}%
361+
\pgfmathsetseed{42}%
362+
\pgfdecoratecurrentpath{random steps}%
363+
\pgfusepath{stroke}%"""
364+
# \pgfdecoratecurrentpath must be after the path definition and before the
365+
# path is used (\pgfusepath)
366+
assert baseline in buf

0 commit comments

Comments
 (0)