7

I want to produce a picture that contains lots of two-part cylinder-kind shapes (maybe about 200) along certain paths. Here is an example of such a shape:

\documentclass{standalone}
\usepackage[svgnames]{xcolor}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

%%%%% H-O bond between atom 1 and atom 3

%%% O half of the bond
% O half bond shading
\pgfdeclareverticalshading{1_3_O3_BondShading}{100bp}{
  color(0.000bp)=(Black!75!Red);
  color(22.773bp)=(Black!75!Red);
  color(45.525bp)=(Black!3.893!Red);
  color(53.986bp)=(White!26.261!Red);
  color(59.561bp)=(White!26.261!Red);
  color(68.022bp)=(Black!3.893!Red);
  color(90.773bp)=(Black!75!Red);
  color(100.000bp)=(Black!75!Red)}
% O half bond drawing
\begin{scope}
  \pgfsetadditionalshadetransform{\pgftransformyscale{0.0856124622417}}
  \pgfpathmoveto{\pgfpoint{3.107cm}{-5.942cm}}
  \pgfpathlineto{\pgfpoint{2.998cm}{-5.816cm}}
  \pgfpatharcaxes{0}{180}{\pgfpoint{0.085cm}{0.073cm}}{\pgfpoint{-0.034cm}{0.040cm}}
  \pgfpathlineto{\pgfpoint{2.936cm}{-6.088cm}}
  \pgfpatharcaxes{180}{0}{\pgfpoint{0.085cm}{0.073cm}}{\pgfpoint{-0.034cm}{0.040cm}}
  \pgfusepath{clip}
  \pgfpathcircle{\pgfpoint{2.968cm}{-5.952cm}}{1.168cm}
  \pgfshadepath{1_3_O3_BondShading}{-49.384}
  \pgfusepath{}
\end{scope}

%%% H half of the bond
% H half bond shading
\pgfdeclareverticalshading{1_3_H1_BondShading}{100bp}{
  color(0.000bp)=(Black!75!White);
  color(22.773bp)=(Black!75!White);
  color(45.525bp)=(Black!20.000!White);
  color(53.986bp)=(White!26.261!White);
  color(59.561bp)=(White!26.261!White);
  color(68.022bp)=(Black!20.000!White);
  color(90.773bp)=(Black!75!White);
  color(100.000bp)=(Black!75!White)}
% H half bond drawing
\begin{scope}
  \pgfsetadditionalshadetransform{\pgftransformyscale{0.0856124622417}}
  \pgfpathmoveto{\pgfpoint{2.936cm}{-6.088cm}}
  \pgfpathlineto{\pgfpoint{3.026cm}{-6.193cm}}
  \pgfpatharcaxes{180}{0}{\pgfpoint{0.086cm}{0.073cm}}{\pgfpoint{0.034cm}{-0.040cm}}
  \pgfpathlineto{\pgfpoint{3.107cm}{-5.942cm}}
  \pgfpatharcaxes{0}{180}{\pgfpoint{0.085cm}{0.073cm}}{\pgfpoint{-0.034cm}{0.040cm}}
  \pgfusepath{clip}
  \pgfpathcircle{\pgfpoint{3.066cm}{-6.067cm}}{1.168cm}
  \pgfshadepath{1_3_H1_BondShading}{-49.384}
  \pgfusepath{}
\end{scope}

\end{tikzpicture}

\end{document}

Since this makes the code unreadably long I thought of putting this into a decoration. The decoration will have to take a lot of arguments: the names, colors and positions of the shadings, as well as the coordinates for the \pgfpatharcaxes commands. Unfortunately, I have very little experience with TeX and pgf programming so that this will take up some time. Since I want to be sure that this time investment is not wasted I'd like to know whether it is to be expected that TikZ decorations come with any penalty for the compilation time compared to the normal code shown above (I would refrain from using a decoration if the compilation would take more than twice as long)? Is it advisable to use a decoration in my case or might there be reasons against it?

3

The benefit of decoration is that it could help you place shadings properly. But before that, let us define a flexible shading

\pgfdeclareverticalshading[base color]{cylinder}{1000bp}{
  color(0.000bp)=(black!75!base color);
  color(22.773bp)=(black!75!base color);
  color(45.525bp)=(black!3.893!base color);
  color(53.986bp)=(white!26.261!base color);
  color(59.561bp)=(white!26.261!base color);
  color(68.022bp)=(black!3.893!base color);
  color(90.773bp)=(black!75!base color);
  color(100.000bp)=(black!75!base color)}

\pgfutil@colorlet{base color}{green}
\pgfuseshading{cylinder}

Then we try to utilize this shading in a decoration. There are many parameters

  • start at is the starting position.
  • end at is the ending position.
  • start by determines if the shape is convex or concave at the starting position.
    (1: convex; .1: slightly convex; -1: concave)
  • end by works similarly

The problem here is that the shading does not rotate with us. (By "us" I mean the decoration engine.) So I need to modify the behavior of \pgfshadepath -- I called it \pgfshadepath@revise.

\pgfkeys{
  /pgf/decoration/.cd,
  start at/.code={\pgfmathsetmacro\pgfdecorationstartat{#1}},
  start at=.25,
  end at/.code={\pgfmathsetmacro\pgfdecorationendat{#1}},
  end at=.75,
  start by/.code={\pgfmathsetmacro\pgfdecorationstartby{#1}},
  start by=-.5,
  end by/.code={\pgfmathsetmacro\pgfdecorationendby{#1}},
  end by=-.5,
  base color/.code=\pgfutil@colorlet{base color}{#1},
  base color=white,
  amplitude=1cm
}

\newdimen\pgf@xd
\newdimen\pgf@xe
\newdimen\pgf@yd

\pgfdeclaredecoration{cylinder}{initial}
{
  \state{initial}[width=\pgfdecorationstartat*\pgfdecoratedinputsegmentlength,next state=draw]
  {}
  \state{draw}[width=0pt,next state=final]
  {
    \pgf@xe\pgfdecorationsegmentamplitude pt
    \pgf@xd\pgfdecorationstartby\pgf@xe \pgf@xe\pgfdecorationendby\pgf@xe
    \pgf@yd\pgfdecorationsegmentamplitude pt
    \pgfpathmoveto{\pgfqpoint{0pt}{\pgf@yd}}
    \pgfpatharcaxes{90}{270}{\pgfqpoint{\pgf@xd}{0pt}}{\pgfqpoint{0pt}{\pgf@yd}}
    \pgfpathlineto{\pgfpoint{(\pgfdecorationendat-\pgfdecorationstartat)*\pgfdecoratedinputsegmentlength}{-\pgf@yd}}
    \pgfpatharcaxes{-90}{90}{\pgfqpoint{\pgf@xe}{0pt}}{\pgfqpoint{0pt}{\pgf@yd}}
    \pgfclosepath
    \pgftransformyscale{\pgf@yd/50bp}
    \pgfgettransform\pgfcurrenttransform
    \pgfsetadditionalshadetransform{\pgfsettransform\pgfcurrenttransform}
    \pgfshadepath@revise{cylinder}
    \pgfusepath{}
  }
  \state{final}
  {}
}

\def\pgfshadepath@revise#1{%
    \begingroup%
      \pgfsys@beginscope%
        \pgfsyssoftpath@invokecurrentpath%
        \pgfsys@clipnext%
        \pgfsys@discardpath%
        \ifx\pgf@shade@extra@transform\pgfutil@empty%
        \else%
          \pgflowlevel{\pgf@shade@extra@transform}%
        \fi%
        \pgfuseshading{#1}%
      \pgfsys@endscope%
    \endgroup%
}

\tikz{
    \draw(-4,-1)--(4,1);
    \draw decorate[decoration=cylinder]{(-4,-1)--(4,1)};
}

We can decorate the same path multiple times.

\tikz{
    \draw(-4,-1)to[
        to path={
            decorate[decoration={cylinder,base color=red  ,start at=.0 ,end at=.2 ,start by= .5,end by=-.5}]{(\tikztostart)--(\tikztotarget)}
            decorate[decoration={cylinder,base color=blue ,start at=.25,end at=.45,start by= .5,end by= .5}]{(\tikztostart)--(\tikztotarget)}
            decorate[decoration={cylinder,base color=white,start at=.55,end at=.75,start by=-.5,end by= .5}]{(\tikztostart)--(\tikztotarget)}
            decorate[decoration={cylinder,base color=black,start at=.8,end at=1   ,start by=-.5,end by=-.5}]{(\tikztostart)--(\tikztotarget)}
        }
    ](4,1);
}

In your case we need only two times. We can write a parser. Here ) red ( means that the cylinder is in red, and is concave at both side. If you change it to ( blue ), it becomes a blue cylinder convex at both side. The number between two cylinders represents the position of the "cut".

\tikzset{
    bond/.code args={#1 #2 #3 #4 #5 #6 #7}{
        \if#1|\edef\Astartby{0}\else\if#1(\edef\Astartby{.5}\else\if#1)\edef\Astartby{-.5}\else\edef\Astartby{#1}\fi\fi\fi
        \if#3|\edef\Aendby{  0}\else\if#3(\edef\Aendby{ -.5}\else\if#3)\edef\Aendby{   .5}\else\edef\Aendby{  #3}\fi\fi\fi
        \if#5|\edef\Bstartby{0}\else\if#5(\edef\Bstartby{.5}\else\if#5)\edef\Bstartby{-.5}\else\edef\Bstartby{#5}\fi\fi\fi
        \if#7|\edef\Bendby{  0}\else\if#7(\edef\Bendby{ -.5}\else\if#7)\edef\Bendby{   .5}\else\edef\Bendby{  #7}\fi\fi\fi
        \tikzset{
            to path={
                decorate[decoration={cylinder,base color=#2,start at=0,end at=#4,start by=\Astartby,end by=\Aendby}]{(\tikztostart)--(\tikztotarget)}
                decorate[decoration={cylinder,base color=#6,start at=#4,end at=1,start by=\Bstartby,end by=\Bendby}]{(\tikztostart)--(\tikztotarget)}
            }
        }
    }
}

\tikz{
    \draw(-4,-1)to[bond=) red ( .4 ) white (](4,1);
}

Now go into Chemistry. We can define the style of bonds between different pairs of atoms. This is Urea, only that the double bond between carbon an nitrogen is not drawn properly.

\tikzset{
    C-O/.style={bond=( black | .4  | red )},
    C-N/.style={bond=( black | .45 | blue )},
    N-H/.style={bond=( blue  | .5  | white )},
    C/.style={circle,shade,minimum size=5cm,ball color=black},
    O/.style={circle,shade,minimum size=4cm,ball color=red},
    H/.style={circle,shade,minimum size=3cm,ball color=white},
    N/.style={circle,shade,minimum size=4.5cm,ball color=blue}
}

\tikz{
    \path(0,0)node[C](C1){}(0,10)node[O](O1){}(9,-3)node[N](N1){}(-9,-3)node[N](N2){}(9,-8)node[H](H1){}(13,0)node[H](H2){}(-9,-8)node[H](H3){}(-13,0)node[H](H4){};
    \path(C1)to[C-O](O1)(C1)to[C-N](N1)(C1)to[C-N](N2)(N1)to[N-H](H1)(N1)to[N-H](H2)(N2)to[N-H](H3)(N2)to[N-H](H4);
}

With our parser, we can indicate the 3D structure by the convexity of cylinders. This is methane.

\tikzset{
    C>H/.style={bond=( black ) .4  ) white )},
    C<H/.style={bond=( black ( .4  ( white )}
}

\tikz{
    \path(0,0)node[C](C1){}(8,0)node[H](H1){}(-8,0)node[H](H2){}(0,8)node[H](H3){}(0,-8)node[H](H4){};
    \path(C1)to[C>H](H1)(C1)to[C>H](H2)(C1)to[C<H](H3)(C1)to[C<H](H4);
}

Code

\documentclass[border=9,tikz]{standalone}
\usetikzlibrary{decorations.pathmorphing,topaths}

\begin{document}

\makeatletter

\pgfdeclareverticalshading[base color]{cylinder}{1000bp}{
  color(0.000bp)=(black!75!base color);
  color(22.773bp)=(black!75!base color);
  color(45.525bp)=(black!3.893!base color);
  color(53.986bp)=(white!26.261!base color);
  color(59.561bp)=(white!26.261!base color);
  color(68.022bp)=(black!3.893!base color);
  color(90.773bp)=(black!75!base color);
  color(100.000bp)=(black!75!base color)}

\pgfutil@colorlet{base color}{green}
\pgfuseshading{cylinder}


\pgfkeys{
  /pgf/decoration/.cd,
  start at/.code={\pgfmathsetmacro\pgfdecorationstartat{#1}},
  start at=.25,
  end at/.code={\pgfmathsetmacro\pgfdecorationendat{#1}},
  end at=.75,
  start by/.code={\pgfmathsetmacro\pgfdecorationstartby{#1}},
  start by=-.5,
  end by/.code={\pgfmathsetmacro\pgfdecorationendby{#1}},
  end by=-.5,
  base color/.code=\pgfutil@colorlet{base color}{#1},
  base color=white,
  amplitude=1cm
}

\newdimen\pgf@xd
\newdimen\pgf@xe
\newdimen\pgf@yd

\pgfdeclaredecoration{cylinder}{initial}
{
  \state{initial}[width=\pgfdecorationstartat*\pgfdecoratedinputsegmentlength,next state=draw]
  {}
  \state{draw}[width=0pt,next state=final]
  {
    \pgf@xe\pgfdecorationsegmentamplitude pt
    \pgf@xd\pgfdecorationstartby\pgf@xe \pgf@xe\pgfdecorationendby\pgf@xe
    \pgf@yd\pgfdecorationsegmentamplitude pt
    \pgfpathmoveto{\pgfqpoint{0pt}{\pgf@yd}}
    \pgfpatharcaxes{90}{270}{\pgfqpoint{\pgf@xd}{0pt}}{\pgfqpoint{0pt}{\pgf@yd}}
    \pgfpathlineto{\pgfpoint{(\pgfdecorationendat-\pgfdecorationstartat)*\pgfdecoratedinputsegmentlength}{-\pgf@yd}}
    \pgfpatharcaxes{-90}{90}{\pgfqpoint{\pgf@xe}{0pt}}{\pgfqpoint{0pt}{\pgf@yd}}
    \pgfclosepath
    \pgftransformyscale{\pgf@yd/50bp}
    \pgfgettransform\pgfcurrenttransform
    \pgfsetadditionalshadetransform{\pgfsettransform\pgfcurrenttransform}
    \pgfshadepath@revise{cylinder}
    \pgfusepath{}
  }
  \state{final}
  {}
}

\def\pgfshadepath@revise#1{%
    \begingroup%
      \pgfsys@beginscope%
        \pgfsyssoftpath@invokecurrentpath%
        \pgfsys@clipnext%
        \pgfsys@discardpath%
        \ifx\pgf@shade@extra@transform\pgfutil@empty%
        \else%
          \pgflowlevel{\pgf@shade@extra@transform}%
        \fi%
        \pgfuseshading{#1}%
      \pgfsys@endscope%
    \endgroup%
}

\tikz{
    \draw(-4,-1)--(4,1);
    \draw decorate[decoration=cylinder]{(-4,-1)--(4,1)};
}

\tikz{
    \draw(-4,-1)to[
        to path={
            decorate[decoration={cylinder,base color=red  ,start at=.0 ,end at=.2 ,start by= .5,end by=-.5}]{(\tikztostart)--(\tikztotarget)}
            decorate[decoration={cylinder,base color=blue ,start at=.25,end at=.45,start by= .5,end by= .5}]{(\tikztostart)--(\tikztotarget)}
            decorate[decoration={cylinder,base color=white,start at=.55,end at=.75,start by=-.5,end by= .5}]{(\tikztostart)--(\tikztotarget)}
            decorate[decoration={cylinder,base color=black,start at=.8,end at=1   ,start by=-.5,end by=-.5}]{(\tikztostart)--(\tikztotarget)}
        }
    ](4,1);
}

\tikzset{
    bond/.code args={#1 #2 #3 #4 #5 #6 #7}{
        \if#1|\edef\Astartby{0}\else\if#1(\edef\Astartby{.5}\else\if#1)\edef\Astartby{-.5}\else\edef\Astartby{#1}\fi\fi\fi
        \if#3|\edef\Aendby{  0}\else\if#3(\edef\Aendby{ -.5}\else\if#3)\edef\Aendby{   .5}\else\edef\Aendby{  #3}\fi\fi\fi
        \if#5|\edef\Bstartby{0}\else\if#5(\edef\Bstartby{.5}\else\if#5)\edef\Bstartby{-.5}\else\edef\Bstartby{#5}\fi\fi\fi
        \if#7|\edef\Bendby{  0}\else\if#7(\edef\Bendby{ -.5}\else\if#7)\edef\Bendby{   .5}\else\edef\Bendby{  #7}\fi\fi\fi
        \tikzset{
            to path={
                decorate[decoration={cylinder,base color=#2,start at=0,end at=#4,start by=\Astartby,end by=\Aendby}]{(\tikztostart)--(\tikztotarget)}
                decorate[decoration={cylinder,base color=#6,start at=#4,end at=1,start by=\Bstartby,end by=\Bendby}]{(\tikztostart)--(\tikztotarget)}
            }
        }
    }
}

\tikz{
    \draw(-4,-1)to[bond=) red ( .4 ) white (](4,1);
}

\tikzset{
    C-O/.style={bond=( black | .4  | red )},
    C-N/.style={bond=( black | .45 | blue )},
    N-H/.style={bond=( blue  | .5  | white )},
    C/.style={circle,shade,minimum size=5cm,ball color=black},
    O/.style={circle,shade,minimum size=4cm,ball color=red},
    H/.style={circle,shade,minimum size=3cm,ball color=white},
    N/.style={circle,shade,minimum size=4.5cm,ball color=blue}
}

\tikz{
    \path(0,0)node[C](C1){}(0,10)node[O](O1){}(9,-3)node[N](N1){}(-9,-3)node[N](N2){}(9,-8)node[H](H1){}(13,0)node[H](H2){}(-9,-8)node[H](H3){}(-13,0)node[H](H4){};
    \path(C1)to[C-O](O1)(C1)to[C-N](N1)(C1)to[C-N](N2)(N1)to[N-H](H1)(N1)to[N-H](H2)(N2)to[N-H](H3)(N2)to[N-H](H4);
}

\tikzset{
    C>H/.style={bond=( black ) .4  ) white )},
    C<H/.style={bond=( black ( .4  ( white )}
}

\tikz{
    \path(0,0)node[C](C1){}(8,0)node[H](H1){}(-8,0)node[H](H2){}(0,8)node[H](H3){}(0,-8)node[H](H4){};
    \path(C1)to[C>H](H1)(C1)to[C>H](H2)(C1)to[C<H](H3)(C1)to[C<H](H4);
}

\end{document}
| improve this answer | |
  • Wow, after such a long time I hadn't expected to get such an awesome answer to this question. You did much more than I actually asked for. Thank you very much. – Philipp Jan 22 '16 at 16:18
  • I hope you could help me understand some part of your code better: Your definition of \pgfshadepath@revise is beyond me but I feel like I should be able to understand the rest. Yet the first three line \state{draw} block of the cylinder decoration (those that deal with \pgf@xe, \pgf@xd, and \pgf@yd) are a bit unclear to me. The first and third lines seem to set the values for \pgf@xe and \pgf@yd to \pgfdecorationsegmentamplitude in units of pt, right? ... – Philipp Jan 22 '16 at 16:45
  • ...But what does the second line do? Does it set the value of \pgf@xd to the product of \pgfdecorationstartby and \pgf@xe and analoguously the value of \pgf@xe to \pgfdecorationendby time \pgf@xe? Or do I have that completely wrong? – Philipp Jan 22 '16 at 16:47
  • @Philipp The definition of \pgfshadepath@revise is basically copied from that of \pgfshadepath. I cannot say I understand it; I just choose the part I need. What you said about those dimensions are absolutely right. It is fine to omit = in \pgf@xd=10pt so one can write \pgf@xd10pt. Also it is legal to say \pgf@xd=2\pgf@xe. It is the only way to do multiplication in vanilla TeX. – Symbol 1 Jan 22 '16 at 16:52
  • Thank you once again. I hope I can soon find the time to use your code for my old molecule-drawing program. – Philipp Jan 22 '16 at 16:59

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.