Can TikZ dashed lines emulate PSTricks dashed lines?
EDIT: This answer has evolved using multiple versions. The answer at the very end is probably the best, but anyway...
In the simplest case, when the sub-path is a straight line and its length is known the following approach can be used:
It is not as PSTricks does it, but it sort of does the right thing. The spacing between dashes is always expanded using this method and never shrunk, so can get quite big with small distances.
If on + off > distance - on
then nothing is done.
Also, it is necessary in this case to set the bounding box of the picture manually as recent versions of PGF expand the picture to include half the line width of each drawn path. This can be done using \useasboundingbox
or more simply (as suggested by both Paul Gaborit and percusse) using the keys trim left
and trim right
).
\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\tikzset{cheating dash/.code args={on #1 off #2 distance #3}{
\pgfmathparse{#3-#1}\let\rest=\pgfmathresult%
\pgfmathparse{#1+#2}\let\onoff=\pgfmathresult%
\pgfmathparse{max(floor(\rest/\onoff), 1)}\let\nfullonoff=\pgfmathresult%
\pgfmathparse{max((\rest-\onoff*\nfullonoff)/\nfullonoff+#2, #2)}\let\offexpand=\pgfmathresult%
\tikzset{dash pattern=on #1 off {\offexpand pt}}%
}}
\newcommand{\bookend}{{\color{blue}\rule{1pt}{1ex}}}
\newcommand{\tikzdash}[1]{%
\bookend%
\tikz[baseline=-.5ex, trim left, trim right=#1]{%
\draw[line width=2pt,cheating dash=on 4pt off 2pt distance #1] (0,0) -- (#1,0);}%
\bookend}
\begin{document}
\tikz[y=-7.5pt]
\foreach \i [count=\y from 0] in {10,...,50}
\node [anchor=west, label=west:\tiny\i pt] at (0,\y) {\tikzdash{\i pt}};
\end{document}
A slightly more general approach (to be used inside a tikzpicture
) employs a to path
, which doesn't require knowing the sub-path length in advance but again only works with straight lines. It is unfortunately a lot more involved as some work has to be done, firstly because references to nodes without anchors (e.g., \draw (A) -- (B);
) require extra calculations to get the point on the border, and secondly because to paths
are constructed (sort of) separately and then brought into the main picture.
\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\makeatletter
\tikzset{cheating dash to/.style args={on #1 off #2}{%
to path={
\pgfextra{
\pgf@process{%
% Scan \tikztostart
\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)%
% Make correction if the node border point needs to be calculated
\iftikz@shapeborder%
\pgf@process{\pgfpointshapeborder{\tikz@shapeborder@name}{\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)}}%
\fi%
}%
\edef\tikztostart{\the\pgf@x,\the\pgf@y}%
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\pgf@process{%
% Do the same for \tikztotarget
\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)%
\iftikz@shapeborder%
\pgf@process{\pgfpointshapeborder{\tikz@shapeborder@name}{\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)}}%
\fi%
}%
\edef\tikztotarget{\the\pgf@x,\the\pgf@y}%
\advance\pgf@x by-\pgf@xa%
\advance\pgf@y by-\pgf@ya%
% \pgf@x and \pgf@y now contain the path vector
\pgfmathveclen{\the\pgf@x}{\the\pgf@y}%
% Same calculations as before
\pgfmathparse{\pgfmathresult-#1}\let\rest=\pgfmathresult%
\pgfmathparse{#1+#2}\let\onoff=\pgfmathresult%
\pgfmathparse{max(floor(\rest/\onoff), 1)}\let\nfullonoff=\pgfmathresult%
\pgfmathparse{max((\rest-\onoff*\nfullonoff)/\nfullonoff+#2, #2)}\let\offexpand=\pgfmathresult%
}
(\tikztostart) -- (\tikztotarget)
\pgfextra{%
% Have to do this here.
\edef\tmp@dash{[dash pattern=on #1 off {\offexpand pt}]}%
\expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\tikz@after@path%
\expandafter\expandafter\expandafter{\expandafter\tikz@after@path\tmp@dash}%
}%
}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}[every node/.style={draw}]
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw (0,\i) to [cheating dash to=on 4pt off 2pt] (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}
\node [rectangle] at (0, \i) (A-\i) {$A_\i$};
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw (A-\i) to [cheating dash to=on 4pt off 2pt] (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}{
\node [shape=star, inner sep=0pt] at (0, \i) (A-\i) {$A_\i$};
\node [shape=circle] at (5, \i) (B-\i) {$B_\i$};
}
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw (A-\i) to [cheating dash to=on 4pt off 2pt] (B-\j);
\end{tikzpicture}
\end{document}
Although I don't think decorations will provide sufficient accuracy in all cases, following the suggestions of Qrrbrbirlbel and Jake, the following seems to work. Note, that the cheating dash
is applied as a preaction. This is because the decoration uses \pgfsetdash
and this lasts until the end of the current scope (i.e., beyond the end of the path it is applied); preactions are applied within a separate scope but options such as color and line width must be passed to the preaction using the cheating dash
key.
\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\usetikzlibrary{shapes.geometric}
\pgfdeclaredecoration{cheating dash}{start}{
\state{start}[width=\pgfdecoratedremainingdistance, persistent precomputation={
\let\on=\pgfdecorationsegmentlength%
\let\off=\pgfdecorationsegmentamplitude%
\pgfmathparse{\pgfdecoratedremainingdistance-\on}\let\rest=\pgfmathresult%
\pgfmathparse{\on+\off}\let\onoff=\pgfmathresult%
\pgfmathparse{max(floor(\rest/\onoff), 1)}\let\nfullonoff=\pgfmathresult%
\pgfmathparse{max((\rest-\onoff*\nfullonoff)/\nfullonoff+\off, \off)}\let\offexpand=\pgfmathresult%
\pgfsetdash{{\on}{\offexpand}}{0pt}%
}]{\pgfsetpath\pgfdecoratedpath}
}
\begin{document}
\begin{tikzpicture}[every node/.style={draw},
cheating dash/.style args={on #1 off #2 with #3}{
preaction={decoration={cheating dash, segment length=#1, amplitude=#2}, decorate, draw, #3}
}]
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\path [cheating dash=on 4pt off 2pt with {}] (0,\i) .. controls ++(0,1) and ++(0,-1) .. (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}
\node [rectangle] at (0, \i) (A-\i) {$A_\i$};
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\path [cheating dash=on 4pt off 2pt with {blue}] (A-\i) .. controls ++(45:1) and ++(225:1) .. (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}{
\node [shape=star, inner sep=0pt] at (0, \i) (A-\i) {$A_\i$};
\node [shape=circle] at (5, \i) (B-\i) {$B_\i$};
}
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\path [cheating dash=on 4pt off 2pt with {red}] (A-\i) .. controls ++(-45+\j*10:1) and ++(235-\i*10:1) .. (B-\j);
\end{tikzpicture}
\end{document}
OK, a final version, following the comment of percusse, here is a version which doesn't require a preaction
. I'm not a fan of global assignments for this sort of thing, but it's the only way currently that I can see that it can be done. The result is the same as the picture above:
\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\usetikzlibrary{shapes.geometric}
\pgfdeclaredecoration{cheating dash}{start}{
\state{start}[width=\pgfdecoratedremainingdistance, persistent precomputation={
\let\on=\pgfdecorationsegmentlength%
\let\off=\pgfdecorationsegmentamplitude%
\pgfmathparse{\pgfdecoratedremainingdistance-\on}\let\rest=\pgfmathresult%
\pgfmathparse{\on+\off}\let\onoff=\pgfmathresult%
\pgfmathparse{max(floor(\rest/\onoff), 1)}\let\nfullonoff=\pgfmathresult%
\pgfmathparse{max((\rest-\onoff*\nfullonoff)/\nfullonoff+\off, \off)}\global\let\offexpand=\pgfmathresult%
}]{\pgfsetpath\pgfdecoratedpath}
}
\begin{document}
\tikzset{
cheating dash/.code args={on #1 off #2}{
\tikzset{decoration={cheating dash, segment length=#1, amplitude=#2}, decorate}%
% Use csname so catcode of @ doesn't have do be changed.
\csname tikz@addoption\endcsname{\pgfsetdash{{#1}{\offexpand}}{0pt}}%
}
}
\begin{tikzpicture}[every node/.style={draw}]
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw [cheating dash=on 4pt off 2pt] (0,\i) .. controls ++(0,1) and ++(0,-1) .. (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}
\node [rectangle] at (0, \i) (A-\i) {$A_\i$};
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw [cheating dash=on 4pt off 2pt, blue] (A-\i) .. controls ++(45:1) and ++(225:1) .. (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}{
\node [shape=star, inner sep=0pt] at (0, \i) (A-\i) {$A_\i$};
\node [shape=circle] at (5, \i) (B-\i) {$B_\i$};
}
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw [cheating dash=on 4pt off 2pt, red] (A-\i) .. controls ++(-45+\j*10:1) and ++(235-\i*10:1) .. (B-\j);
\end{tikzpicture}
\end{document}
Actually, here's another definition of the cheating dash
style which hacks into the decoration code without actually creating a decoration. It is used in the same way as the previous code.
\tikzset{
cheating dash/.code args={on #1 off #2}{
% Use csname so catcode of @ doesn't have do be changed.
\csname tikz@addoption\endcsname{%
\pgfgetpath\currentpath%
\pgfprocessround{\currentpath}{\currentpath}%
\csname pgf@decorate@parsesoftpath\endcsname{\currentpath}{\currentpath}%
\pgfmathparse{\csname pgf@decorate@totalpathlength\endcsname-#1}\let\rest=\pgfmathresult%
\pgfmathparse{#1+#2}\let\onoff=\pgfmathresult%
\pgfmathparse{max(floor(\rest/\onoff), 1)}\let\nfullonoff=\pgfmathresult%
\pgfmathparse{max((\rest-\onoff*\nfullonoff)/\nfullonoff+#2, #2)}\let\offexpand=\pgfmathresult%
\pgfsetdash{{#1}{\offexpand}}{0pt}}%
}
}
Based on the excellent answer of @Mark, here is a slightly different solution. It increases not only the distance between the dashes, but stretches (shrinking or expanding) the entire pattern by a factor. IMHO that's the way PSTricks does it.
For comparison, the result for the presented solution (stretch dash
) is shown on the left side of the image, on the right side is the proposed solution of Mark (cheating dash
).
\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\usetikzlibrary{shapes.geometric}
\makeatletter
% suggested answer: stretching the dash pattern by a factor
\tikzset{%
stretch dash/.code args={on #1 off #2}{%
\tikz@addoption{%
\pgfgetpath\currentpath%
\pgfprocessround{\currentpath}{\currentpath}%
\pgf@decorate@parsesoftpath{\currentpath}{\currentpath}%
\pgfmathparse{max(round((\pgf@decorate@totalpathlength-#1)/(#1+#2)),0)}%
\let\npattern=\pgfmathresult%
\pgfmathparse{\pgf@decorate@totalpathlength/(\npattern*(#1+#2)+#1)}%
\let\spattern=\pgfmathresult%
\pgfsetdash{{\spattern*#1}{\spattern*#2}}{0pt}%
}%
}%
}
% last version of @Mark Wibrow
\tikzset{
cheating dash/.code args={on #1 off #2}{
\tikz@addoption{%
\pgfgetpath\currentpath%
\pgfprocessround{\currentpath}{\currentpath}%
\pgf@decorate@parsesoftpath{\currentpath}{\currentpath}%
\pgfmathparse{\pgf@decorate@totalpathlength-#1}%
\let\rest=\pgfmathresult%
\pgfmathparse{#1+#2}%
\let\onoff=\pgfmathresult%
\pgfmathparse{max(floor(\rest/\onoff), 1)}%
\let\nfullonoff=\pgfmathresult%
\pgfmathparse{max((\rest-\onoff*\nfullonoff)/\nfullonoff+#2,#2)}%
\let\offexpand=\pgfmathresult%
\pgfsetdash{{#1}{\offexpand}}{0pt}%
}%
}%
}
\makeatother
% comparison based on the examples of @Mark Wibrow
\newcommand{\bookend}{{\color{blue}\rule{1pt}{1ex}}}
\newcommand{\tikzdash}[2]{%
\bookend%
\tikz[baseline=-.5ex, trim left, trim right=#1]{%
\draw[line width=2pt,#2] (0,0) --
(#1,0);}%
\bookend}
\newcommand*\dashtest[1]{%
\begin{tikzpicture}[y=-7.5pt]
\node at (0,30pt) {\tiny\texttt{#1}};
\foreach \i [count=\y from 0] in {1,...,50}
\node [anchor=west, label=west:\tiny\i pt] at (0,\y)
{\tikzdash{\i pt}{#1}};
\end{tikzpicture}
}
\newcommand*\shapetest[1]{%
\begin{tikzpicture}[every node/.style={draw}]
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw [#1] (0,\i) .. controls ++(0,1) and
++(0,-1) .. (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}
\node [rectangle] at (0, \i) (A-\i) {$A_\i$};
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw [#1, blue] (A-\i) .. controls ++(45:1)
and ++(225:1) .. (5, \j);
\tikzset{yshift=-5cm}
\foreach \i in {1,...,5}{
\node [shape=star, inner sep=0pt] at (0, \i) (A-\i) {$A_\i$};
\node [shape=circle] at (5, \i) (B-\i) {$B_\i$};
}
\foreach \i in {1,...,5}
\foreach \j in {1,...,5}
\draw [#1, red] (A-\i) .. controls
++(-45+\j*10:1) and ++(235-\i*10:1) .. (B-\j);
\end{tikzpicture}
}
\begin{document}
\shapetest{stretch dash=on 10pt off 3pt}
\dashtest{stretch dash=on 10pt off 3pt}
\dashtest{cheating dash=on 10pt off 3pt}
\shapetest{cheating dash=on 10pt off 3pt}
\end{document}