4

I would like to draw a spring with the TikZ coil decoration, but I'm unable to center it on the path without fine adjustment by hand.

Example:

\documentclass{article}

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

\begin{document}
\begin{tikzpicture}
  \draw
    [%
      thick,
      decoration=
        {%
          coil,
          amplitude=0.5cm,
          aspect=0.7,
          pre length=0.5cm,
          post length=0.5cm,
          segment length=1cm,
        },%
      decorate,
    ]
  (0,0) -- (5cm, 0);
\end{tikzpicture}
\end{document}

not centered tikz decoration

The spring is not symmetrical, the right and left sides don't get the same length.

Is there a way to automatically compute the pre length, post length or segment length parameters to obtain a symmetrical spring?

5
  • 1
    As far as I can see, decoration is not intended to work the way you try. It looks like decoration is just inserted somehow. // What seems to come close, is tikz.dev/gd-overview#sec-27.2 , using the force library. See the last drawing befor sec. 27.3 // See also tikz.dev/gd-force
    – MS-SPO
    Apr 2 at 18:56
  • 2
    @MS-SPO Thank you for taking the time to consider my question. The force library (that I didn't know, it looks very nice) draws graphs automatically, from constraints (like "springs"). But here I just want to decorate a line of fixed length, with the decoration library. I know that segment length sets the pattern length when aspect=0, but I didn't manage to understand what's the pattern length when aspect is non-zero.
    – jlab
    Apr 2 at 19:31
  • @MS-SPO Isn't it a deformation/transformation of the whole path? If it was inserted like a node, you might be able to use pos, but I don't think it's located along the path. I think it affects how the path is drawn? (But maybe you've read the code, which I haven't. I've just (sort of) read the doc.)
    – cfr
    Apr 2 at 23:45
  • 2
    If there isn't room for a complete segment at the end of the path, TikZ completes the path with a straight line. So unless you give parameters which fit a number of segments perfectly to the path, you will always get a bit of straight line at the end. So it isn't exactly that the decoration isn't centred. It is rather that the length assigned to the decoration is centred, but the decoration isn't a perfect fit and there's a bit of space at the end which gets filled by straight line. So the decoration bit is centred, if you like, but the altered bit of path isn't.
    – cfr
    Apr 3 at 0:11
  • @cfr It's exactly that. The difficulty is that the last part of the coil decoration is larger than the segment length. But thanks to @Tom I found a way to compute the correct path parameters.
    – jlab
    Apr 3 at 20:49

2 Answers 2

4

You could do this:

Edits: To compensate the round error when aspect smaller than 0.5, change amplitude=(0.5*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect) to amplitude=(0.49*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect)

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations.pathmorphing}
\newcounter{Nocoils}
\setcounter{Nocoils}{3}
\newlength{\lengthspr}
\setlength{\lengthspr}{5cm}
\newlength{\preandpostlens}
\setlength{\preandpostlens}{0.3cm}
\tikzset{
/pgf/decoration/pre length=\preandpostlens,
/pgf/decoration/post length=\preandpostlens
}

\begin{document}
\begin{tikzpicture}
\setlength{\lengthspr}{5.21cm}
\setcounter{Nocoils}{5}
\draw
    [%
      thick,
      decoration=
        {%
          coil,
          aspect=0.7,
          segment length=(\lengthspr-2*\pgfkeysvalueof{/pgf/decoration/pre length})/(\value{Nocoils}),
          amplitude=(0.49*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect),  
        },%
      decorate,
    ]
  (0,0) -- (\lengthspr, 0);
\end{tikzpicture}

\begin{tikzpicture}
\setlength{\lengthspr}{4cm}
\setlength{\preandpostlens}{0.72cm}
\setcounter{Nocoils}{4}
\draw
    [%
      thick,
      decoration=
        {%
          coil,
          aspect=0.27,
          segment length=(\lengthspr-2*\preandpostlens)/(\value{Nocoils}),
          amplitude=(0.49*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect),  
        },%
      decorate,
    ]
  (0,0) -- (\lengthspr, 0);
\end{tikzpicture}
\end{document}

enter image description here

Edit: I propose another method that use same segment length for every spring then calculate the corresponding number of coils, pre legnth and post length depends on the total length of the spring. Maybe this is more likely what you wanted:

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


\newlength{\lengthspr}
\setlength{\lengthspr}{5cm}
\newlength{\seglens}
\setlength{\seglens}{0.5cm}
\newcounter{Nocoils}
\setcounter{Nocoils}{\fpeval{floor(\lengthspr/\seglens,0)}}
\newlength{\preandpostlens}
\setlength{\preandpostlens}{\fpeval{(\lengthspr-(\value{Nocoils}*\seglens))/2cm}cm}
\tikzset{
/pgf/decoration/pre length=\preandpostlens,
/pgf/decoration/post length=\preandpostlens
}

\begin{document}
\begin{tikzpicture}
\setlength{\lengthspr}{3.31cm}
\draw
    [%
      thick,
      decoration=
        {%
          coil,
          aspect=0.33,
          segment length=\seglens,
          amplitude=(0.49*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect),  
        },%
      decorate,
    ]
  (0,0) -- (\lengthspr, 0);
\end{tikzpicture}

\begin{tikzpicture}
\setlength{\lengthspr}{5.21cm}
\setcounter{Nocoils}{\fpeval{floor(\lengthspr/\seglens,0)}}
\setlength{\preandpostlens}{\fpeval{(\lengthspr-(\value{Nocoils}*\seglens))/2cm}cm}
\draw
    [%
      thick,
      decoration=
        {%
          coil,
          aspect=0.33,
          segment length=\seglens,
          amplitude=(0.49*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect),  
        },%
      decorate,
    ]
  (0,0) -- (\lengthspr, 0);
\end{tikzpicture}
\end{document}

enter image description here

4
  • 1
    I see. The key is to also adjust the amplitude. I couldn't find the right formula. Thank you.
    – jlab
    Apr 2 at 21:38
  • 1
    Well, it seems that it doesn't work in all circumstance. For example, with aspect=0.3.
    – jlab
    Apr 2 at 21:44
  • 1
    @jlab I think there could be small round error happened to prevent the last coil from drawing because the last segment smaller than 0.5\pgfdecorationsegmentlength. So, to salvage this, you could set amplitude=(0.49*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect).
    – Tom
    Apr 3 at 3:08
  • 1
    It seems that the relationship amplitude=(0.5*\pgfdecorationsegmentlength)/ (2*\pgfdecorationsegmentaspect) corresponds to the last part of the decoration (the part after the multiple \pgfdecorationsegmentlength). Your answer helped me a lot, and pointed me in the right direction. Thanks for giving different methods.
    – jlab
    Apr 3 at 20:44
2

I look a bit further, helped by @Tom answer. The definition of the coil decoration is given in the file pgflibrarydecorations.pathmorphing.code.tex, and start with

\pgfdeclaredecoration{coil}{coil}
{%
  \state{coil}[switch if less than=%
    1.5\pgfdecorationsegmentlength+%
    \pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude+%
    \pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude to last,
               width=+\pgfdecorationsegmentlength]
...

So \pgfdecorationsegmentlength is the good pattern length, but the difficulty is that the last part of the coil is longer and needs at least 0.5\pgfdecorationsegmentlength + 2*\pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude.

Say differently, if

  • L is the full length of the path,
  • p is equal to pre length and post length,
  • s is the segment length,
  • a is the amplitude,
  • x is the aspect,
  • n is the number of coils, then
L = p + n⋅s + 0.5⋅s + 2⋅x⋅a + p

Using TikZ: Calculate "pre length" based on path length, I can get the full path length with\pgfmetadecoratedpathlength, but it doesn't work if I try to set the amplitude. So I resolved to set instead the pre length with:

pre length=
        (
          \pgfkeysvalueof{/pgf/decoration/min prepost length}
          + 0.5*
            (
            \pgfmetadecoratedpathlength
              - 2*\pgfkeysvalueof{/pgf/decoration/min prepost length}
              - 2*\pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude
              - 0.5\pgfdecorationsegmentlength
              - \pgfdecorationsegmentlength*floor
                (
                  (
                    \pgfmetadecoratedpathlength
                    - 2*\pgfkeysvalueof{/pgf/decoration/min prepost length}
                    - 2*\pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude
                    - 0.5\pgfdecorationsegmentlength
                  )
                  /\pgfdecorationsegmentlength
                )
            )
        )

(a mod function would have made this expression clearer, but I got errors about expansion). Here /pgf/decoration/min prepost length is a dedicated key used tho assure a minimum value for the pre length and the post length. Note that mathematically the first \pgfkeysvalueof{/pgf/decoration/min prepost length} cancels with 0.5*(- 2*\pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude), but I keep them for clarity.

I get now a computation which work almost all the time (except, when aspect is negative, if aspect*amplitude is greater than segment length, but it's sufficient for me).

\documentclass{article}

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

\tikzset{%
  ,/pgf/decoration/min prepost length/.initial=2mm
  ,coil spring/.style={%
    ,thick
    ,smooth
    ,decorate
    ,decoration={
      ,coil
      ,aspect=0.5
      ,segment length=2mm
      ,amplitude=2mm
      ,pre length=
        (
          \pgfkeysvalueof{/pgf/decoration/min prepost length}
          + 0.5*
            (
            \pgfmetadecoratedpathlength
              - 2*\pgfkeysvalueof{/pgf/decoration/min prepost length}
              - 2*\pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude
              - 0.5\pgfdecorationsegmentlength
              - \pgfdecorationsegmentlength*floor
                (
                  (
                    \pgfmetadecoratedpathlength
                    - 2*\pgfkeysvalueof{/pgf/decoration/min prepost length}
                    - 2*\pgfdecorationsegmentaspect\pgfdecorationsegmentamplitude
                    - 0.5\pgfdecorationsegmentlength
                  )
                  /\pgfdecorationsegmentlength
                )
            )
        )
      ,post length=\pgfkeysvalueof{/pgf/decoration/min prepost length}
    }
  }
}

\begin{document}
\begin{tikzpicture}
  \draw[gray, very thin, step=2mm] (0,-1) grid (5,4);
  \draw (0,-1) grid (5,4);
  \draw
    [%
      ,coil spring, ultra thick
    ]
  (0,3) -- +(5cm, 0);
  \draw
    [%
      ,coil spring, ultra thick
      ,decoration={aspect=0.5, amplitude=0.5cm, segment length=1cm}
    ]
  (0,2) -- +(4.5cm, 0);
  \draw
    [%
      ,coil spring, ultra thick
      ,decoration={aspect=0.3, amplitude=0.5cm, segment length=1cm}
    ]
  (0,1) -- +(5cm, 0);
  \draw
    [%
      ,coil spring, ultra thick
      ,decoration={aspect=-0.2, amplitude=0.5cm, min prepost length=1cm}
    ]
  (0,0) -- +(5cm, 0);
\end{tikzpicture}
\end{document}

solution of pre length computation

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .