I'm trying to create a cylinder with 3D shading effects. The cylinders in the picture are standing vertically, and have a body and an end. I'd like the body to be shaded so that it varies uniformly from dark on the left side of the cylinder body to light in the middle (facing the viewer) to dark on the right again (light shading effect). The color should remain the same in a vertical direction. If the same is possible for the cylinder end that would be nice, but it is not so important.

I did some experimentation. Cylinder A below has some shading effect, but I have no idea how I got it. I don't even know whether it makes sense to use shade as an argument to a node, except that it is clearly doing something.

Cylinder B is similar to what I want, except for the shading effect.

This was tested with PGF 2.0 and Tex Live 2009 on Debian squeeze.

\documentclass{article}
\usepackage{tikz}
\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\begin{document}
\pagestyle{empty}
\begin{tikzpicture}
\tikzset{draw, name=s, shape=cylinder, line width=0.1cm, shape
border rotate=90, aspect=.2, inner xsep=3cm, inner ysep=2cm,
cylinder uses custom fill, cylinder end fill=blue!25, cylinder
body fill=blue!40};
\node [aspect=0.25, shade={left color=red!20,right color=blue!50}] at (1,0) {A};
\node[aspect=0.10] at (10,0) {B};
\end{tikzpicture}
\end{document}

• As far as I can tell, even in pgf 2.10, there seems to be no way to shade the cylinder body and the cylinder end separately. Any shading or filling that you apply to a cylinder node will be applied to the outline path of the node. I think the only ways to do it are: 1) define a custom shape by modifying the cylinder shape, or 2) don't use shapes and draw the cylinders from scratch. – Jan Hlavacek Feb 9 '11 at 16:57
• Hi Jan. Ok, how would one shade them together? I'd also be interested in solutions to the other two possibilities you suggest. – Faheem Mitha Feb 9 '11 at 17:03
• I'd also like to know what is going on with cylinder A? Though should that be a separate question? – Faheem Mitha Feb 9 '11 at 18:56
• For the cylinder A, you specify shading for the node. TikZ shades the node like you asked it, but for the cylinder, it considers the node to be the whole cylinder. That's what I meant by writing that you cannot shade the body and the end separately. It should be possible to modify the code in pgflibraryshapes.geometric.code.tex and add keys and code for shading, but I don't have time to do that right now. Give me few days, I will try something, unless someone else comes up with a better solution. – Jan Hlavacek Feb 9 '11 at 19:20
• Hi Jan, Well except that for my code for cylinder A the shading is the wrong way (it goes from top to bottom, not left to right) and the color is a sort of grey. If appropriate and useful, I could upload the figure here. – Faheem Mitha Feb 9 '11 at 21:31

For creating a standalone cylinder, without using the predefined TikZ shapes, you can do something like this:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\pagestyle{empty}
\begin{tikzpicture}
\coordinate (ll) at (-3,-2);
\coordinate (lr) at (3,-2);
\coordinate (ul) at (-3,2);
\coordinate (ur) at (3,2);
\shade [shading angle=90] (ll) arc (-180:-60:3cm and .75cm) -- +(0,4) arc (-60:-180:3cm and .75cm) -- cycle;
\shade [shading angle=270] (lr) arc (0:-60:3cm and .75cm) -- +(0,4) arc (-60:0:3cm and .75cm) -- cycle;
\draw [thick] (ll) arc (-180:0:3cm and .75cm) -- (ur) arc (0:-180:3cm and .75cm) -- cycle;
\node at (0,-.75){\Huge A};
\end{tikzpicture}
\end{document}

• Hi Jan, that looks good. I'd still be interested in a more 'automated' solution to the problem though. – Faheem Mitha Feb 9 '11 at 20:42

You can use the predefined cylinder shape and then draw an ellipse with a solid fill on top. With the following code, you can supply all the options to the cylinder shape as before, the let code in the ellipse takes care of the position, size and rotation of the ellipse. You only need to adjust the colour.

The operation let \p<number> = (<coordinate>) in ... is described on p. 150 (section 14.15) in the pgf manual: It assigns a point described by (<coordinate>) to the local variable \p<number>, where <number> can be arbitrarily chosen. The variables \x<number> and \y<number> then contain the x and y values of the point. The registers \n<number> can be used to store results from mathematical operations.

The construct <coordinate>!<number>!<second coordinate> is described on p. 135 (section 13.5.5) in the manual: It describes a point along the line from <first coordinate> to <second coordinate>, where the value 0 would be at the first point, 1 at the second, and 0.5 halfway in between.

The points (cyl.before top), (cyl.after top) and (cyl.top) are defined by the cylinder shape (see p. 434, section 48.3 in the pgf manual).

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric,calc}
\begin{document}
\begin{tikzpicture}

\node [draw,
shape=cylinder,
name=nodename, % Can be defined arbitrarily
alias=cyl, % Will be used by the ellipse to reference the cylinder
aspect=1.5,
minimum height=3cm,
minimum width=2cm,
left color=blue!30,
right color=blue!60,
middle color=red!20, % Has to be called after left color and middle color
outer sep=-0.5\pgflinewidth, % to make sure the ellipse does not draw over the lines
shape border rotate=90
] at (1,2) {A};

\fill [red!20] let
\p1 = ($(cyl.before top)!0.5!(cyl.after top)$),
\p2 = (cyl.top),
\p3 = (cyl.before top),
\n1={veclen(\x3-\x1,\y3-\y1)},
\n2={veclen(\x2-\x1,\y2-\y1)},
\n3={atan2((\y2-\y1),(\x2-\x1))}
in

\end{tikzpicture}
\end{document}


Version for PGF 2.0

For PGF 2.0, the code needs to be adapted slightly, because the mathematical function atan2 is missing in the old version and the syntax for an ellipse is different.

The operation let \p = () in ... is described on p. 127 (section 13.14) in the pgf 2.0 manual ('The Let Operation').

The construct <coordinate>!<number>!<coordinate> is described on p. 116, section 12.4.3 in the pgf 2.0 manual.

The points (cyl.before top), (cyl.after top) and (cyl.top) are defined by the cylinder shape (see p. 326, section 39.3 ('Geometric Shapes') in the pgf 2.0 manual).

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric,calc}
\begin{document}
\begin{tikzpicture}

\node [draw,
shape=cylinder,
name=nodename, % Can be defined arbitrarily
alias=cyl, % Will be used by the ellipse to reference the cylinder
aspect=1.5,
minimum height=3cm,
minimum width=2cm,
left color=blue!30,
right color=blue!60,
middle color=red!20, % Has to be called after left color and middle color
outer sep=-0.5\pgflinewidth, % to make sure the ellipse does not draw over the lines
shape border rotate=90
] at (1,2) {A};

\fill [red!20] let
\p1 = ($(cyl.before top)!0.5!(cyl.after top)$),
\p2 = (cyl.top),
\p3 = (cyl.before top),
\n1={veclen(\x3-\x1,\y3-\y1)},
\n2={veclen(\x2-\x1,\y2-\y1)}
in
(\p1) ellipse (\n1 and \n2);

\end{tikzpicture}
\end{document}

• Hi Jake. Well, that looks very fancy. I'll have to study it. Hmm. I get ERROR: Package PGF Math Error: Unknown function atan2'. – Faheem Mitha Feb 9 '11 at 21:11
• @Faheem: Thank you, I'm flattered =) My understanding is that it is appropriate to ask questions directly related to the answer (like "why did you use atan2 here?"), but if more general topics are touched upon ("How do I rotate predefined shapes by an arbitrary angle?"), they should be in a separate question. – Jake Feb 9 '11 at 21:15
• Sorry, I guess I edited at the same time as you answered. I'm guessing that atan2 is only in 2.10? – Faheem Mitha Feb 9 '11 at 21:17
• @Faheem: Ah, it seems that this very useful function is not included in pgf v 2.0 (you should upgrade to 2.10 independently of this issue, there are many excellent features in the current version!). If you only need vertical cylinders, you can remove the line \n3=..., the final comma in the line before that, and the rotate=\n3 command in the ellipse options, and everything should work fine. – Jake Feb 9 '11 at 21:20
• @Jake: Thanks for all your assistance with this. I learned a lot! – Faheem Mitha Feb 15 '11 at 7:47

Just for fun. The above answers are great. However, Jan's answer doesn't create a node. Jake's fantastic answer constructs the ellipse in two steps. This is not at all a bad thing, but one may want to construct it in one go. Here is a way to do it in one step. I did not come up with the path of the end, rather this is just copied from the shapes.geometric library. It seems possible to (ab)use path pictures to "host" some pgf code that one finds some other places. I do not know if that has been mentioned before on this site, if it was, I will be happy to give proper credit, nor do I know if there are major pitfalls. I only did very limited testing, so please use this with care.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{shapes.geometric}
\begin{document}
\tikzset{cylinder end fill/.style={path picture={
\pgftransformshift{\centerpoint}%
\pgftransformrotate{\rotate}%
\pgfpathmoveto{\beforetop}%
\pgfpathclose
\pgfsetfillcolor{#1}%
\pgfusepath{fill}}
}}
\begin{tikzpicture}
\pgfmathsetmacro{\myaspect}{pi/4}
\node [draw,
shape=cylinder,
aspect=\myaspect,
minimum height=3cm,
minimum width=2cm,
rotate=20,
cylinder end fill=blue,
left color=blue!30,
right color=black,
middle color=blue!80,
font=\sffamily,
text width=pi*1cm,
text=white]  {Hello world, ducks, koalas and marmots!};
\end{tikzpicture}
\end{document}


OLDER ANSWER: (unnecessarily complicated) In this answer, a "new" shape gets declared based on the standard cylinder. I did not extensively test it, I merely did it to learn something new. The new thing I learned is in the upper part of the answer.

\documentclass{beamer}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\makeatletter

%
% /pgf/cylinder body top shade  : Custom shading color for the cylinderbody.
% /pgf/cylinder body bottom shade : Custom shading color for the cylinderbody.
%

\pgfkeys{/pgf/.cd,
cylinder end fill/.initial=white,
}

\savedmacro\getcylinderpoints{%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/inner xsep}}%
\pgf@x\pgf@xc%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/inner ysep}}%
\pgf@y\pgf@yc%
\ifpgfshapeborderusesincircle%
\pgfmathsetmacro\rotate{\pgfkeysvalueof{/pgf/shape border rotate}}%
\ifdim\pgf@x<\pgf@y%
\pgf@x\pgf@y%
\else%
\pgf@y\pgf@x%
\fi%
\pgf@x1.414213\pgf@x%
\pgf@y1.414213\pgf@y%
\else%
\pgfmathmod{\pgfkeysvalueof{/pgf/shape border rotate}}{360}%
\ifdim\pgfmathresult pt<0pt\relax%
\fi%
\pgfmathsetcount\c@pgf@counta{+\pgfmathresult}%
\divide\c@pgf@counta90\relax%
\multiply\c@pgf@counta90\relax%
\edef\rotate{\the\c@pgf@counta}%
\ifnum\c@pgf@counta=90\relax%
\pgf@xa\pgf@x%
\pgf@x\pgf@y%
\pgf@y\pgf@xa%
\pgf@yc\pgf@xc%
\else%
\ifnum\c@pgf@counta=270\relax%
\pgf@xa\pgf@x%
\pgf@x\pgf@y%
\pgf@y\pgf@xa%
\pgf@yc\pgf@xc%
\fi%
\fi%
\fi%
\pgf@xa\pgf@x%
\pgf@ya\pgf@y%
\pgfutil@tempdima\pgfshapeaspect\pgf@ya%
\pgfutil@tempdimb\pgf@ya%
%
%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/minimum width}}%
\ifdim\pgfutil@tempdimb<.5\pgf@xc\relax%
\pgfutil@tempdimb.5\pgf@xc%
\pgf@ya\pgfutil@tempdimb%
\fi%
%
% Calculate how far the node contents can extend into the cylinder bottom.
%
\pgf@yb\pgfutil@tempdimb%
\pgfmathdivide@{\pgfmath@tonumber{\pgf@yb}}{\pgfmath@tonumber{\pgfutil@tempdimb}}%
\pgfmathasin@{\pgfmathresult}%
\pgfmathcos@{\pgfmathresult}%
\let\angle\pgfmathresult%
\pgf@xb\pgfmathresult\pgfutil@tempdima%
%
%
\pgf@x.5\pgflinewidth%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/minimum height}}%
\ifdim\pgf@x<\pgf@xc%
\fi%
%
%
\pgf@x\pgfutil@tempdima\relax%
\pgf@y\pgfutil@tempdimb\relax%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/outer xsep}}%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/outer ysep}}%
\ifdim\pgf@xc>\pgf@yc%
\edef\outersep{\the\pgf@xc}%
\else%
\edef\outersep{\the\pgf@yc}%
\fi%
%
\pgfextract@process\cylindercenter{%
\pgf@x\pgfutil@tempdima%
\pgf@x.5\pgf@x%
\pgf@y0pt%
}%
%
\pgfextract@process\beforetop{%
\pgf@x\pgf@xa%
\pgf@y\pgf@ya%
}%
\pgfextract@process\afterbottom{%
\pgf@x-\pgf@xa%
\pgf@y\pgf@ya%
}%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/outer ysep}}%
\pgfextract@process\beforetopanchor{%
\beforetop%
}%
\pgfextract@process\afterbottomanchor{%
\afterbottom%
}%
%
\beforetopanchor%
\ifdim\pgf@x>\pgf@y%
\else%
\fi%
}
\savedanchor\centerpoint{%
\pgf@x.5\wd\pgfnodeparttextbox%
\pgf@y.5\ht\pgfnodeparttextbox%
}%
\savedanchor\midpoint{%
\pgf@x.5\wd\pgfnodeparttextbox%
\pgfmathsetlength\pgf@y{+0.5ex}%
}%
\savedanchor\basepoint{%
\pgf@x.5\wd\pgfnodeparttextbox%
\pgf@y0pt%
}%
\anchor{center}{\centerpoint}
\anchor{shape center}{%
\getcylinderpoints%
{\centerpoint}{\rotate}%
}%
\anchor{mid}{\midpoint}%
\anchor{mid east}{%
\getcylinderpoints%
\let\pgf@cylinder@referencepoint\midpoint%
}%
\anchor{mid west}{%
\getcylinderpoints%
\let\pgf@cylinder@referencepoint\midpoint%
}%
\anchor{base}{\basepoint}%
\anchor{base east}{%
\getcylinderpoints%
\let\pgf@cylinder@referencepoint\basepoint%
}%
\anchor{base west}{%
\getcylinderpoints%
\let\pgf@cylinder@referencepoint\basepoint%
}%
\anchor{north}{%
\getcylinderpoints%
}%
\anchor{south}{%
\getcylinderpoints%
}%
\anchor{east}{%
\getcylinderpoints%
}%
\anchor{west}{%
\getcylinderpoints%
}%
\anchor{north east}{%
\getcylinderpoints%
}%
\anchor{south west}{%
\getcylinderpoints%
}%
\anchor{south east}{%
\getcylinderpoints%
}%
\anchor{north west}{%
\getcylinderpoints%
}%
\anchor{before top}{%
\getcylinderpoints%
}
\anchor{top}{%
\getcylinderpoints%
\pgfmathrotatepointaround{%
\beforetop%
\pgf@y0pt\relax%
}{\centerpoint}}{\centerpoint}{\rotate}%
}
\anchor{after top}{%
\getcylinderpoints%
}
\anchor{before bottom}{%
\getcylinderpoints%
}
\anchor{bottom}{%
\getcylinderpoints%
\pgfmathrotatepointaround{%
\afterbottom%
\pgf@y0pt\relax%
}{\centerpoint}}{\centerpoint}{\rotate}%
}
\anchor{after bottom}{%
\getcylinderpoints%
}
\backgroundpath{%
\getcylinderpoints%
{%
\pgftransformshift{\centerpoint}%
\pgftransformrotate{\rotate}%
\pgfpathmoveto{\afterbottom}%
\pgfpathlineto{\beforetop\pgf@y-\pgf@y}%
\pgfpathclose%
\pgfpathmoveto{\beforetop}%
}%
}%
\behindbackgroundpath{%
\ifpgfcylinderusescustomfill%
\getcylinderpoints%
{%
\pgftransformshift{\centerpoint}%
\pgftransformrotate{\rotate}%
\pgfpathmoveto{\afterbottom}%
\pgfpathlineto{\beforetop\pgf@y-\pgf@y}%
\pgfpathclose%
\expandafter\pgfsetfillcolor\expandafter{\pgfkeysvalueof{/pgf/cylinder body fill}}%
\pgfusepath{fill}%
%
\pgfpathmoveto{\beforetop}%
\pgfpathclose
\expandafter\pgfsetfillcolor\expandafter{\pgfkeysvalueof{/pgf/cylinder end fill}}%
\pgfusepath{fill}%
}%
\fi%
\getcylinderpoints%
{%
%
\pgftransformshift{\centerpoint}%
\pgftransformrotate{\rotate}%
\begin{pgfscope}%
\pgfpathmoveto{\afterbottom}%
\pgfpathlineto{\beforetop\pgf@y-\pgf@y}%
\pgfpathclose%
\pgfsetfillcolor{white}%
\pgfsetfillopacity{0}%
\pgfusepath{fill}%
\end{pgfscope}
%
\pgfpathmoveto{\beforetop}%
\pgfpathclose
\expandafter\pgfsetfillcolor\expandafter{\pgfkeysvalueof{/pgf/cylinder end fill}}%
\pgfusepath{fill}%
}%
\fi%
}%
\anchorborder{%
\pgfextract@process\externalpoint{}%
\getcylinderpoints%
\pgfutil@ifundefined{pgf@cylinder@referencepoint}{\let\referencepoint\centerpoint}{%
\let\referencepoint\pgf@cylinder@referencepoint}%
\pgfextract@process\externalpoint{%
\externalpoint%
\pgf@xa\pgf@x%
\pgf@ya\pgf@y%
\referencepoint%
}%
\pgfmathanglebetweenpoints{\centerpoint}{\externalpoint}%
\pgfmathsubtract@{\pgfmathresult}{\rotate}%
\ifdim\pgfmathresult pt<0pt\relax%
\fi%
\let\externalangle\pgfmathresult%
%
\ifdim\externalangle pt<\pgfmathresult pt\relax%
\ifdim\externalangle pt<\pgfmathresult pt\relax%
\pgfmathrotatepointaround{%
\pgfmathpointintersectionoflineandarc%
{\pgfmathrotatepointaround{\externalpoint}{\centerpoint}{-\rotate}}%
{\pgfmathrotatepointaround{\referencepoint}{\centerpoint}{-\rotate}}%
{%
\beforetop%
\pgf@xa\pgf@x%
\centerpoint%
}%
}{\centerpoint}{\rotate}%
\else%
\pgfpointintersectionoflines{%
{\centerpoint}{\rotate}}{%
{\centerpoint}{\rotate}}%
{\referencepoint}{\externalpoint}%
\fi%
\else%
\ifdim\externalangle pt>\pgfmathresult pt\relax%
\ifdim\externalangle pt>\pgfmathresult pt\relax%
\pgfmathrotatepointaround{%
\pgfmathpointintersectionoflineandarc%
{\pgfmathrotatepointaround{\externalpoint}{\centerpoint}{-\rotate}}%
{\pgfmathrotatepointaround{\referencepoint}{\centerpoint}{-\rotate}}%
{%
\beforetop%
\pgf@xa\pgf@x%
\centerpoint
}%
}{\centerpoint}{\rotate}%
\else%
\pgfpointintersectionoflines{%
{\centerpoint}{\rotate}}{%
{\centerpoint}{\rotate}}%
{\referencepoint}{\externalpoint}%
\fi%
\else%
\pgfmathrotatepointaround{%
\pgfmathpointintersectionoflineandarc%
{\pgfmathrotatepointaround{\externalpoint}{\centerpoint}{-\rotate}}%
{\pgfmathrotatepointaround{\referencepoint}{\centerpoint}{-\rotate}}%
{%
\afterbottom%
\pgf@xa\pgf@x%
\centerpoint
}%
}{\centerpoint}{\rotate}%
\fi%
\fi%
}
}
\makeatother

\begin{document}
\begin{frame}[t]
\frametitle{}
\begin{tikzpicture}[scale=1., transform shape]
`