9

I am trying to replicate the following figure here

enter image description here

and this is what I have so far

\documentclass[12pt,a4paper]{standalone}

\usepackage{tikz,tkz-euclide,pgfplots}
\usetkzobj{all}
\newcommand{\ball}[2]{%
        \begin{scope}[shift = {(#1,#2)}
            \fill[red] (0,0) circle (0.5);
            \clip (0,0) circle (0.5);
            \shade[shading=ball,ball color=white,1] (0.15,0.25) circle (0.7);
        \end{scope}}

\begin{document}%

    \begin{tikzpicture}     
        \def\height{0.75}
        \def\length{3}

        \tkzDefPoint(0,0){A}         \tkzDefPoint(0.9*\length,-\height){B}
        \tkzDefPoint(2*\length,0){C} \tkzDefPoint(1.1*\length, \height){D}
        \tkzDrawPolygon(A,B,C,D)

        \begin{scope}[shift={(0,2)}]
                \tkzDefPoint(0,0){A1}         \tkzDefPoint(0.9*\length,-\height){B1}
                \tkzDefPoint(2*\length,0){C1} \tkzDefPoint(1.1*\length, \height){D1}
                \tkzDrawPolygon(A1,B1,C1,D1)
        \end{scope}

        \tkzDrawSegments(A,A1 B1,B C1,C D1,D);  
%               \ball{2}{2}
    \end{tikzpicture}
\end{document}

By using the tips I got from this question. I think I almost got it, but the code will not compile when uncommenting the last line. What is the reason for this?

Other means create this image would also appreciated, it is always refreshing to see different approaches to a problem.

  • You forgot to close the scope options: \begin{scope}[shift = {(#1,#2)}]. You also have an undefined option for \shade: instead of \shade[shading=ball,ball color=white,1], write \shade[ball color=white]. I would also suggest \shade[ball color=white] (0.15,0.25) circle (1); – hpesoj626 Jun 23 '13 at 12:22
9

A possible solution:

\documentclass[png,border=10pt,tikz]{standalone}

\usepackage{tikz,tkz-euclide,pdftexcmds}
\usetikzlibrary{backgrounds}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}
\usetkzobj{all}

\makeatletter

\pgfdeclareradialshading[tikz@ball]{my ball}{\pgfqpoint{5bp}{10bp}}{%
 color(0bp)=(tikz@ball!10!white);
 color(8bp)=(tikz@ball!65!white);
 color(12bp)=(tikz@ball);
 color(25bp)=(tikz@ball!70!black);
 color(50bp)=(black)}

\pgfdeclareradialshading[tikz@ball]{new ball}{\pgfqpoint{12bp}{12bp}}{%
 color(0cm)=(tikz@ball!10!white);
 color(0.5cm)=(tikz@ball!50!white);
 color(0.75cm)=(tikz@ball);
 color(0.9cm)=(tikz@ball!80!black);
 color(1cm)=(tikz@ball!60!black)} 

% code by percusse:
% https://tex.stackexchange.com/a/74237/13304
\pgfdeclareradialshading{ball shadow}{\pgfpointorigin}%
{color(0cm)=(black);
color(2mm)=(gray!70);
color(3mm)=(gray!30);
color(7mm)=(white)
} 

 % to make possible use "myball color=..." 
\tikzoption{my ball color}{\pgfutil@colorlet{tikz@ball}{#1}\def\tikz@shading{my ball}\tikz@addmode{\tikz@mode@shadetrue}}

\tikzoption{new ball color}{\pgfutil@colorlet{tikz@ball}{#1}\def\tikz@shading{new ball}\tikz@addmode{\tikz@mode@shadetrue}}

\tikzoption{ball shadow color}{\pgfutil@colorlet{tikz@ball}{#1}\def\tikz@shading{ball shadow}\tikz@addmode{\tikz@mode@shadetrue}}


\newcommand{\ball}[3][white]{%
        \begin{scope}[shift = {(#2,#3)}]
          \node[shading=ball shadow,xscale=2,yscale=0.3,circle,minimum size=7mm] 
            at (0.15,-0.225){};
          % test to decide which shading use: special one for white balls
          % not 100% efficient: maybe it's better to use directly a custom
          % white ball shading, but this way one could always use 
          % "my ball color=green" say
          \ifnum\pdf@strcmp{#1}{white}=\z@%  
            \shade[my ball color=#1] (0.15,0.25) circle (0.5);
          \else
            \shade[new ball color=#1] (0.15,0.25) circle (0.5);
          \fi
        \end{scope}
}
\makeatother

\begin{document}%

\begin{tikzpicture}     
    \def\height{0.75}
    \def\length{3}

    \tkzDefPoint(0,0){A}         \tkzDefPoint(0.9*\length,-1.1\height){B}
    \tkzDefPoint(1.9*\length,0){C} \tkzDefPoint(1.1*\length, \height){D}
    %\tkzDrawPolygon(A,B,C,D) % Beware to put the right segments in foreground
    % now this can not be a polygon: need to be split
    % Same would apply to the shifted one if the balls touch the upper layer
    \tkzDrawSegments(C,D D,A);

    \begin{scope}[shift={(0,2)}]
            \tkzDefPoint(0,0){A1}\tkzDefPoint(0.9*\length,-\height){B1}
            \tkzDefPoint(1.9*\length,0){C1} \tkzDefPoint(1.1*\length, \height){D1}
            \tkzDrawPolygon(A1,B1,C1,D1)
    \end{scope}

    \tkzDrawSegments(A,A1 C1,C D1,D);
    \begin{pgfonlayer}{foreground}
      \tkzDrawSegments(B,B1);
      % other segments in foreground
      \tkzDrawSegments(A,B B,C);
    \end{pgfonlayer}

    % balls: beware the order matters
    \ball{1}{0.15}
    \ball[orange]{2.6}{0.35}
    \ball{1.85}{-0.2}
    \ball[orange]{4.3}{0.25}
    \ball[orange]{3.425}{-0.1}
\end{tikzpicture}
\end{document}

The result:

enter image description here

The tricks are:

  • define the shading in a similar way as How to shade mindmap concepts? so that the \ball command, besides the usual capability of being shifted, can also set the color;
  • draw specific segments, like the segment B,B1 in the foreground layer: in such a way balls seem to be in background;
  • draw the balls with a precise order: to not use too much the pgfonlayer enviroment, the simplest thing to do is to first draw the balls behind, proceeding then with the other ones.
  • Nice code! How can I number o name the balls? – Cragfelt Sep 11 '17 at 21:02
  • @Cragfelt: of course.. I guess the easiest way is to set up a counter and then increase it within the \ball command – Claudio Fiandrino Sep 12 '17 at 14:20

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.