1

I want to draw something like the following in TikZ, but, unfortunately, I'm not sure how to get to the needed result. The figure shows the path of ions in a quadrupole mass spectrometer. Outside the quadrupole (those 4 rods) no electromagnetic field applies to the ions and thus they fly in a straight line. If they enter the quadrupole they can either come into resonance with the electromagnetic field and thus be on a cylindrical spiral path or not be in resonance and thus be on a conical spiral path and sooner or later exit the quadrupole at a side.

Examples

My take to this problem was to use pgfplots to draw the spirals using a 3D plot with the function {x*cos(deg(x))},{x*sin(deg(x)},{x} for the conical plot and {cos(deg(x))},{sin(deg(x)},{x} for the cylindrical one. Unfortunately, I find myself unable to solve the following issues:

  • correctly position the spirals
  • draw a straight line that transforms into a spiral and then back to a straight line after exiting the quadrupole (only for the cylindrical one)
  • stop the conical helix shortly after the path has exited the quadrupole

I'm well aware that this is quite a lot of issues and thus I'm happy for any hints.

My current (miserable) attempt

\documentclass{standalone}

\usepackage{xparse} \usepackage{ifthen} \usepackage{tikz} \usepackage{pgfplots}

\pgfplotsset{compat=1.8} \usetikzlibrary{calc} \usetikzlibrary{decorations.markings}

\begin{document}

\begin{tikzpicture} % General constants % %%%%%%%%%%%%%%%%%

\coordinate (msOrigin) at (0,0);
\pgfmathsetmacro{\msY}{3}

\pgfmathsetmacro{\offsetX}{0.3}
\pgfmathsetmacro{\offsetY}{0.2}
\pgfmathsetmacro{\spacer}{0.75}
\pgfmathsetmacro{\arrowLength}{1}
\pgfmathsetmacro{\centerOffset}{0.3}


% Quadrupole constants
% %%%%%%%%%%%%%%%%%%%%

\pgfmathsetmacro{\quadrupoleRadiusHorizontal}{0.08}
\pgfmathsetmacro{\quadrupoleRadiusVertical}{0.2}
\pgfmathsetmacro{\quadrupoleLength}{3}
\pgfmathsetmacro{\quadrupolePathLength}{\quadrupoleLength - (2 * \quadrupoleRadiusHorizontal)}

\pgfmathsetmacro{\quadrupoleTopFrontY}{0.5 * \msY + \centerOffset + 2 * \quadrupoleRadiusVertical}
\pgfmathsetmacro{\quadrupoleTopBackY}{\quadrupoleTopFrontY + \offsetY}
\pgfmathsetmacro{\quadrupoleBottomBackY}{0.5 * \msY - \centerOffset}
\pgfmathsetmacro{\quadrupoleBottomFrontY}{\quadrupoleBottomBackY - \offsetY}

\NewDocumentCommand{\cylinder}{m m m m m m m m}{%  coordX, coordY, length, radiusX, radiusY, colorCylinder, colorEllipse, opacity
    \fill [#6, fill opacity = #8]
        ($ (msOrigin) + ({#1},{#2}) $)
        --
        ++({#3},0)
        arc
        (90:270:-{#4} and {#5})
        --
        ++(-{#3},0)
        arc
        (270:90:-{#4} and {#5});

    \draw [fill = #7, fill opacity = #8]
        ($ (msOrigin) + ({#1},{#2}) + (0,{-#5}) $)
        ellipse
        ({#4} and {#5});

    \draw
        ($ (msOrigin) + ({#1},{#2}) $)
        --
        ++({#3},0)
        arc
        (90:270:-{#4} and {#5})
        --
        ++(-{#3},0);
}

\NewDocumentCommand{\quadrupoleRod}{m m m}{% segment, top/bottom, front/back
    \ifthenelse{\equal{#2}{top} \AND \equal{#3}{front}}{%
        \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal + \offsetX}
        \pgfmathsetmacro{\coordY}{\quadrupoleTopFrontY}
    }{}

    \ifthenelse{\equal{#2}{top} \AND \equal{#3}{back}}{%
        \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal}
        \pgfmathsetmacro{\coordY}{\quadrupoleTopBackY}
    }{}

    \ifthenelse{\equal{#2}{bottom} \AND \equal{#3}{front}}{%
        \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal + \offsetX}
        \pgfmathsetmacro{\coordY}{\quadrupoleBottomFrontY}
    }{}

    \ifthenelse{\equal{#2}{bottom} \AND \equal{#3}{back}}{%
        \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal}
        \pgfmathsetmacro{\coordY}{\quadrupoleBottomBackY}
    }{}

    \cylinder
        {\coordX}
        {\coordY}
        {\quadrupolePathLength}
        {\quadrupoleRadiusHorizontal}
        {\quadrupoleRadiusVertical}
        {gray}
        {white}
        {1}
}

\NewDocumentCommand{\quadrupolePair}{m m}{% segment, front/back
    \ifthenelse{\equal{#2}{front} \OR \equal{#2}{back}}{%
        \quadrupoleRod{#1}{top}{#2}
        \quadrupoleRod{#1}{bottom}{#2}
    }{}
}

\quadrupolePair{1}{back}
\begin{axis}[
    rotate around={-90:(current axis.origin)},
    view = {30}{20},
    axis line style = {draw = none},
    tick style = {draw = none},
    zmax = 60,
    xtick=\empty,
    ytick=\empty,
    ztick=\empty
]
    \addplot3+[
        mark = none,
        thick,
        red,
        domain = 0:50*pi,
        samples = 1000,
        samples y = 0,
    ]
    % ({x*cos(deg(x))},{x*sin(deg(x)},{x});
    ({cos(deg(x))},{sin(deg(x)},{x});
\end{axis}
\quadrupolePair{1}{front}

\end{tikzpicture}

\end{document}

Update 2020-11-26

I found this answer on TeX.SX helping to draw the cylindrical coil. By some modifications, I was able to get relatively far in the process. One remaining issue is the line connecting the horizontal path with the spiral as the code mark=at position #1 with \coordinate (#2); throws a Dimension too large. error, even if I don't understand why. The coils are small and definitely below 19 feet...

Another issue that remains is the conical spiral. I have a starting point, but unfortunately, it looks gross.

State as of 2020-11-26

\documentclass{standalone}

\usepackage{xparse} \usepackage{ifthen} \usepackage{tikz}

\usetikzlibrary{calc} \usetikzlibrary{decorations.markings}

\tikzset{ mark position/.style args={#1(#2)}{ postaction={ decorate, decoration={ markings, mark=at position #1 with \coordinate (#2); } } } }

\NewDocumentCommand{\cylinder}{m m m m m m m m}{% coordX, coordY, length, radiusX, radiusY, colorCylinder, colorEllipse, opacity \fill [#6, fill opacity = #8] ($ (msOrigin) + ({#1},{#2}) $) -- ++({#3},0) arc (90:270:-{#4} and {#5}) -- ++(-{#3},0) arc (270:90:-{#4} and {#5});

\draw [fill = #7, fill opacity = #8]
    ($ (msOrigin) + ({#1},{#2}) + (0,{-#5}) $)
    ellipse
    ({#4} and {#5});

\draw
    ($ (msOrigin) + ({#1},{#2}) $)
    --
    ++({#3},0)
    arc
    (90:270:-{#4} and {#5})
    --
    ++(-{#3},0);

}

\NewDocumentCommand{\quadrupoleRod}{m m m}{% segment, top/bottom, front/back \ifthenelse{\equal{#2}{top} \AND \equal{#3}{front}}{% \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal + \offsetX} \pgfmathsetmacro{\coordY}{\quadrupoleTopFrontY} }{}

\ifthenelse{\equal{#2}{top} \AND \equal{#3}{back}}{%
    \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal}
    \pgfmathsetmacro{\coordY}{\quadrupoleTopBackY}
}{}

\ifthenelse{\equal{#2}{bottom} \AND \equal{#3}{front}}{%
    \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal + \offsetX}
    \pgfmathsetmacro{\coordY}{\quadrupoleBottomFrontY}
}{}

\ifthenelse{\equal{#2}{bottom} \AND \equal{#3}{back}}{%
    \pgfmathsetmacro{\coordX}{\quadrupoleRadiusHorizontal}
    \pgfmathsetmacro{\coordY}{\quadrupoleBottomBackY}
}{}

\cylinder
    {\coordX}
    {\coordY}
    {\quadrupolePathLength}
    {\quadrupoleRadiusHorizontal}
    {\quadrupoleRadiusVertical}
    {gray}
    {white}
    {1}

}

\NewDocumentCommand{\quadrupolePair}{m m}{% segment, front/back \ifthenelse{\equal{#2}{front} \OR \equal{#2}{back}}{% \quadrupoleRod{#1}{top}{#2} \quadrupoleRod{#1}{bottom}{#2} }{} }

\begin{document}

% General constants % %%%%%%%%%%%%%%%%% \pgfmathsetmacro{\offsetX}{0.5} \pgfmathsetmacro{\offsetY}{0.6} \pgfmathsetmacro{\spacer}{0.75} \pgfmathsetmacro{\centerOffset}{0.3}

% Quadrupole constants % %%%%%%%%%%%%%%%%%%%%

\pgfmathsetmacro{\quadrupoleRadiusHorizontal}{0.08} \pgfmathsetmacro{\quadrupoleRadiusVertical}{0.2} \pgfmathsetmacro{\quadrupoleLength}{4} \pgfmathsetmacro{\quadrupolePathLength}{\quadrupoleLength - (2 * \quadrupoleRadiusHorizontal)}

\pgfmathsetmacro{\quadrupoleTopFrontY}{\centerOffset + 2 * \quadrupoleRadiusVertical} \pgfmathsetmacro{\quadrupoleTopBackY}{\quadrupoleTopFrontY + \offsetY} \pgfmathsetmacro{\quadrupoleBottomBackY}{-\centerOffset} \pgfmathsetmacro{\quadrupoleBottomFrontY}{\quadrupoleBottomBackY - \offsetY}

\begin{tikzpicture} \coordinate (msOrigin) at (0,0);

% Define a formula for the coil.
% This is what the numbers mean:
% 0.25: the x offset
% 0.13: how far the rings are apart
% 0.30: how much from the side the rings are seen
% 0.75: radius of the rings
\def\coil#1{
    {0.25 + 0.13 * (2 * #1 + \t) + 0.30 * sin(- \t  *  pi r))},
    {0.75 * cos(-\t * pi r)}
}

% Draw the background-rods
\quadrupolePair{1}{back}

% Draw the part of the coil behind
\foreach \n in {1,...,14} {
    \draw[domain={0:1},smooth,variable=\t,samples=15]
        plot (\coil{\n}); 
}

% Draw the part of the coil in front
\foreach \n in {0,1,...,13} {
    \ifthenelse{\equal{\n}{0} \OR \equal{\n}{13}}
    {%
        \ifthenelse{\equal{\n}{0}}{%
            \draw[
                domain = {1:2},
                smooth,
                variable = \t,
                samples = 15,
                % mark position = 0(start)
            ]
                plot (\coil{\n});
        }{%
        \draw[
                domain = {1:2},
                smooth,
                variable = \t,
                samples = 15,
                % mark position = 1(end)
            ]
                plot (\coil{\n});
        }
    }{
        \draw[
            domain = {1:2},
            smooth,
            variable = \t,
            samples = 15
        ]
            plot (\coil{\n});
    }
}

% Draw the foreground-rods
\quadrupolePair{1}{front}

\draw 
    % (start) % to join the mark position "start"
    (0.25, -0.75)
    to [out = 180, in = 0] 
    ++(-1, 0.75);
\draw 
    % (end) % to join the mark position "end"
    (4, -0.75) 
    to [out = 0, in = 180] 
    ++(1, 0.75);

\end{tikzpicture}

\hspace{1em}

\begin{tikzpicture} \coordinate (msOrigin) at (0,0);

% Define a formula for the coil.
% This is what the numbers mean:
% 0.25: the x offset
% 0.13: how far the rings are apart
% 0.30: how much from the side the rings are seen
% 0.75: radius of the rings
\def\coil#1{
    {0.25 + 0.13 * (2 * #1 + \t) + 0.30 * sin(- \t  *  pi r)},
    {0.75 * #1/10 * \t * cos(-\t * pi r)}
}

% Draw the background-rods
\quadrupolePair{1}{back}

% Draw the part of the coil behind
\foreach \n in {1,...,14} {
    \draw[domain={0:1},smooth,variable=\t,samples=15]
        plot (\coil{\n});
}

% Draw the part of the coil in front
\foreach \n in {0,1,...,13} {
    \ifthenelse{\equal{\n}{0} \OR \equal{\n}{13}}
    {%
        \ifthenelse{\equal{\n}{0}}{%
            \draw[
                domain = {1:2},
                smooth,
                variable = \t,
                samples = 15,
                % mark position = 0(start)
            ]
                plot (\coil{\n});
        }{%
        \draw[
                domain = {1:2},
                smooth,
                variable = \t,
                samples = 15,
                % mark position = 1(end)
            ]
                plot (\coil{\n});
        }
    }{
        \draw[
            domain = {1:2},
            smooth,
            variable = \t,
            samples = 15
        ]
            plot (\coil{\n});
    }
}

% Draw the foreground-rods
\quadrupolePair{1}{front}

\end{tikzpicture}

\end{document}

Sam
  • 2,958

1 Answers1

3

I see no reason to use PGF code - you are almost there just by noticing that the spiral can be plotted by {cos(deg(x))},{sin(deg(x)},{x}. I normally love PGFPlots, but this is not a plot(axis, scale, ticks, labels, ...). I believe that the plot function in TikZ is the right way.

To straighten the ends of the spiral I let the amplitude decay at the same with the pitch of the loops. I am not sure how you want the conical to end - a simple way is just to let the amplitude of the coil go up fast and adjust the domain.

\documentclass[tikz, border=1cm]{standalone}
\begin{document}
\begin{tikzpicture}[ultra thick]
\newcommand{\domA}{-pi}
\newcommand{\domB}{0}
\newcommand{\domC}{2*pi}
\newcommand{\domD}{4*pi}
\newcommand{\domE}{\domC+0.5}
\newcommand{\pitch}{10}
\newcommand{\ampA}{(1/(1+\domB-\x))}
\newcommand{\ampB}{(1/(1-\domC+\x))}
\newcommand{\ampC}{(0.1*(\x-\domB)+1)}

\draw[red, domain={\domA:\domB}, smooth, samples=100] plot (\x, {\ampAcos((\ampA\pitch\x+(1-\ampA)\pitch\domB) r)}, {\ampAsin((\ampA\pitch\x+(1-\ampA)\pitch\domB) r)} ); \draw[green, domain={\domB:\domC}, smooth, samples=200] plot (\x, {cos(\pitch\x r)} , {sin(\pitch\x r)} ); \draw[blue, domain={\domC:\domD}, smooth, samples=100] plot (\x, {\ampBcos((\ampB\pitch\x+(1-\ampB)\pitch\domC) r)}, {\ampBsin((\ampB\pitch\x+(1-\ampB)\pitch\domC) r)} );

\begin{scope}[yshift=-4cm] \draw[teal, domain={\domA:\domB}, smooth, samples=100] plot (\x, {cos((\ampA\pitch\x+(1-\ampA)\pitch\domB) r)}, {sin((\ampA\pitch\x+(1-\ampA)\pitch\domB) r)} ); \draw[orange, domain={\domB:\domC}, smooth, samples=200] plot (\x, {\ampCcos(\pitch\x r)} , {\ampCsin(\pitch\x r)} ); \draw[violet, domain={\domC:\domE}, smooth, samples=100] plot (\x, {\ampC1/\ampBcos(\pitch\x r)} , {\ampC1/\ampBsin(\pitch\x r)} ); \end{scope}

\end{tikzpicture} \end{document}

Spirals

Edit:

The default z-vector in TikZ points to (−3.85mm, −3.85mm). To change the perspective, you can use e.g. z={(-3.85mm, 3.85mm)} like this:

\documentclass[tikz, border=1cm]{standalone}
\begin{document}
\begin{tikzpicture}[z={(-3.85mm, 3.85mm)}]
\newcommand{\domA}{-pi}
\newcommand{\domB}{0}
\newcommand{\domC}{2*pi}
\newcommand{\domD}{4*pi}
\newcommand{\domE}{\domC+0.5}
\newcommand{\pitch}{10}
\newcommand{\ampA}{(1/(1+\domB-\x))}
\newcommand{\ampB}{(1/(1-\domC+\x))}
\newcommand{\ampC}{(0.1*(\x-\domB)+1)}

\draw[fill=gray] (-1,1.2,1) -- (7,1.2,1) arc[start angle=90, end angle=-90, x radius=0.1cm, y radius=0.2cm] -- (-1,0.8,1); \drawfill=white circle[x radius=0.1cm, y radius=0.2cm]; \draw[fill=gray] (-1,-1.2,1) -- (7,-1.2,1) arc[start angle=-90, end angle=90, x radius=0.1cm, y radius=0.2cm] -- (-1,-0.8,1); \drawfill=white circle[x radius=0.1cm, y radius=0.2cm];

\draw[red, thick, domain={\domA:\domB}, smooth, samples=100] plot (\x, {\ampAcos((\ampA\pitch\x+(1-\ampA)\pitch\domB) r)}, {\ampAsin((\ampA\pitch\x+(1-\ampA)\pitch\domB) r)} ); \draw[red, thick, domain={\domB:\domC}, smooth, samples=200] plot (\x, {cos(\pitch\x r)} , {sin(\pitch\x r)} ); \draw[red, thick, domain={\domC:\domD}, smooth, samples=100] plot (\x, {\ampBcos((\ampB\pitch\x+(1-\ampB)\pitch\domC) r)}, {\ampBsin((\ampB\pitch\x+(1-\ampB)\pitch\domC) r)} );

\draw[fill=gray] (-1,1.2,-1) -- (7,1.2,-1) arc[start angle=90, end angle=-90, x radius=0.1cm, y radius=0.2cm] -- (-1,0.8,-1); \drawfill=white circle[x radius=0.1cm, y radius=0.2cm]; \draw[fill=gray] (-1,-1.2,-1) -- (7,-1.2,-1) arc[start angle=-90, end angle=90, x radius=0.1cm, y radius=0.2cm] -- (-1,-0.8,-1); \drawfill=white circle[x radius=0.1cm, y radius=0.2cm];

\end{tikzpicture} \end{document}

Rods and spiral

The kink in the red spiral is because the smooth does not work across different plots. I can see two ways to correct this: Either remove the smooth option and increase the samples a lot. -or better: Use TikZ declare function to declare a piecewise function and only do one plot.

  • I really like this, but the perspective seems odd. If you compare it to the perspective of the four rods in my examples, wouldn't it be more correct if the coils were seen from the left side? This feels like looking to them from the right. It's hard to explain what I mean, but I guess that you'll understand. – Sam Nov 27 '20 at 15:34
  • 1
    Yes you are correct - I just used TikZ default perspective. See edit. – hpekristiansen Nov 27 '20 at 16:35
  • Much better. Thanks, I didn't know about the default orientation. Nice to know! One last question: Have you any idea how to increase the visibility of which part of the spiral is in front and which one is in the back? Sometimes if I look at it, I see a spiral turning clockwise and sometimes I see a counter-clockwise one. I guess it would help to interrupt the "back-part" if the "front-part" crosses it, but I'm not sure if this is even possible using a function. – Sam Nov 27 '20 at 21:22
  • Normally one would just draw a double line with the outer part white - white, double=red. This does not work directly here as the line self intersect. Try it to see the result. A solution would be to split the plot into individual loops. It is a problem that I would like to investigate myself, so maybe I will ask a question on this site tomorrow. – hpekristiansen Nov 27 '20 at 22:00
  • If the rods absolutely needs to be visible directly behind the self crossings of the coil, you need a much more complex solution with clip like this: https://tex.stackexchange.com/a/568980/8650 or you can calculate the correct domain for each loop. – hpekristiansen Nov 27 '20 at 22:05
  • Yeah exactly that would be an idea but not for a self-intersecting function. Would be great to have a solution for that, but I cannot even think of one, let alone think about the implementation in TikZ. If you do so, feel free to link it tho this post. Still: Thank you very much for your detailed answer in which I learned some new quirks of TikZ. – Sam Nov 27 '20 at 22:06
  • See https://tex.stackexchange.com/questions/572737/decoration-for-self-crossing-lines-curves . You can already use the shown \foreach example to make your spiral. – hpekristiansen Nov 28 '20 at 16:44
  • That's a nice method but, unfortunately, it's not an option for this use case as the line doubling would be visible on the rods. I'll try to find a solution employing clip. – Sam Nov 28 '20 at 21:42