17

How can I (Is it possible to) make a decoration style which takes as arguement one path (or possibly more, that would be perfect), and makes an arc, snake, ellipse (whatever), where the paths intersect each other? I've seen These two ways of doing this:

But they are rather exaustive especially if one path has more than one intersection with the other, then, this can become very exaustive. Furthemore, they're mostly restricted to straight lines.

First attempt:

Inspired by Emma's answer to this question I was able to put together a MWE, but it's still far from good as it does not support curved paths and the lines do not join (if multiple coordinates are use to construct the path). Other than that, there's no control over the jump size...

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations.pathreplacing,intersections,calc}
\tikzset{
    over path/.style={
        decoration={show path construction, lineto code={
          \path[name path=this path] (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);
          \path[name intersections={of=this path and #1, total=\t}, /utils/exec={\global\let\t=\t}]%
                                    let \n1={int(\t+1)} in%
                                    (\tikzinputsegmentfirst) coordinate (int-0)%
                                    foreach \i in {1,...,\n1}{%
                                      \ifnum \i<\n1%
                                          (intersection-\i) coordinate (int-\i)%
                                      \else
                                          (\tikzinputsegmentlast) coordinate (int-\n1)%
                                      \fi};
          \draw (\tikzinputsegmentfirst) foreach[remember=\i as \last (initially 0)] \i in {1,...,\t}{%
            let \p1=($(int-\last)-(int-\i)$), \n1={veclen(\x1,\y1)}, \n2={abs(4pt)}, \n3={\i+1}  in%
            [rounded corners=\n2/4] -- ($(int-\last)!\n1-\n2!(int-\i)$) to[bend left=90, looseness=1.7] ($(int-\last)!\n1+\n2!(int-\n3)$)} -- (\tikzinputsegmentlast);
                }
            },
            decorate
        }
    }

\begin{document}
\begin{tikzpicture}
    \coordinate (a) at (-1,0.5);
    \coordinate (b) at (8,0.5);
    \coordinate (c) at (3,-0.5);
    \draw[ultra thick, name path=sine, domain=-1:8, smooth, samples=50] plot (\x,{sin(\x r)});
    \draw[over path=sine] (a) -- (c) |- (b);
\end{tikzpicture}
\end{document}

enter image description here

Bad line join:

enter image description here

  • 1
    Have you looked at the knots library? – cfr Nov 14 '16 at 17:30
  • @cfr, Didn't know about that. Just looked at it now (briefly), it seems to me that it can compute the intersections of paths and put one over another, but does not do the "jumping over". That's really quite useful, thank's for the reference, but I think it can't give me what I'm looking for... =/ – Guilherme Zanotelli Nov 14 '16 at 19:06
  • I really meant at how it does it - not that it would do exactly what you need. – cfr Nov 14 '16 at 21:23
  • @cfr, ohh yes! Apparently there is a very good way to do what I want. knots library uses the spath3 package to keep track of the constructed path (which is part of what I wish). Unfortunately the documentation is not so straightfoward -- at least for me. I'll try to make something out of it, thanks for the reference! – Guilherme Zanotelli Nov 15 '16 at 6:50
  • 3
    If you use line cap=round then there is not join problem. – Symbol 1 Nov 18 '16 at 23:29
9

Here's a solution. It's fully automatic and should well even for curved lines. The notation is avoid intersection={name of other path}{drawing options}. It finds the list of intersection points, then executes a decoration that measures the distance to the next intersection point and when gets close, does an evasive maneuver. Using the options avoid intersect amplitude, avoid intersect width, and avoid intersect offset you can adjust the evasive maneuver.

It's not perfect. It is very slow (on my computer, the two examples take 1.4 seconds and 3.8 seconds respectively), which may be unavoidable. Also, if you include the smooth option in the second example below it breaks with a "dimension too large" error. I have no idea why. I think the output looks very good.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations}
\usetikzlibrary{intersections}

\makeatletter
\tikzset{
  avoid intersection amplitude/.store in=\avint@amplitude,
  avoid intersection width/.code={\edef\avint@width{\dimexpr#1/2}},
  avoid intersection offset/.store in=\avint@offset,
  avoid intersection has corners/.code={\pgfdecoratepathhascornerstrue}
}
\def\avint@amplitude{5pt}
\def\avint@width{5pt}
\def\avint@offset{0pt}

\pgfdeclaredecoration{avoidintersect}{initial}{
  \state{initial}[width=\pgfdecoratedinputsegmentlength/100,next state=measure]
  {
    \gdef\avint@intersectionnumber{1}
    \pgfpathlineto{\pgfpointorigin}
  }

  \state{measure}[width=\pgfdecoratedinputsegmentlength/100,next state=wait,auto corner on length=2pt,
               persistent postcomputation=\let\pgf@decorate@next@state\avint@smuggle@pgf@decorate@next@state]
  {
    \pgfpathlineto{\pgfpointorigin}
    \pgfgettransform\avint@temptransform
    \pgftransforminvert
    \pgfpointintersectionsolution{\avint@intersectionnumber}
    \pgf@pos@transform{\pgf@x}{\pgf@y}
    \pgfmathveclen{\pgf@x}{\pgf@y}
    \pgfsettransform\avint@temptransform
    \xdef\avint@waitcycles{\the\numexpr\dimexpr\pgfmathresult pt-\avint@width*3-\avint@offset\relax/\dimexpr\pgfdecoratedinputsegmentlength/100\relax}
    \ifnum\avint@waitcycles>50\relax\gdef\avint@waitcycles{50}\fi
    \global\let\avint@smuggle@pgf@decorate@next@state\pgf@decorate@next@state        
    \ifdim\pgfmathresult pt<\dimexpr\avint@width+\avint@offset\relax
        \gdef\avint@smuggle@pgf@decorate@next@state{zig}
    \else
        \ifdim\pgfmathresult pt<\dimexpr\avint@width*2+\avint@offset\relax
            \gdef\avint@smuggle@pgf@decorate@next@state{measure}    
        \fi
    \fi        
  }

  \state{wait}[width=\pgfdecoratedinputsegmentlength/100,next state=measure,repeat state=\avint@waitcycles,auto corner on length=2pt]{
    \pgfpathlineto{\pgfpointorigin}
  } 

  \state{zig}[width=\avint@width, next state=zag]{
    \pgfpathcurveto{\pgfqpoint{\avint@width}{0cm}}{\pgfqpoint{0pt}{\avint@amplitude}}{\pgfqpoint{\avint@width}{\avint@amplitude}}
  }
  \state{zag}[width=\avint@width, next state=measure,
              persistent postcomputation=\let\pgf@decorate@next@state\avint@smuggle@pgf@decorate@next@state]{
    \pgfpathcurveto{\pgfqpoint{\avint@width}{\avint@amplitude}}{\pgfqpoint{0pt}{0cm}}{\pgfqpoint{\avint@width}{0pt}}
    \xdef\avint@intersectionnumber{\the\numexpr\avint@intersectionnumber+1}
    \global\let\avint@smuggle@pgf@decorate@next@state\pgf@decorate@next@state
    \ifnum\avint@intersectionnumber>\pgfintersectionsolutions
      \gdef\avint@smuggle@pgf@decorate@next@state{done}
    \fi
  }

  \state{done}[width=\pgfdecoratedinputsegmentlength/100,auto corner on length=2pt]{
    \pgfpathlineto{\pgfpointorigin}
  }

  \state{final}{\pgfpathlineto{\pgfpointdecoratedpathlast}}
}    

\tikzset{
    avoid intersection/.code 2 args={
        \pgfkeysalso{name path=avint@temp,draw=none}
        \expandafter\def\expandafter\tikz@postactions\expandafter{\tikz@postactions
            \pgfintersectionsortbyfirstpath
            \tikz@intersect@namedpaths
            \pgfintersectionofpaths
                {\pgfsetpath\tikz@intersect@path@name@avint@temp}
                {\expandafter\pgfsetpath\csname tikz@intersect@path@name@#1\endcsname}
            \begin{pgfdecoration}{{avoidintersect}{\pgfdecoratedpathlength}}
            \pgfsetpath\tikz@intersect@path@name@avint@temp
            \end{pgfdecoration}
            \pgfgetpath\avint@temp@path
            \pgfusepath{discard}
            \draw[/utils/exec={\tikz@addmode{\pgfsyssoftpath@setcurrentpath\avint@temp@path}},#2](0,0)rectangle (0,0);%
        }
    }
}
\makeatother

\begin{document}
\begin{tikzpicture}
    \coordinate (a) at (-1,0.5);
    \coordinate (b) at (8,0.5);
    \coordinate (c) at (3,-0.5);
    \draw[ultra thick, name path=sine, domain=-1:8, smooth, samples=50] plot (\x,{sin(\x r)});
    \draw[avoid intersection={sine}{white,double=black},avoid intersection has corners] (a) -- (c) |- (b);
\end{tikzpicture}

\begin{tikzpicture}
    \coordinate (a) at (-1,0.5);
    \coordinate (b) at (8,0.5);
    \coordinate (c) at (3,-0.5);
    \draw[ultra thick,name path=line] (a) -- (c) |- (b);
    \draw[avoid intersection={line}{white,double=black},avoid intersection offset=1.5pt,domain=-1:8,samples=50] plot (\x,{sin(\x r)});
\end{tikzpicture}

\end{document}

Here's the output:

You can see that the line join works fine: enter image description here

  • Wow! Very nice, thank you for all the effort! For me the time is not really an issue, one can suppress the decorations for the draft and only apply it in the end... Any idea why the joins are a little bit weird? They look... broken? – Guilherme Zanotelli Feb 3 '18 at 22:30
  • @GuilhermeZanotelli Should be fixed. I added the option avoid intersection has corners for this. The bottom corner still isn't quite as good as an undecorated corner, but I can't make it any better. – Hood Chatham Feb 3 '18 at 23:07
  • Oh yeah. Now it's perfect! Real nice job, thanks for that! Now my circuit drawings will be just as i want them to be without such great efforts... =D – Guilherme Zanotelli Feb 4 '18 at 22:31

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.