I was able to create the following diagram of a spin echo sequence (NMR, NQR spectroscopy) using TikZ:

enter image description here

It was produces using this code snippet:

    \coordinate (yoffset) at (0, -2);
    \coordinate (start) at (12, 0);
    \coordinate (acq_start) at ($(start) + 1*(yoffset)$);
    \coordinate (end) at (0, 0);
    \coordinate (acq_end) at ($(end) + 1*(yoffset)$);
    \coordinate (rf_1_ll) at ($(end) + (1,-0.5)$);
    \coordinate (rf_1_ur) at ($(end) + (2,0.5)$);
    \coordinate (rf_dash1) at ($(end) + (1,0)$);
    \coordinate (rf_dash2) at ($(end) + (2,0)$);
    \coordinate (rf_2_ll) at ($(end) + (4,-0.5)$);
    \coordinate (rf_2_ur) at ($(end) + (6,0.5)$);
    \coordinate (rf_1_midway) at ($(end) + (1.5,0)$);
    \coordinate (rf_2_midway) at ($(end) + (5,0)$);
    \coordinate (te_half) at ($(rf_2_midway) - (rf_1_midway)$);
    \coordinate (acq_ll) at ($(acq_end) + 2*(te_half) - (-0.5, -0.5) + (1.5, 0)$);
    \coordinate (acq_ur) at ($(acq_end) + 2*(te_half) - (0.5, 0.5) + (1.5, 0)$);

    \draw[dashed] (rf_1_midway) -- ($(rf_1_midway) + 3*(yoffset)$) node [midway, name=te_1] {};
    \draw[dashed] (rf_2_midway) -- ($(rf_2_midway) + 3*(yoffset)$) node [midway, name=te_2] {};
    \draw[<->] (te_1) -- (te_2) node [midway, anchor = south] {$\frac{T_E}{2}$};
    \draw[dashed] ($(rf_2_midway) + (te_half)$) -- ($(rf_2_midway) + (te_half) +3*(yoffset)$) node [midway, name=te_3] {};
    \draw[<->] (te_2) -- (te_3) node [midway, anchor = south] {$\frac{T_E}{2}$};

    \draw[dashed] (rf_dash1) -- ($(rf_dash1) + (0, 1)$) node [name = tp_lab1] {};
    \draw[dashed] (rf_dash2) -- ($(rf_dash2) + (0, 1)$) node [name = tp_lab2] {};
    \draw[<->] (tp_lab1) -- (tp_lab2) node [midway, anchor = south] {$T_P$};

    \draw ($(start) + 0*(yoffset)$) -- ($(end) + 0*(yoffset)$) node[anchor = east] {RF pulse};
    \draw[fill=black!50] (rf_1_ll) rectangle (rf_1_ur);
    \draw[fill=black!50] (rf_2_ll) rectangle (rf_2_ur);
    \draw (acq_start) -- (acq_end) node[anchor = east] {Acquisition};
    \draw[fill=black!50] (acq_ll) rectangle (acq_ur);

    \draw[->] ($(end) + 3*(yoffset)$) -- ($(start) + 3*(yoffset)$) node[anchor = north] {time};
    \draw[->] ($(end) + 3*(yoffset) + (0.5, -0.5)$) -- ($(end) + 3*(yoffset) + (0.5, 2.5)$) node [anchor = north east] {Signal};
    \draw[domain=0:3.5,smooth, line width = 1pt,variable=\x,shift={(1.5,-6)}] plot ({\x},{2*2.718^-\x - 0.02});
        \draw[domain=0:3.5, line width = 1pt,smooth,variable=\x,shift={(-8.5,-6)}] plot ({\x},{1.3*2.718^-\x});
    \draw[domain=0:3.5, line width = 1pt, smooth,variable=\x,shift={(8.5,-6)}] plot ({\x},{1.3*2.718^-\x});
    \draw[dashed, line width = 1pt, domain=0:10.5,smooth,variable=\x,shift={(1.5,-6)}] plot ({\x},{2*2.718^(-\x/16)});
    \draw node at (3.5, -5) {\footnotesize FID $\propto \exp{-\frac{t}{T_2^*}}$};
    \draw node at (10, -4.5) {\footnotesize SE $\propto \exp{-\frac{t}{T_2}}$};

There are a few problems with this, which is why I am asking this question.

  • First of all, it is very verbose - probably more than it should be - yet does not convey what is actually happening very well.
  • I am aware of the notion of "paths" in TikZ, but i was not able to use it nicely in this example.
  • It does not scale very well, i.e. if i wanted to add more dashed lines at the beginning and the end of the blocks, i would have to do it manually for every line. Further, the way I produced the arrows between the blocks required me to set up 2 auxiliary nodes midway between start and end of the dashed lines, and then draw the arrow connecting them. I am sure there is a better way to do this.
  • The extensive use of the calc tikzlibrary. I do not particularly like the syntax and I feel that it is unnecessarily complex. I also was not able to figure out a nice way to only use the x and y coordinate of a node, i.e. construct a point (node_A.x, node_B.Y). This probably would have helped to simplify this too.

I wanted to particularly ask the following: How could I improve this code, regarding especially with the above mentioned points?

  • 2
    Hi, welcome. Regarding the last point in particular, you can use (node A |- node B) to get the x-coordinate of node A and y-coordinate of node B (see e.g. tex.stackexchange.com/q/401425). Regarding "paths", I don't quite understand what you mean. Everytime you do for example \draw you create a new path. – Torbjørn T. Mar 23 '18 at 11:57
  • Thank you. Oh okay very nice, I came across this syntax already but was not quite sure how to use it. I am aware that i create a path evertime I \draw (or \coordinate for that matter). Basically what i meant is how can i simplify the code by using paths well, constructing maybe some invisible paths, placing some nodes in a clever way, and so on. I am just guessing that there are some more advanced but easier ways of drawing this diagram, other than setting up a bunch of coordinates and connecting them more or less individually by draw commands. – Martin Zach Mar 23 '18 at 12:05
  • 1
    You say that it "does not convey what is actually happening very well." Does that mean that the diagram doesn't look right, or does it mean that the code isn't "self-documenting" enough. If you are not too happy with your diagram, can you include an image of a better diagram in your question? Since you don't like the code syntax, would you be open to a solution written in the Asymptote language? – James Mar 23 '18 at 12:05
  • 1
    I mean that the code is not very self documenting, exactly. In fact I am very much happy with the outcome of it. Of course I am open to gain insight into other solutions, however i prefer tikz because of the tight integration and consitency with the rest of the document. – Martin Zach Mar 23 '18 at 12:14
  • @MartinZach: Understood, and welcome to the site! By the way, to be sure that I am notified of your response to my comment, you should start your reply with "@James" as I have done with your name at the beginning of this comment. You are automatically notified of comments to your own question, but I was not notified of your response to my comment. – James Mar 23 '18 at 12:36

There are always several ways of doing the same thing.

Here I parameterize the drawing to a greater extent, making styles and declaring functions both for the plot and constants used throughout. I don't use the calc library at all, nor perpendicular coordinates (-|). I do use relative coordinates, indicated by ++, a couple of times.


    minimum size=1cm
   line width = 1pt
  declare function={

% draw axes
\draw [axis] (0,-1) -- (0,3) node[below left] {Signal};
\draw [axis] (Tmin,0) -- (Tmax,0) node[below] {time};

% draw horizontal lines
\draw (Tmax,Aqheight) -- (Tmin, Aqheight) node[left] {Aquisition};
\draw (Tmax,RFheight) -- (Tmin, RFheight) node[left] {RF Pulse};

% plots with annotations
\draw[myplot] plot ({\x+plotshift},{f(-\x,2)-0.02});
\node[above right,font=\footnotesize] at (plotshift+1,{f(-1,2)}) {FID $\propto \exp{-\frac{t}{T_2^*}}$};

\draw[myplot] plot ({-\x+T+plotshift},{f(-\x,1.3)});
\draw[myplot] plot ({\x+T+plotshift},{f(-\x,1.3)});

\draw[myplot,dashed,domain=0:1.5*T] plot ({\x+plotshift},{f(-\x/16,2)});
\node[above right,font=\footnotesize] at (plotshift+T+1,{f(-(T+1)/16,2)}) {SE $\propto \exp{-\frac{t}{T_2}}$};

% dashed lines
% note addition of coordinate
\foreach [count=\i] \x in {0,0.5,1}
   \draw [dashed] (\x*T+plotshift,0) -- ++(0,RFheight) coordinate[pos=0.45] (T-\i);

% arrowed lines between dashed lines
\foreach [evaluate={\j=int(\i+1)}] \i in {1,2}
   \draw [annotation] (T-\i) -- node[above] {$\frac{T_E}{2}$} (T-\j);

% grey boxes
\node [mybox] (a) at (plotshift,RFheight) {};
\node [mybox, minimum width=2cm] at (plotshift+T/2,RFheight) {};
\node [mybox] at (plotshift+T,Aqheight) {};

% annotation of first box
\draw [dashed] (a.north west) -- ++(0,0.5) coordinate(tmpa);
\draw [dashed] (a.north east) -- ++(0,0.5) coordinate(tmpb);
\draw [annotation] (tmpa) -- node[above] {$T_P$} (tmpb);



enter image description here

| improve this answer | |
  • Very clean solution. I especially like the useage of the declare function functionality for defining lengths and heigths. – Martin Zach Mar 23 '18 at 18:33

And since you are asking about programming style, here is a version in Metapost, to give you an idea of an alternative approach which I hope "conveys what is actually happening" a bit better.

enter image description here

This is wrapped up in luamplib, so compile this with lualatex -- or work out how to adapt it for GMP, or plain Metapost.


    % start by defining a unit size
    numeric u;
    u = 5mm;

    % now define the axes
    path xx, yy;
    xx = (left -- 21 right) scaled u;
    yy = (down -- 5 up) scaled u;

    % and draw & label them
    drawarrow xx; label.bot("time", point 1 of xx);
    drawarrow yy; label.lft("Signal", point 0.9 of yy);

    % the other horizontal lines are defined by copying the x-axis and shifting up
    path aa, pp;
    aa = xx shifted (0, 8u);
    pp = xx shifted (0, 12u);

    % and draw & label them too
    draw aa withcolor 1/2 white; label.lft("Acquisition", point 0 of aa);
    draw pp withcolor 1/2 white; label.lft("RF pulse",    point 0 of pp);

    % now define the vertical lines 
    path t[];
    t1 = (point 0 of xx -- point 0 of pp) shifted (3u,0);
    t2 = (point 0 of xx -- point 0 of pp) shifted (9u,0);
    t3 = (point 0 of xx -- point 0 of pp) shifted (15u,0);

    % and the three boxes
    path b[];
    b1 = unitsquare shifted -(1/2, 1/2) scaled 2u             shifted point 1 of t1;
    b2 = unitsquare shifted -(1/2, 1/2) xscaled 4u yscaled 2u shifted point 1 of t2;
    b3 = unitsquare shifted -(1/2, 1/2) scaled 2u             shifted (aa intersectionpoint t3);

    % draw the lines and boxes in a loop
    forsuffixes $=1,2,3:
        draw t$ dashed evenly withcolor 1/2 white;
        fill b$ withcolor 7/8[blue, white]; 
        draw b$;

    % define the two signal lines
    % the first is defined as bezier cubic splines connecting various points...
    path s[]; 
    s1 = point 1/3 of t1 ..  point 0 of t2 shifted 1 up {right} ..  point 1/4 of t3 
       & point 1/4 of t3 ..  point 0.96 of xx shifted 1 up {right};

    % the second picks out points from the first, the final shift up done by eye
    s2 = point 0 of s1 .. point 2 of s1 .. point 4 of s1 shifted 36 up;

    % draw the signal lines using some color (for emphasis)
    draw s1 withcolor 2/3 blue;
    draw s2 dashed withdots scaled 1/2 withcolor 2/3 blue;

    % add labels along the lines
    label.urt("$\hbox{FID}\propto e^{-t/T_2^*}$", point 1/4 of s1);
    label.urt("$\hbox{SE}\propto e^{-t/T_2}$", point 9/8 of s2);

    % finally write a short function to do the "double arrow" annotation
    vardef annotate(expr description, a, b) = 
        interim ahangle := 25;  % smaller arrow heads...
        interim ahlength := 3;
        path p;   % straight path from a to b, but shortened a bit
        p = a--b cutbefore fullcircle scaled 3 shifted a
                 cutafter  fullcircle scaled 3 shifted b;
        % draw the arrow and put the label near the middle
        drawdblarrow p;
        label(description, point 1/2 of p shifted (unitvector(direction 1/2 of p) rotated 90 scaled 3 labeloffset));

    % add the three annotations
    annotate("$\frac12T_E$", point 1/2 of t1, point 1/2 of t2);
    annotate("$\frac12T_E$", point 1/2 of t2, point 1/2 of t3);
    annotate("$T_P$", point 3 of b1 shifted 5 up, point 2 of b1 shifted 5 up); 

| improve this answer | |

Your code looks already pretty good, but if I had to draw it, I'd probably start with the plot and then add everything else afterwards. Here is a not too elaborate proposal, which, among other things, makes use of the -| syntax advertized by Torbjørn T..

    %1. start with the plot
    \draw[-latex] (0,-1) -- (0,3) node[left,pos=0.9]{signal};
    \draw[-latex] (-1,0) -- (12,0) node[below]{time};
    \draw[domain=0:3.5,smooth, line width = 1pt,variable=\x,shift={(1,0)}] plot ({\x},{2*2.718^-\x - 0.02})
    node[pos=0,above,xshift=1.8cm,yshift=0.7cm] {\footnotesize FID $\propto \exp{-\frac{t}{T_2^*}}$};
        \draw[domain=0:3.5, line width = 1pt,smooth,variable=\x,shift={(-8,0)}] plot ({\x},{1.3*2.718^-\x});
    \draw[domain=0:3.5, line width = 1pt, smooth,variable=\x,shift={(8,0)}] plot ({\x},{1.3*2.718^-\x});
    \draw[dashed, line width = 1pt, domain=0:10.5,smooth,variable=\x,shift={(1,0)}] 
    plot ({\x},{2*2.718^(-\x/16)}) node[above,xshift=-2cm,yshift=0.2cm] {\footnotesize SE $\propto \exp{-\frac{t}{T_2}}$};
    %2. add the dashed lines
    \foreach [count=\n] \X in {1,4.5,8}
    \draw [dashed] (\X,0) coordinate (X-\n) -- (\X,6.5) coordinate (Y-\n);
    \draw[latex-latex,shorten >=2pt,shorten <=2pt] ($(X-1)!0.5!(Y-1)$)
    --($(X-2)!0.5!(Y-2)$) node[midway,above] {$T_E/2$};
    \draw[latex-latex,shorten >=2pt,shorten <=2pt] ($(X-2)!0.5!(Y-2)$)
    --($(X-3)!0.5!(Y-3)$) node[midway,above] {$T_E/2$};
    %3. draw the horizontal lines
    \foreach [count=\n]\X/\Y in {4.5/Aquisition,6.5/RF Pulse}
    \draw[-]  (-1,\X) coordinate[label=left:\Y] (H-\n)  -- (12.5,\X);
    \node[fill=gray,minimum width=1cm,minimum height=1cm] (box-1) at (H-2 -| X-1){};
    \draw[dashed] (box-1.north west) -- ++(0,0.4cm) coordinate (B-1);
    \draw[dashed] (box-1.north east) -- ++(0,0.4cm) coordinate (B-2);
    \draw[latex-latex,shorten >=2pt,shorten <=2pt] (B-1) -- (B-2)
    node[midway,above] {$T_F$};
    \node[fill=gray,minimum width=2cm,minimum height=1cm] at (H-2 -| X-2){};
    \node[fill=gray,minimum width=1cm,minimum height=1cm] at (H-1 -| X-3){};

enter image description here

| improve this answer | |

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.