2

This is a question that's been asked twice before, but both of the previous solutions involve clipping; my paths are more complicated and I have a lot of them, the clipping solutions provided here and here don't work for me because they rely on pre-computing an offset to define a clip path encompassing "one side" of the path.

Here's my current MWE, I use the curveto decoration and the raise option to shift the curve to one side of the path or the other. It almost works, and I'd like to know how to address these two problems

  1. Artifacts at the endpoints (seen in the render). How do I get rid of those?
  2. The code is really untikz, I'd like to replace the macro \sidebyside with something that looks like a tikz command.

MWE

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\usetikzlibrary{decorations.pathmorphing}

    \tikzset{
        draw left/.style={
            decorate,
            decoration={
                curveto,
                raise={.5*\the\pgflinewidth}
            }
        },
        draw right/.style={
            decorate,
            decoration={
            curveto,
            raise={-.5*\the\pgflinewidth}}
        }
    }
    \newcommand{\sidebyside}[3]{%
        \draw [draw left, #1] #3;%
        \draw [draw right, #2] #3;%
    }

\begin{document}
\begin{tikzpicture}[scale=4, line width=6pt]
    \useasboundingbox (-.3,-.3) rectangle (1.3, .3);[!
    % In practice this path is going to be determined 
    % by node positions and bending commands that makes        
    % using it as one side of a clip region (as in the 
    % previous solutions to this problem) challenging.

    % Not repeating the path computation and having a 
    % more tikz syntax than this macro would a bonus
    \sidebyside{densely dotted, red}{blue}{(0,0) -- (1,0)}
\end{tikzpicture}
\end{document}
6

As for your question to make this more TikZy: this can be achieve with the show path construction decoration.

UPDATE: I tried to understand where the "feature" comes from and believe to have understood things well enough to remove it. It comes from the \pgfpathlineto{\pgfpointdecoratedpathlast} in \pgfdeclaredecoration{curveto}{initial}{...}. Why is that? All points on the curve are raised. When connecting to the last point, TikZ thus sees a slope, and correspondingly tilts the straight segment. The really clean and "once and for all" fix would be to convince TikZ that there is no slope, something that requires major surgery and is likely to break something that worked before. You may, though, make a feature request at the pgf GitHub site. For the time being, I propose the following "minimal invasive" fix:

  1. Declare a "new" curveto decoration cappedcurveto that does not draw the last stretch.
  2. Shorten the ends of the paths by the corresponding negative amount.

This works, at least in the examples. The show path construction decoration is used to make things more TikZy, all you need to do is to say

\draw[left side/.style={densely dotted, orange},side by side](0,-0.3) to[bend right] (1,-0.3);

Full example

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations,decorations.pathmorphing,decorations.pathreplacing}
\makeatletter
% This is the original curveto decoration:
% 
% \pgfdeclaredecoration{curveto}{initial}{%
%   \state{initial}[width=\pgfdecoratedinputsegmentlength/100]
%   {
%     \pgfpathlineto{\pgfpointorigin}
%   }%
%   \state{final}{\pgfpathlineto{\pgfpointdecoratedpathlast}}%
% }%
% the "feature" comes from the \pgfpathlineto{\pgfpointdecoratedpathlast}
% which adds a line from the last *raised* point on the curve to 
% to the last point of the path.
% To "repair" it we drop the last lines
\pgfdeclaredecoration{cappedcurveto}{initial}{%
  \state{initial}[width=\pgfdecoratedinputsegmentlength/100]
  {
    \pgfpathlineto{\pgfpointorigin}
  }%
  \state{final}{%\pgfpathmoveto{\pgfpointdecoratedpathlast}
  }%
}%
\makeatother

    \tikzset{
        draw left/.style={
            decorate,
            decoration={
                cappedcurveto,
                raise={.5*\pgflinewidth}
            }
        },
        draw right/.style={
            decorate,
            decoration={
            cappedcurveto,
            raise={-.5*\pgflinewidth}}
        },
        side by side/.style={decoration={show path construction,
         curveto code={
          \draw[draw left,left side]
           (\tikzinputsegmentfirst) .. controls
        (\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)
        ..(\tikzinputsegmentlast);
          \draw[draw right,right side]
          (\tikzinputsegmentfirst) .. controls
        (\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)
        ..(\tikzinputsegmentlast);},
        lineto code={
          \draw[draw left,left side]
           (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);
          \draw[draw right,right side]
          (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);}
        },decorate}
    }

\begin{document}
\begin{tikzpicture}[scale=4, line width=6pt,shorten >=-\pgflinewidth/2,right side/.style={blue}]
    \draw[left side/.style={densely dotted, red},side by side](0,0) to (1,0);
    \draw[left side/.style={densely dotted, orange},side by side](0,-0.3) to[bend right] (1,-0.3);
    \draw[left side/.style={densely dotted, yellow},side by side](0,0.3) to[bend left] (1,0.3);
\end{tikzpicture}
\end{document}

enter image description here

As you can see, the "feature" is still there. A way to remove it would be to use a full-fledged decoration automaton. The "feature" is gone. ;-)

I'd also like to advertise something that works also for more complicated paths, and uses decorations.markings to raise and lower the path. It is largely taken from here, where you can find more applications thereof.

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations.markings,decorations.pathreplacing}
\newcounter{parrow}
\tikzset{record path/.style={/utils/exec=\tikzset{parrow/.cd,#1},
  decorate,decoration={markings,mark=at position 0 with
    {\setcounter{parrow}{1}%\typeout{\pgfdecoratedpathlength}
    \path (0,\pgfkeysvalueof{/tikz/parrow/dist}/2) coordinate (parrowt-\pgfkeysvalueof{/tikz/parrow/name}-\number\value{parrow})
   (0,-\pgfkeysvalueof{/tikz/parrow/dist}/2)coordinate (parrowb-\pgfkeysvalueof{/tikz/parrow/name}-\number\value{parrow});
     \pgfmathsetmacro{\mystep}{(\pgfdecoratedpathlength-4pt)/int(1+(\pgfdecoratedpathlength-4pt)/2pt)}
     \xdef\mystep{\mystep}},
  mark=between positions 2pt and 1 step \mystep pt with {\stepcounter{parrow}%
   \path (0,\pgfkeysvalueof{/tikz/parrow/dist}/2) coordinate (parrowt-\pgfkeysvalueof{/tikz/parrow/name}-\number\value{parrow})
   (0,-\pgfkeysvalueof{/tikz/parrow/dist}/2)coordinate (parrowb-\pgfkeysvalueof{/tikz/parrow/name}-\number\value{parrow});}
    }},reconstruct top/.style={insert path={plot[variable=\t,samples at={1,...,\number\value{parrow}},smooth] (parrowt-#1-\t)}},
  reconstruct bottom/.style={insert path={plot[variable=\t,samples at={\number\value{parrow},\the\numexpr\value{parrow}-1,...,1},smooth]
  (parrowb-#1-\t)}},
  parrow/.cd,dist/.initial=6pt,step/.initial=2pt,name/.initial={}}
\tikzset{back and forth/.style={decoration={show path construction,
    moveto code={},
    lineto code={
      \path[record path={name=tmp}] (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);
      \draw[back,reconstruct top=tmp,shorten >=-0.67pt,shorten <=-0.67pt];
      \draw[forth,reconstruct bottom=tmp,shorten >=-0.67pt,shorten <=-0.67pt];
    },
    curveto code={
      \path[record path={name=tmp}] (\tikzinputsegmentfirst) .. controls
        (\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)
        ..(\tikzinputsegmentlast);
      \draw[back,reconstruct top=tmp,shorten >=-0.67pt,shorten <=-0.67pt];
      \draw[forth,reconstruct bottom=tmp,shorten >=-0.67pt,shorten <=-0.67pt];
    },
    closepath code={
      \path[record path={name=tmp}] (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);
      \draw[back,reconstruct top=tmp,shorten >=-0.67pt,shorten <=-0.67pt];
      \draw[forth,reconstruct bottom=tmp,shorten >=-0.67pt,shorten <=-0.67pt];} },decorate},back/.style={densely
dotted,red},forth/.style={blue}}

\begin{document}
\begin{tikzpicture}[scale=4, line width=6pt,parrow/dist=6pt]
    \path[back and forth] (0,0) to[out=0,in=0] (1,-1) to[out=180,in=180]
    (2,0);
\end{tikzpicture}
\end{document}

enter image description here

This raises the question whether or not one can combine these things to provide us with one smooth continuous path. The answer is yes. This variant comes with two options. Option 1 is clumsy and safe(r). Option 2 is less clumsy but can backfire. It is (almost) as bad as using \pgfextra for a path. So if you use it, be warned. (A technical aside: the more recent versions of TikZ come with a use path key. However, this is not the use path variant introduced in Qrrbrbirlbel's nice answer. For the following to work, we need Qrrbrbirlbel's variant, which is called reuse path. The now official use path won't do the job. If you want to know why, make a major donation to the marmot recovery foundation. ;-))

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\makeatletter
\pgfdeclaredecoration{cappedcurveto}{initial}{%
  \state{initial}[width=\pgfdecoratedinputsegmentlength/100]
  {
    \pgfpathlineto{\pgfpointorigin}
  }%
  \state{final}{%\pgfpathmoveto{\pgfpointdecoratedpathlast}
  }%
}%
\tikzset{reuse path/.code={%
    \pgfsyssoftpath@setcurrentpath{#1}}
}% unfortunately this is *not* the definition of the `use path` key that has
% been added to the pgfmanual!
\makeatother

    \tikzset{
        draw left/.style={
            decorate,
            decoration={
                cappedcurveto,
                raise={.5*\pgflinewidth}
            }
        },
        draw right/.style={
            decorate,
            decoration={
            cappedcurveto,
            raise={-.5*\pgflinewidth}}
        },
        unsafe side by side/.style={save path=\pathA,
        /utils/exec={\draw[right side,draw right,reuse path=\pathA];
    \draw[left side,draw left,reuse path=\pathA];}}
    }
\begin{document}
\begin{tikzpicture}[scale=4, line width=6pt,shorten >=-\pgflinewidth/2,right
side/.style={blue},left side/.style={densely dotted, red}]
    \path[save path=\pathA]  (0,0) to[out=0,in=0] (1,-1) to[out=180,in=180]
    (2,0);
    \draw[right side,draw right,reuse path=\pathA];
    \draw[left side,draw left,reuse path=\pathA];
\end{tikzpicture}

\begin{tikzpicture}[scale=4, line width=6pt,shorten >=-\pgflinewidth/2,right
side/.style={blue},left side/.style={densely dotted, red}]
    \path[unsafe side by side]   (0,0) to[out=0,in=0] (1,-1) to[out=180,in=180]
    (2,0);
\end{tikzpicture}
\end{document}

enter image description here

This finally allows us to address Ulrike Fischer's request.

\documentclass[border=10pt,tikz]{standalone}
\usepackage{tikzlings}
\usetikzlibrary{decorations,decorations.markings}
\makeatletter
\pgfdeclaredecoration{cappedcurveto}{initial}{%
  \state{initial}[width=\pgfdecoratedinputsegmentlength/100]
  {
    \pgfpathlineto{\pgfpointorigin}
  }%
  \state{final}{%\pgfpathmoveto{\pgfpointdecoratedpathlast}
  }%
}%
\tikzset{reuse path/.code={%
    \pgfsyssoftpath@setcurrentpath{#1}}
}% unfortunately this is *not* the definition of the `use path` key that has
% been added to the pgfmanual!
\makeatother
    \tikzset{
        draw left/.style={
            decorate,
            decoration={
                cappedcurveto,
                raise={.5*\pgflinewidth}
            }
        },
        draw right/.style={
            decorate,
            decoration={
            cappedcurveto,
            raise={-.5*\pgflinewidth}}
        },
        unsafe side by side/.style={save path=\pathA,
        /utils/exec={\draw[right side,draw right,reuse path=\pathA];
    \draw[left side,draw left,reuse path=\pathA];}}
    }
\def\LstTikZlings{"bear","penguin","marmot","owl","koala","coati","snowman","mouse","moles","sloth","pig","cat","hippo","rhino"}
\pgfmathtruncatemacro{\nTikZlings}{dim({\LstTikZlings})}
\begin{document}
\foreach \X in {1,...,\nTikZlings}
{\begin{tikzpicture}[scale=4, line width=6pt,shorten >=-\pgflinewidth/2,right
side/.style={blue},left side/.style={densely dotted, red}]
    \path[save path=\pathA]  (0,0) to[out=0,in=0] (1,-1) to[out=180,in=180]
    (2,0);
    \draw[right side,draw right,reuse path=\pathA];
    \draw[left side,draw left,reuse path=\pathA,save path=\pathL];
    \path[postaction={decorate,decoration={markings,
    mark=between positions 3pt and 1 step 7pt with {
    \pgfmathtruncatemacro{\myindex}{int(mod(\X+\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}-1,\nTikZlings))}
    \pgfmathsetmacro{\mytikzling}{{\LstTikZlings}[\myindex]}
    \ifnum\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}=1
    \expandafter\csname\mytikzling\endcsname[scale=0.11,yshift=-12pt,rotate=-90]
    \else
    \expandafter\csname\mytikzling\endcsname[scale=0.11,yshift=-12pt]
    \fi
    }}},reuse path=\pathL];
\end{tikzpicture}}
\end{document}

enter image description here

  • and now a small tikzling in every compartment and then let it move ;-) – Ulrike Fischer Sep 14 '19 at 11:08
  • There is an open issue along those lines: github.com/pgf-tikz/pgf/issues/622 – Henri Menke Sep 14 '19 at 23:09
  • @HenriMenke Thanks! But I do not quite understand the purpose of the issue. This can be done with a simple \clip or reverseclip. Of course it can also be done with the above, I think. – Schrödinger's cat Sep 14 '19 at 23:15
  • @Schrödinger'scat I was trying to do it with a decoration but that resulted in an overshoot at sharp corners: tex.stackexchange.com/questions/291072/… – Henri Menke Sep 14 '19 at 23:23
  • @HenriMenke The capped decorations do no better in this regard, they are also not designed for this. What is wrong with clipping? – Schrödinger's cat Sep 14 '19 at 23:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.