4

I'm trying to make paths that are shaded to as to appear as cylinders with spherical ends.

I've found a way to do this with decorations.

I use vertical shading on rectangles for the path segments, and radial shading on circles for the path ends.

I need to get the ends visually behind the segments. I have thought of two ways to do this.

One method is to draw both ends before I draw the segments. The downside of this is that I don't have the right angle for the radial shading.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{decorations}
\usetikzlibrary{shadings}

\pgfdeclarelayer{barends}
\pgfsetlayers{barends,main}
\pgfdeclaredecoration{cylindricalbar}{initial}
{
  \state{initial}[width=2pt,next state=middle]{
  % sphere on end
  \begin{pgfscope}
  \pgfpathcircle{\pgfpointdecoratedpathlast}{5pt}
  \pgfshadepath{barendshading}{\pgfdecoratedangle}
  \end{pgfscope}
  % sphere on start
  \begin{pgfscope}
  \pgfpathcircle{\pgfpoint{0}{0}}{5pt}
  \pgfshadepath{barendshading}{\pgfdecoratedangle}
  \end{pgfscope}
  % first cylindrical segment
  \begin{pgfscope}
  \pgfpathrectanglecorners
    {\pgfpoint{-1pt}{-5pt}}
    {\pgfpoint{3pt}{5pt}}
  \pgfshadepath{barshading}{\pgfdecoratedangle}
  \end{pgfscope}
  }
  \state{middle}[width=2pt]
  {
    \begin{pgfscope}
    \pgfpathrectanglecorners
      {\pgfpoint{-1pt}{-5pt}}
      {\pgfpoint{3pt}{5pt}}
    \pgfshadepath{barshading}{\pgfdecoratedangle}
    \end{pgfscope}
  }
  \state{final}
  {
  }
} 

\begin{document}
\begin{tikzpicture}[decoration=cylindricalbar]
    \pgfdeclareradialshading[mycolor,white]
      {barendshading}
      {\pgfpoint{0bp}{5bp}}
      {color(0bp)=(white);
       color(28bp)=(mycolor);
       color(60bp)=(mycolor)}

    \pgfdeclareverticalshading[mycolor,white]
      {barshading}
      {100bp}
      {color(0bp)=(mycolor); 
       color(25bp)=(mycolor); 
       color(55bp)=(white); 
       color(75bp)=(mycolor);
       color(100bp)=(mycolor)}


  \colorlet{mycolor}{green}
  \path [decorate]     (0,0) -- (0,3);
  \colorlet{mycolor}{red}
  \path [decorate]     (3,0) .. controls (3,2) and  (2,3) ..  (0,3);
  \colorlet{mycolor}{blue}
  \path [decorate] (0,0) -- (3,0);
 \end{tikzpicture}
\end{document}

bar with misaligned shading on end

The second method is to put the bar ends on a layer that is placed behind the main layer. The downside to this is that if I draw two different bars, the end of the second bar lies behind the body of the first bar so it looks strange.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{decorations}
\usetikzlibrary{shadings}

\pgfdeclarelayer{barends}
\pgfsetlayers{barends,main}
\pgfdeclaredecoration{cylindricalbar}{initial}
{
  \state{initial}[width=2pt,next state=middle]{
  % sphere on start
  \begin{pgfscope}
  \pgfpathcircle{\pgfpoint{0}{0}}{5pt}
  \pgfshadepath{barendshading}{\pgfdecoratedangle}
  \end{pgfscope}
  % first cylindrical segment
  \begin{pgfscope}
  \pgfpathrectanglecorners
    {\pgfpoint{-1pt}{-5pt}}
    {\pgfpoint{3pt}{5pt}}
  \pgfshadepath{barshading}{\pgfdecoratedangle}
  \end{pgfscope}
  }
  \state{middle}[width=2pt]
  {
    \begin{pgfscope}
    \pgfpathrectanglecorners
      {\pgfpoint{-1pt}{-5pt}}
      {\pgfpoint{3pt}{5pt}}
    \pgfshadepath{barshading}{\pgfdecoratedangle}
    \end{pgfscope}
  }
  \state{final}
  {
  % sphere on end
  \begin{pgfscope}
  \begin{pgfonlayer}{barends}
  \pgfpathcircle{\pgfpointdecoratedpathlast}{5pt}
  \pgfshadepath{barendshading}{\pgfdecoratedangle}
  \end{pgfonlayer}
  \end{pgfscope}
  }
} 

\begin{document}
\begin{tikzpicture}[decoration=cylindricalbar]
    \pgfdeclareradialshading[mycolor,white]
      {barendshading}
      {\pgfpoint{0bp}{5bp}}
      {color(0bp)=(white);
       color(28bp)=(mycolor);
       color(60bp)=(mycolor)}

    \pgfdeclareverticalshading[mycolor,white]
      {barshading}
      {100bp}
      {color(0bp)=(mycolor); 
       color(25bp)=(mycolor); 
       color(55bp)=(white); 
       color(75bp)=(mycolor);
       color(100bp)=(mycolor)}


  \colorlet{mycolor}{green}
  \path [decorate]     (0,0) -- (0,3);
  \colorlet{mycolor}{red}
  \path [decorate]     (3,0) .. controls (3,1) and  (1,3) ..  (0,3);
  \colorlet{mycolor}{blue}
  \path [decorate] (0,0) -- (3,0);
 \end{tikzpicture}
\end{document}

bar with squared ends

Is there any way to (1) get the \pgfdecoratedangle of the final segment during while drawing the ininital segment, or (2) Put the final segment automatically on a layer below the the previous segment, but above all the segments on a previously drawn path?

I realize that I could create a bar end layer for each bar, and properly arrange the layers, but I'd rather not have to add a layer for each bar.

2

You can defined two decorations (the first to draw the spheres and the second to draw the segments) and use preaction and postaction to apply them in the right order.

\documentclass{standalone}
\usepackage{tikz}
%\usetikzlibrary{calc}
\usetikzlibrary{decorations}
\usetikzlibrary{shadings}

\def\cylindricalsphere{
  \begin{pgfscope}
    \pgfpathcircle{\pgfpoint{0}{0}}{5pt}
    \pgfshadepath{barendshading}{\pgfdecoratedangle}
  \end{pgfscope}
}
\pgfdeclaredecoration{cylindricalbarspheres}{initial}
{
  \state{initial}[width=1pt,next state=middle]{\cylindricalsphere}
  \state{middle}[width=1pt]{}
  \state{final}{\cylindricalsphere}
}
\def\cylindricalsgement{
  \begin{pgfscope}
    \pgfpathrectanglecorners{\pgfpoint{-.55pt}{-5pt}}{\pgfpoint{.55pt}{5pt}}
    \pgfshadepath{barshading}{\pgfdecoratedangle}
  \end{pgfscope}
}
\pgfdeclaredecoration{cylindricalbarsegments}{initial}
{
  \state{initial}[width=1pt,next state=middle]{\cylindricalsgement}
  \state{middle}[width=1pt]{\cylindricalsgement}
  \state{final}{}
} 

\tikzset{
  cylindricalbar/.style={
    preaction={decorate,decoration=cylindricalbarspheres},
    postaction={decorate,decoration=cylindricalbarsegments},
  }
}


\begin{document}
\begin{tikzpicture}
  \pgfdeclareradialshading[mycolor,white]
  {barendshading}{\pgfpoint{0bp}{5bp}}
  {color(0bp)=(white);color(28bp)=(mycolor);color(60bp)=(mycolor)}

  \pgfdeclareverticalshading[mycolor,white]
  {barshading}{100bp}
  {color(0bp)=(mycolor);color(25bp)=(mycolor); 
    color(55bp)=(white);color(75bp)=(mycolor);color(100bp)=(mycolor)}

  \colorlet{mycolor}{green}
  \path [cylindricalbar]     (0,0) -- (0,3);

  \colorlet{mycolor}{red}
  \path [cylindricalbar]     (4,0) arc(0:90:4);

  \colorlet{mycolor}{red}
  \path [cylindricalbar]     (0,3.5) arc(90:0:3.5);

  \colorlet{mycolor}{blue}
  \path [cylindricalbar] (0,1) -- (2,1);
  \colorlet{mycolor}{blue}
  \path [cylindricalbar] (3,0) -- (0,0);
 \end{tikzpicture}
\end{document}

enter image description here

  • This is perfect. You captured the asymmetry I was after, and have the ends working correctly. I had read about preaction and postaction, but I didn't realize that the whole decoration could be placed in preaction or postaction. – Carl Sorensen Feb 19 '17 at 14:47
2

I'm not 100% certain about the required output, but another way of faking rounded ends could be use the line cap=round key and getting the shading using repeated \draw commands with different line widths:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations}
\pgfdeclaredecoration{shade}{final}{
  \state{final}{%
    \edef\currentlinewidth{\the\pgflinewidth}%
    \foreach \i in {100,98,...,0}
      \draw [line cap=round, line join=round, line width=\i/100*\currentlinewidth, 
        shade.outer.color!\i!shade.inner.color]
          \pgfextra{\pgfsetpath\pgfdecoratedpath};
  }
}
\pgfkeys{/pgf/decoration/.cd,
  inner color/.code=\colorlet{shade.inner.color}{#1},
  outer color/.code=\colorlet{shade.outer.color}{#1},
}
\tikzset{shade line/.style={
    decoration={shade, outer color=#1, inner color=white}, decorate
}}
\begin{document}
\begin{tikzpicture}[line width=5pt]

\path [shade line=green] (0,0) -- (0,3);
\path [shade line=red]   (0,3) arc (90:0:3);
\path [shade line=blue]  (0,0) -- (3,0);
\end{tikzpicture}
\end{document} 

enter image description here

  • Here, the shading is symmetric. – Paul Gaborit Feb 19 '17 at 10:21
  • Yes, and I wanted the shading to be not quite symmetric. If the shading were symmetric, the existing code would work. – Carl Sorensen Feb 19 '17 at 14:45
1

If this is the desired output:

enter image description here

credits should go to Paul Gaborit for his cylindricalbar and invclip codes.

\documentclass{standalone}
\usepackage{tikz}
%\usetikzlibrary{calc}
\usetikzlibrary{decorations}
\usetikzlibrary{shadings}

\def\cylindricalsphere{
  \begin{pgfscope}
    \pgfpathcircle{\pgfpoint{0}{0}}{5pt}
    \pgfshadepath{barendshading}{\pgfdecoratedangle}
  \end{pgfscope}
}
\pgfdeclaredecoration{cylindricalbarspheres}{initial}
{
  \state{initial}[width=1pt,next state=middle]{\cylindricalsphere}
  \state{middle}[width=1pt]{}
  \state{final}{\cylindricalsphere}
}
\def\cylindricalsgement{
  \begin{pgfscope}
    \pgfpathrectanglecorners{\pgfpoint{-.55pt}{-5pt}}{\pgfpoint{.55pt}{5pt}}
    \pgfshadepath{barshading}{\pgfdecoratedangle}
  \end{pgfscope}
}
\pgfdeclaredecoration{cylindricalbarsegments}{initial}
{
  \state{initial}[width=1pt,next state=middle]{\cylindricalsgement}
  \state{middle}[width=1pt]{\cylindricalsgement}
  \state{final}{}
} 

\tikzset{
  cylindricalbar/.style={
    preaction={decorate,decoration=cylindricalbarspheres},
    postaction={decorate,decoration=cylindricalbarsegments},
  }
}

\tikzset{invclip/.style={clip,insert path={{[reset cm]
      (-\maxdimen,-\maxdimen) rectangle (\maxdimen,\maxdimen)
    }}}}


\begin{document}
\begin{tikzpicture}
  \pgfdeclareradialshading[mycolor,white]
  {barendshading}{\pgfpoint{0bp}{5bp}}
  {color(0bp)=(white);color(28bp)=(mycolor);color(60bp)=(mycolor)}

  \pgfdeclareverticalshading[mycolor,white]
  {barshading}{100bp}
  {color(0bp)=(mycolor);color(25bp)=(mycolor); 
    color(55bp)=(white);color(75bp)=(mycolor);color(100bp)=(mycolor)}


  \colorlet{mycolor}{green}
  \path [cylindricalbar]     (0,0) -- (0,3);

  \colorlet{mycolor}{red}
  \path [cylindricalbar]     (0,3) arc(90:0:3);

\begin{pgfinterruptboundingbox}
\path[invclip] (-5pt,0) arc (-180:0:5pt)|-(-5pt,6pt);
\end{pgfinterruptboundingbox}
  \colorlet{mycolor}{blue}
  \path [cylindricalbar] (3,0) -- (0,0);
   \end{tikzpicture}
\end{document}

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.