12

I draw images that are composed of several overlapping nodes. If I add a drop shadow to each node, the shadow of the second node is drawn over the first node, as seen in the picture below (which consist of two cylinder nodes drawn from bottom to top, each with a shadow):

example

(Thanks to zeroth for providing the image in reply to this related question. )

The documentation to TikZ states it clearly: You can apply a shadow only to a path, but not to a scope. The workaround I'm using is this:

  • Create a \foreach loop that loops over {drop shadow,}, thus assigning first drop shadow, then the empty string to a macro, say, \s
  • Use \s for the options of every node. (Perhaps I could append \s to every node and every path, didn't try that yet.)

Of course the image is drawn twice in the resulting PDF, which somewhat affects rendering speed and perhaps the size of the resulting PDF. In addition, the resulting code is rather ugly. My questions are:

  1. Is there a clean way of achieving the desired result using a TikZ/pgf feature? Perhaps it is possible to turn a sequence of TikZ/pgf commands into a single path?
  2. If not:
    • Is it possible to add some style parameters to nodes and paths in the first pass so that the details of the image are not rendered when drawing the shadow?
    • Would it be technically feasible to implement an environment shadowgroup that would hide the complexity and ugliness behind that?
7
  • 1
    Wasn't this exact question just asked earlier today? Unless this is a totally new question, you should delete this and edit the original question. If this is really a new question, you should add a link in the question to the earlier one as they are clearly related. Feb 6, 2012 at 19:54
  • No. The first question was about removing the darker parts in overlapping shadows. This is about drawing the shadow for the whole image at once instead of for each individual object.
    – krlmlr
    Feb 6, 2012 at 19:57
  • 1
    Ok. Please edit the question to cross reference them as this is then a follow up to the earlier one, and someone looking at this would be interested in looking at the earlier one as well. Feb 6, 2012 at 19:58
  • 1
    I have edited the question, the link is there. Should I add more information?
    – krlmlr
    Feb 6, 2012 at 20:00
  • Why not declaring a new shape? That would save you from a lot of trouble.
    – percusse
    Mar 3, 2012 at 12:06

3 Answers 3

11
+50

The follow-up question Pass options to the scope that is internally created by preaction was, to my amazement, solvable with my code from "Z-level" in TikZ. I'm going to have to resort to astonishment (and plagarism) now because it turns out that this works with drop shadow with no modification (my solutions tend to be the epitome of hackishness so the fact that one works for something it was not tested with is Definitely Unusual).

Here's the code. I've taken the liberty of removing the nested tikzpictures (see What are most important variables set at the beginning of a tikzpicture? of a scope? and links therein).

\documentclass{article}
%\url{https://tex.stackexchange.com/q/43618/86}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\usetikzlibrary{shadows}

\pgfdeclarelayer{back}
\pgfsetlayers{back,main}

\makeatletter
\pgfkeys{%
  /tikz/on layer/.code={
    \pgfonlayer{#1}\begingroup
    \aftergroup\endpgfonlayer
    \aftergroup\endgroup
  },
  /tikz/node on layer/.code={
    \pgfonlayer{#1}\begingroup
    \expandafter\def\expandafter\tikz@node@finish\expandafter{\expandafter\endgroup\expandafter\endpgfonlayer\tikz@node@finish}%
  },
}
\makeatother

\newcommand{\dbpart}[1]{
    \node[drop shadow={opacity=1.0,on layer=back},draw, cylinder, shape aspect=1.5, inner sep=0.3333em, fill=white,
    rotate=90, minimum width=1cm, minimum height=0.45cm] (cyl) at (0,#1) {};
}
\newcommand{\dbicon}[1][]{
  \begin{scope}[#1]
      \dbpart{0cm}%
      \dbpart{0.4cm}%
  \end{scope}
}

\begin{document}
\begin{tikzpicture}
      \dbicon
\end{tikzpicture}
\end{document}

Here's what happens (oh, and I put fill=white as I agree with Frédéric that it looks nicer).

Drop shadow on background layer

Let's have it without the fill just to see.

Drop shadow on background layer, main shape not filled

6
  • Marvelous! So, the on layer parameter is used for the preaction of the shadow, which in turn affects the scope around this preaction? Thanks a lot -- I'd never be able to put this together using my limited knowledge about PGF. How about adding on layer to the core implementation of TikZ?
    – krlmlr
    Mar 6, 2012 at 20:21
  • @user946850 I don't have that power! Mar 6, 2012 at 20:26
  • Well, but it sure would be worth suggesting. -- I have tested your solution with my code, works like a charm. A clear "accept", but I'd like to drop Frédéric a part of the bounty, too, for coming up with layers. Let's see if this works out.
    – krlmlr
    Mar 9, 2012 at 8:18
  • Glad to hear it works. For future reference, I think this is a case where my answer is the one to accept (since it's the one you ended up using) and Frédéric's gets the original bounty (since it's the one that actually involved work - mine was more of a "Oh, look! It works!", no actual effort involved). I don't think you can take back a bounty ... actually, I'll put a bounty on for Frédéric's answer. Mar 9, 2012 at 8:54
  • @user946850 Done. I have to wait 23hrs to actually award it. Mar 9, 2012 at 8:57
15
+100

This answer gets the job done, but it might not be as clean and efficient as you would like. It builds on the answer to your previous question and adds layers (background layer).

\documentclass[border=5pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{backgrounds}
\usetikzlibrary{shapes.geometric}
\usetikzlibrary{shadows}

\newcommand{\dbpart}[1]{
    \node[draw, cylinder, shape aspect=1.5, inner sep=0.3333em, 
    rotate=90, minimum width=1cm, minimum height=0.45cm] (cyl) at (0,#1) {};
    \begin{scope}[on background layer]
    \node[drop shadow={gray!50,opacity=1},cylinder, shape aspect=1.5, inner sep=0.3333em, 
    rotate=90, minimum width=1cm, minimum height=0.45cm] (cyl) at (0,#1) {};
    \end{scope}
}
\newcommand{\dbicon}{
    \begin{tikzpicture}
      \dbpart{0cm}%
      \dbpart{0.4cm}%
    \end{tikzpicture}
}

\begin{document}
\begin{tikzpicture}
  \node[inner sep=0pt] {
      \dbicon
  };
\end{tikzpicture}

\end{document}

The result is

Result with draw

On a personal note, if I were to use shadows, I would also fill the cylinders (fill=white). The result would be

Result with fill

7
  • 1
    Thank you. This solution is, compared to my workaround, more efficient in terms of rendering time and also more elegant. Now I wonder if it's possible to somehow draw the shadow on the background layer directly, without invoking the \node command twice. I remember from the docs that drop shadow uses some kind of "path recording and replay" mechanism, which could perhaps be changed to draw the shadow on the background layer?
    – krlmlr
    Mar 3, 2012 at 20:23
  • I didn't fill the cylinders on purpose, to show what's happening here more clearly. In the real picture, they're even shaded :-)
    – krlmlr
    Mar 3, 2012 at 20:27
  • 1
    The only way I know how to reuse drawing information is with a preaction or a postaction, but, TMK, these only work within a path, not inside node specifications. In addition the elements included within a layer (you can define your own with \pgfdefinelayer) must be enclosed within the layer (appropriate) layer environment --> you can't use them inside a node specification. It might be possible with deep pgf stuff.
    – Frédéric
    Mar 3, 2012 at 21:12
  • Let's see if anybody knows how to do this: tex.stackexchange.com/q/46957/8057
    – krlmlr
    Mar 6, 2012 at 10:24
  • I took the liberty to rescale the images.
    – krlmlr
    Mar 7, 2012 at 12:42
3

Here is a shape declaration as a starting point. I didn't quite address the shadow problem, and actually I don't understand why it appears as garbled but I hope finish it later. I would appreciate if somebody can have a look too.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shadows,shapes.geometric}

\makeatletter
\pgfdeclareshape{dbicon}
{
    \anchor{center}{\pgfpointorigin}
    \savedanchor{\west}{\pgf@y = 0mm \pgf@x = 1cm} %%
    \anchor{east}{\west \pgf@x=-\pgf@x}
    \anchor{west}{\west}
    \savedanchor{\north}{\pgf@y = 3mm \pgf@x = 0mm} %%
    \anchor{north}{\north}

    \backgroundpath{
        \west \pgf@xa=\pgf@x \pgf@xb=0.75\pgf@xa
        \pgfpathmoveto{\pgfpoint{\pgf@xa}{0}}
        \pgfpathlineto{\pgfpoint{\pgf@xa}{-\pgf@xb}}
        \pgfpatharc{0}{-180}{\pgf@xa and 0.3\pgf@xb}
        \pgfpathlineto{\pgfpoint{-\pgf@xa}{0}}
        \pgfpathellipse{\pgfpointorigin}{\pgfpoint{\pgf@xa}{0mm}}{\pgfpoint{0mm}{0.3\pgf@xb}}
        \pgftransformshift{\pgfpoint{0}{0.9\pgf@xa}} %Shift up and make a full cylinder
        \west \pgf@xa=\pgf@x \pgf@xb=0.75\pgf@xa
        \pgfpathmoveto{\pgfpoint{\pgf@xa}{0}}
        \pgfpathlineto{\pgfpoint{\pgf@xa}{-\pgf@xb}}
        \pgfpatharc{0}{-180}{\pgf@xa and 0.3\pgf@xb}
        \pgfpathlineto{\pgfpoint{-\pgf@xa}{0}}
        \pgfpathellipse{\pgfpointorigin}{\pgfpoint{\pgf@xa}{0mm}}{\pgfpoint{0mm}{0.3\pgf@xb}}
    }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\node[dbicon,shade,shading angle=90,draw,drop shadow] (a) at (0,0) {};
\end{tikzpicture}
\end{document}

enter image description here

2
  • Thank you for your effort. However, to me it seems that declaring shapes is not what the occasional TikZ user will want, especially not if the shape is used only once.
    – krlmlr
    Mar 9, 2012 at 8:25
  • @user946850 No problem :) I just wanted to see if I can do it but I still don't understand the problem. I will have a look soon.
    – percusse
    Mar 9, 2012 at 11:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .