pgf-tikz / pgf

A Portable Graphic Format for TeX
https://pgf-tikz.github.io/
1.11k stars 105 forks source link

Reverse arrow disturbs smooth paths (`draw plot`) #1290

Open tcpaiva opened 10 months ago

tcpaiva commented 10 months ago

Brief outline of the bug

The curve of the line for \draw[]plot[smooth]coordinates{(0,1)(1,0)(2,1)} is the same for \draw[->]plot[smooth]coordinates{(0,1)(1,0)(2,1)} but it is not the same for \draw[<-]plot[smooth]coordinates{(0,1)(1,0)(2,1)}

Minimal working example (MWE)


\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
  \draw[->]plot[smooth]coordinates{(0,1)(1,0)(2,1)};
  \draw[<-, blue, opacity=0.3]plot[smooth]coordinates{(0,1)(1,0)(2,1)}; %% unexpected
\end{tikzpicture}
\end{document}
muzimuzhi commented 10 months ago
diff --git a/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex b/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
index 5a9c5fc2..a29609f9 100644
--- a/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
+++ b/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
@@ -622,7 +626,7 @@
 % Line shortening for straight lines:
 %
 \def\pgf@do@shorten@straightstart{%
-  \edef\pgfprocessresultpathsuffix{\pgfsubpathfirsttoken{\the\pgf@xa}{\the\pgf@ya}\pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}}%
+  \edef\pgfprocessresultpathsuffix{\pgfsubpathfirsttoken{\the\pgf@xa}{\the\pgf@ya}\pgfsubpathsecondtoken{\the\pgf@xa}{\the\pgf@ya}}%
   \expandafter\expandafter\expandafter\def%
   \expandafter\expandafter\expandafter\pgfprocessresultpathsuffix%
   \expandafter\expandafter\expandafter{\expandafter\pgfprocessresultpathsuffix\pgfsubpath
end}%
Full example

```tex \documentclass{article} \usepackage{tikz} \usepackage{xpatch} \makeatletter \xpatchcmd\pgf@do@shorten@straightstart {\pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}} {\pgfsubpathsecondtoken{\the\pgf@xa}{\the\pgf@ya}} {}{\PatchFailed} \makeatother \begin{document} \begin{pgfpicture} % all control points \foreach \i/\j in { 0pt/20pt, 14.45pt/0pt, 20pt/0pt, 25.55pt/0pt, 40pt/20pt } { \pgfpathcircle{\pgfqpoint{\i}{\j}}{1pt} } % new path start after shortening \pgfpathcircle{\pgfqpoint{0.26936pt}{19.62717pt}}{1pt} \pgfusepath{stroke} \pgfpathmoveto{\pgfqpoint{0pt}{20pt}} \pgfpathcurveto {\pgfqpoint{0.0pt}{20.0pt}}{\pgfqpoint{14.45007pt}{0.0pt}}{\pgfqpoint{20.0pt}{0.0pt}} \pgfpathcurveto {\pgfqpoint{25.54993pt}{0.0pt}}{\pgfqpoint{40.0pt}{20.0pt}}{\pgfqpoint{40.0pt}{20.0pt}} \pgfgetpath{\mypath} \pgfusepath{stroke} \pgfsetstrokecolor{red} \pgfsetarrows{->} \pgfsetpath{\mypath} \pgfusepath{stroke} \pgfsetstrokecolor{blue} \pgfsetarrows{<-} \pgfsetpath{\mypath} \pgfusepath{stroke} \end{pgfpicture} % based on original example \begin{tikzpicture}[every plot/.append style={smooth}] \draw plot coordinates {(0,1) (1,0) (2,1)}; \draw[->, yshift=2pt, red, opacity=.3] plot coordinates {(0,1) (1,0) (2,1)}; \draw[<-, yshift=-2pt, blue, opacity=.3] plot coordinates {(0,1) (1,0) (2,1)}; %% unexpected \end{tikzpicture} \end{document} ```

Before after
image image
muzimuzhi commented 10 months ago

Ah, my patch was wrong (or too simple), when the starting and first support points of curve-to is not identical.

What really helpful is to load bending library, which will turn on precise path shortening (needed when path has arrow tips). Without bending a curve-to is treated like a straight line, which is bound to give unsatisfied shortening results in certain cases.

image

Full example 2

\documentclass{article} \usepackage{tikz} \usepackage{xpatch} \begin{document} \def\tests{ \begin{tikzpicture}[scale=3] \def\pathP{ (0, 1) .. controls (0,1) and (.7, 0) .. (1,0) } \def\pathQ{[xshift=1cm] (0,1) .. controls (0,.5) and (.7, 0) .. (1,0) } \def\pathR{[xshift=2cm] (0,1) .. controls (0.5,1) .. (1,0) } \def\pathS{[xshift=3cm] (0,1) .. controls (0,.5) and (.7, 0) .. (.7,0) } \foreach \i in {\pathP, \pathQ, \pathR, \pathS} { \draw \i; \draw[->, shift={(1pt, 1pt)}, red, opacity=.3] \i; \draw[<-, shift={(-1pt, -1pt)}, blue, opacity=.3] \i; } \end{tikzpicture}} \subsection*{Before (pgf-tikz v3.1.10)} \tests \subsection*{+ wront patch} { \makeatletter \xpatchcmd\pgf@do@shorten@straightstart {\pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}} {\pgfsubpathsecondtoken{\the\pgf@xa}{\the\pgf@ya}} {}{\PatchFailed} \makeatother \tests } \subsection*{+ bending library} \usetikzlibrary{bending} \tests \end{document}

muzimuzhi commented 10 months ago

Ah, my patch was wrong (or too simple), when the starting and first support points of curve-to is not identical.

An improved patch. More knowledge about Bezier curve than what I got is needed to make up more test cases.

diff --git a/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex b/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
index 5a9c5fc2..a9d7d4f1 100644
--- a/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
+++ b/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
@@ -622,7 +622,13 @@
 % Line shortening for straight lines:
 %
 \def\pgf@do@shorten@straightstart{%
-  \edef\pgfprocessresultpathsuffix{\pgfsubpathfirsttoken{\the\pgf@xa}{\the\pgf@ya}\pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}}%
+  \edef\pgfprocessresultpathsuffix{%
+    \pgfsubpathfirsttoken{\the\pgf@xa}{\the\pgf@ya}%
+    \ifx\pgfpointfirstonpath\pgfpointsecondonpath
+      \pgfsubpathsecondtoken{\the\pgf@xa}{\the\pgf@ya}%
+    \else
+      \pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}%
+    \fi}%
   \expandafter\expandafter\expandafter\def%
   \expandafter\expandafter\expandafter\pgfprocessresultpathsuffix%
   \expandafter\expandafter\expandafter{\expandafter\pgfprocessresultpathsuffix\pgfsubpath
end}%

image image

Full example 3 (containing patch v2)

```tex \documentclass{article} \usepackage{tikz} \usepackage{xpatch} \begin{document} \def\tests{ \begin{tikzpicture}[scale=2] \def\pathP{ (0, 1) .. controls (0,1) and (.7, 0) .. (1,0) } \def\pathQ{[xshift=1cm] (0,1) .. controls (0,.5) and (.7, 0) .. (1,0) } \def\pathR{[xshift=2cm] (0,1) .. controls (0.7,.5) .. (1,0) } \def\pathS{[xshift=2.2cm] (1,0) .. controls (1.3,0) and (2,1) .. (2,1) } \foreach \i in {\pathP, \pathQ, \pathR, \pathS} { \draw \i; \draw[->, shift={(1pt, 1pt)}, red, opacity=.3] \i; \draw[<-, shift={(-1pt, -1pt)}, blue, opacity=.3] \i; } \end{tikzpicture}} \subsection*{Before (pgf-tikz v3.1.10)} \tests \subsection*{+ wront patch (v1)} { \makeatletter \xpatchcmd\pgf@do@shorten@straightstart {\pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}} {\pgfsubpathsecondtoken{\the\pgf@xa}{\the\pgf@ya}} {}{\PatchFailed} \makeatother \tests } \subsection*{+ revised patch (v2)} { \makeatletter % perhaps a symmetric patch can be applied in \pgf@do@shorten@straightend \xpatchcmd\pgf@do@shorten@straightstart {\pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}} {% % use the same condition in \pgf@prep@straightstart \ifx\pgfpointfirstonpath\pgfpointsecondonpath \pgfsubpathsecondtoken{\the\pgf@xa}{\the\pgf@ya}% \else \pgfsubpathsecondtoken{\the\pgf@xc}{\the\pgf@yc}% \fi } {}{\PatchFailed} \makeatother \tests } \subsection*{+ bending library} \usetikzlibrary{bending} \tests \end{document} ```