1

I want to make a decoration where successive decoration segments are connected by a Bézier curve whose first control point is defined relative to the first decoration segment, while the second point is defined relative to the second segment. To make a simple example, here is my attempt at a decoration which creates a series of curves which:

  1. Start at a point offset by the decoration amplitude from the generating path, at the start point of decoration segment n, i.e., at the point (0, \pgfdecorationsegmentamplitude) in the coordinates of decoration segment n;
  2. Have the first control point on the generating path at the point (0,0) in the coordinates of decoration segment n;
  3. Have the second control point on the generating path at the point (0,0) in the coordinates of decoration segment n+1;
  4. End at a point analogous to the start point, but at the next position along the curve, at point (0, \pgfdecorationsegmentamplitude) in the coordinates of decoration segment n+1.

This should make a series of very sharp points, whose two sides begin co-linear. The curves between successive points should be smooth.

edit: Here is the desired result drawn explicitly (i.e., not using a decoration) on a straight line. The generating path is dashed. But, note that this desired result is an example; what I really want is just a mechanism for passing points from one step in the decoration automaton to the next.

\begin{tikzpicture}
\draw[dashed] (0,0) -- (20em,0);
\draw (0,2 em)
  \foreach \x in {1, 2, ..., 20}
{
 .. controls (\x em - 1 em,0 em) and (\x em,0em) .. (\x em,2 em)
} ;
\end{tikzpicture}

enter image description here

The curve needs to actually be drawn in segment $n+1$, when the second control point and endpoint can be calculated. The start point is not a problem, because it is the current end point of the path and thus does not need to be specified in \pgfpathcurveto. The difficulty is how to pass the first control point, which is calculated in the coordinate system of n, to segment n+2.

I know about the persistent precomputation and persistent postcomputation options, which can be used to execute code whose results will persist to the next state, but I don't know how to get the point location into the persistent postcomputation code, which is executed outside the TEX-group where the coordinate transformation for the state is defined. My attempt calls \pgfpoint inside the state code, where it can use the transform to calculate \pgf@x and \pgf@y, and then tries to capture the values using \pgfgetlastxy in the persistent postcomputation. However, this doesn't work, I think because values of \pgf@x and \pgf@y set inside a TEX-group are local to that TEX-group. I tried using \pgf@process to make the the values global. This works in my test case:

\documentclass{article}
\usepackage{tikz}
\begin{document}
\pgfpoint{0}{0}
\begingroup
    \pgfpoint{2}{3}
\endgroup
\pgfgetlastxy{\lastx}{\lasty}
$\\lastx$ is \lastx; $\\lasty$ is \lasty.
\begingroup
    \makeatletter
    \pgf@process{
        \pgfpoint{2}{3}
    }
\endgroup
\pgfgetlastxy{\lastx}{\lasty}
$\\lastx$ is \lastx; $\\lasty$ is \lasty.
\end{document}

enter image description here

However, in the context of the decoration, the \pgf@process raises an Undefined control sequence error.

edit: Mark Wibrow pointed out that the \makeatletter needed to be outside the decoration definition. After changing that, \pgf@process works without an error, and it does appear that the point is being captured by the persistent postcalculation. However, what is being captured is that point's coordinates in the reference frame of segment n, which is (0,0). (I have confirmed that the point is really being captured, by changing the values to something else). Then, when I refer to it in segment n+1, it is (0,0) in the coordinates of n+1, even though I use \pgftransformreset before declaring the point. Maybe there's something I don't understand about the way the transformations are implemented? I thought that a call to \pgfpoint applies the current transformation matrix to the coordinates you give it, and then sets the values of \pgf@x and \pgf@y accordingly. If that's true, why is the point I get still (0,0)? And why isn't \pgftransformreset working? I get exactly the same result whether I use it or not.

(I have also simplified the example code even more; it had some remnants of my more complicated original design left.)

\usetikzlibrary{decorations}
\newdimen\ya
\newdimen\xa
\makeatletter
\pgfdeclaredecoration{test}{initial}
{
\state{initial}[%
    width=\pgfdecorationsegmentlength, %
    next state=curve, %
    persistent postcomputation={
        %store the next control point
        \pgfgetlastxy{\xa}{\ya}
    }
]
    {
    \pgfpathmoveto{\pgfpoint{0}{\pgfdecorationsegmentamplitude}}
    %calculate the next control point
   \pgf@process{\pgfpoint{0}{0}}
}
%
\state{curve}[%
    width=\pgfdecorationsegmentlength,
    next state=curve,
    persistent postcomputation={
        %store the next control point
        \pgfgetlastxy{\xa}{\ya}
    }
]{
 %draw the curve
    \pgfpathcurveto%
        {
        \pgfgettransform{\oldtransform}
             \pgftransformreset
             \pgfpoint{\xa}{\ya}
             \pgfsettransform{\oldtransform}
             }%
        {\pgfpoint{0}{0}}%
        {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}%
  % set the next control point
 \pgf@process{\pgfpoint{0}{0}}
 }
\state{final}
{
 %draw the curve
 \pgfpathcurveto%
    {\pgfgettransform{\oldtransform}
     \pgftransformreset
     \pgfpoint{\xa}{\ya}
     \pgfsettransform{\oldtransform}}%
    {\pgfpoint{0}{0}}%
    {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}%
}
}
\makeatother
\begin{tikzpicture}[decoration={test, segment length=3em, amplitude=2em}]
    \draw[decorate] (0,0) .. controls (3,3) .. (10,0);
\end{tikzpicture}

enter image description here

I am aware that it might be possible to to this by setting up a series of named coordinates, and then actually drawing the whole curve in the final segment, but I'd like a solution which is less hacky.

Solution (Thanks to Mark): I was incorrect that \pgfpoint automatically applies the current transform. This is actually done by the various drawing commands. To apply the current transform to a point, there is \pgfpointtransformed, which seems to not be in the documentation? This needs to be used twice, once to apply the transform to the control point when it is generated in segment n, and again to invert the transform for segment n+1, which will be applied by \pgfpathcurveto. Here is the correct code:

\documentclass{article}
\usepackage[latin1]{inputenc}
\usepackage{tikz}
\usetikzlibrary{decorations,intersections}

\begin{document}
\newdimen\ya
\newdimen\xa
\makeatletter
\pgfdeclaredecoration{test}{initial}
{
\state{initial}[%
    width=\pgfdecorationsegmentlength, %
    next state=curve, %
    persistent postcomputation={
        %store the next control point
        \pgfgetlastxy{\xa}{\ya}
    }
]{
    \pgfpathmoveto{\pgfpoint{0}{\pgfdecorationsegmentamplitude}}
    %calculate the next control point
    \pgf@process{\pgfpointtransformed{\pgfpoint{0}{0}}}
}
%
\state{curve}[%
    width=\pgfdecorationsegmentlength,
    next state=curve,
    persistent postcomputation={
        %store the next control point
        \pgfgetlastxy{\xa}{\ya}
    }
]{
 %draw the curve
 \pgfpathcurveto%
  {
      \pgfgettransform{\oldtransform}
      \pgftransforminvert
      \pgfpointtransformed{\pgfpoint{\xa}{\ya}}
      \pgfsettransform{\oldtransform}
  }%
  {\pgfpoint{0}{0}}%
    {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}%
 %calculate the next control point
 \pgf@process{\pgfpointtransformed{\pgfpoint{0}{0}}}
 }
\state{final}
{
 %draw the curve
 \pgfpathcurveto%
    {\pgfgettransform{\oldtransform}
     \pgftransforminvert
     \pgfpointtransformed{\pgfpoint{\xa}{\ya}}
     \pgfsettransform{\oldtransform}}%
    {\pgfpoint{0}{0}}%
    {\pgfpoint{0}{\pgfdecorationsegmentamplitude}}%
 %finish up
 \pgfpathquadraticcurveto%
    {\pgfpoint{0}{0}}
    {\pgfpointdecoratedpathlast}
}
}
\makeatother
\begin{tikzpicture}[decoration={test, segment length=3em, amplitude=2em}]
    \draw[decorate] (0,0) .. controls (3,3) .. (10,0);
\end{tikzpicture}
\end{document}

enter image description here

  • can you sketch what the end curve would look like ? I couldn't understand from the spec – percusse Nov 25 '16 at 9:30
  • Sure, I made an example of what it should look like applied to a straight line. The goal would be for it to also be able to follow a curve. – brendan Nov 25 '16 at 9:57
  • Does \pgfpointtransformed{\pgfpoint{0}{0}} (or the more efficient \pgfpointtransformed{\pgfpointorigin}) get what you want? \pgfpointtransformed uses \pgf@process internally. Maybe you don't want to reset the the transformation matrix but apply it to the point to get the "absolute" coordinate? – Mark Wibrow Nov 25 '16 at 14:31
4

There are a couple of issues here.

Firstly, you need to place the \makeatletter before the decoration declaration.

By the time the state code is processed it has already been parsed and the prevailing category code 12 has been assigned to the @ character. Thus, when the decoration is run TeX is not looking for \pgf@process but \pgf, hence the Undefined control sequence errors.

Putting \makeatetter before the decoration declaration gets rid of the errors. But I'm not sure if resulting path is what is required.

Secondly, you probably need to apply the 'global' transformation matrix to to the point defined inside the decoration state to get the 'absolute' coordinate

 \pgfpointtransformed{\pgfpointorigin} 

will do this for you.

Note, that it may be more efficient to simply use a coordinate node. The position of coordinate nodes (as with all nodes) are saved 'absolutely' (i.e., the global transformation matrix is applied to whatever position is specified). There may be some overhead as nodes define several anchors (e.g., north, south etc) which are not needed. But using

\pgfcoordinate{@1}{\pgfpoint{0pt}{0pt}}

inside a decoration state will associate the location absolutely with the coordinate note named @1 and the exact position can be obtained later (inside or outside of a decoration state) using:

\pgfpointanchor{@1}{center}

1

I think you don't need all the pre and post persistant computation to define the control points, you could try using the segment length and amplitude.

The following code outputs this:

enter image description here

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\makeatletter
\pgfdeclaredecoration{test}{initial}{%
\state{initial}[%
    width=\pgfdecorationsegmentlength, %
    next state=curve]{%
    \pgfpathmoveto{\pgfpoint{0}{\pgfdecorationsegmentamplitude}}
    \pgfpathcurveto%
    {\pgfpoint{0pt}{-\pgfdecorationsegmentamplitude/3}}% support a
    {\pgfpoint{\pgfdecorationsegmentlength}{-\pgfdecorationsegmentamplitude/3}}% support b
    {\pgfpoint{\pgfdecorationsegmentlength}{\pgfdecorationsegmentamplitude}}% coordinate
}
\state{curve}[%
    width=\pgfdecorationsegmentlength,
    next state=curve]{%
 %draw the curve
  \pgfpathcurveto%
  {\pgfpoint{0pt}{-\pgfdecorationsegmentamplitude/3}}% support a
  {\pgfpoint{\pgfdecorationsegmentlength}{-\pgfdecorationsegmentamplitude/3}}% support b
  {\pgfpoint{\pgfdecorationsegmentlength}{\pgfdecorationsegmentamplitude}}% coordinate
}
\state{final}{%
  \pgfpathcurveto%
  {\pgfpoint{0pt}{-\pgfdecorationsegmentamplitude/3}}% support a
  {\pgfpoint{\pgfdecorationsegmentlength}{-\pgfdecorationsegmentamplitude/3}}% support b
  {\pgfpointadd{\pgfpointdecoratedpathlast}{\pgfpoint{0pt}{\pgfdecorationsegmentamplitude}}}% coordinate
}
}
\makeatother
\begin{document}
\begin{tikzpicture}[decoration={test, segment length=2em, amplitude=5em}]
    \draw[decorate] (0,0) .. controls (3,3) .. (10,0);
    \draw[dotted] (0,0) .. controls (3,3) .. (10,0);
\end{tikzpicture}
\end{document}
  • The effect is similar, in that it makes a row of points on one side of the path. However, in the desired effect, the two sides of the points start in the same direction, i.e. straight "down" towards the generating path. In your version (and any version using a quadratic curve) there is an angle between the two curves as they leave the point. However, as I said in the question, this is only a simple example of what I would like to be able to do more generally, which is to define the first control point relative to one segment, and the second control point relative to the next. – brendan Nov 25 '16 at 13:13
  • Fair enough. But I think you could still use the idea behind it: use the control sequences \pgfdecorationsegmentlength and \pgfdecorationsegmentamplitude to define the control points instead of computing them all the time. I'll see if can manage it. – Guilherme Zanotelli Nov 25 '16 at 13:18
  • In my final use case, the decoration is going to be smooth (without corners), so I will need the slope of the "entering" and "leaving" control points to be the same. I can't calculate that from the next iteration knowing only the amplitude and distance. – brendan Nov 25 '16 at 13:26
  • Ok then, I officially don't understand what is the desired output you wish, if when you say smooth you mean passing the rounded corners option that works just fine with the current code provided. In any case, I'll leave this here as maybe someone can use it to make your desired answer. – Guilherme Zanotelli Nov 25 '16 at 13:45

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.