5

I found a interesting snowman here, I rewrite it with some relative coordinate as below for fun:

\documentclass[border=2pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes}
\usetikzlibrary{intersections}
\usetikzlibrary{backgrounds}

\begin{document}
\begin{tikzpicture}[framed,line width=1pt]
    \def\a{2cm}
    \def\b{1cm}
    \def\c{0.5cm}
    \node[ellipse,draw,minimum width=\a,minimum height=\a/4] (P0) {};
    \node[ellipse,draw,minimum width=\b,minimum height=\b/2,above=-1pt of P0] (P1) {};
    \node[ellipse,draw,minimum width=\c,minimum height=\c,above=-1pt of P1] (P2) {};
    \draw (P2) ++(-30:.2) arc(-30:-150:.2);
    \draw[fill=black] (P2) +(.1,0) circle(.04) +(-.1,0) circle(.04);

    \path[name path=M1] (P1) ++(10:.4) --++(20:2);
    \path [name path=N1] (P0.east) |- (P2.east);
    \path [name intersections={of=M1 and N1,by=CS1}];
    \draw[very thick] (P1) ++(10:.4) -- (CS1);

    \path[name path=M2] (P1) ++(170:.4) --++(160:2);
    \path [name path=N2] (P0.west) |- (P2.west);
    \path [name intersections={of=M2 and N2,by=CS2}];
    \draw[very thick] (P1) ++(170:.4) -- (CS2);
  \end{tikzpicture}
  \end{document}

Everything seems good but as you see this output: enter image description here

Two defects here:

  1. The path command I intend to get the intersection point only, but it seems now use the draw area, so the picture is too wider with white!
  2. The intersection command is split into 3 lines now, can we combine it to one line to get the intersection point?tikz
  • 1
    You can tell TikZ to ignore certain things so that they are not used when calculating the bounding box. Or you can reset the bounding box manually to whatever you prefer. – cfr Mar 20 '16 at 4:02
  • Following up on @cfr's comment, check out the pgfinterruptboundingbox environment or the \pgfresetboundingbox macro. – Torbjørn T. Mar 20 '16 at 19:59
  • And you can't combine the three lines because you need two distinct named paths. Of course, you can define a macro or something so it is one line of code, but you need the macro to then execute the three lines of code so you name-path, name-other-path, get-intersections. – cfr Mar 20 '16 at 20:16
5

Here's one way to ignore certain commands for the purpose of calculating the bounding box. [Thanks to Torbjørn T. for telling me not to reinvent the wheel.]

\documentclass[tikz,multi,border=10pt]{standalone}
\usetikzlibrary{positioning,shapes.geometric,intersections,backgrounds}
\begin{document}
\begin{tikzpicture}[framed,line width=1pt]
  \def\a{2cm}
  \def\b{1cm}
  \def\c{0.5cm}
  \node[ellipse,draw,minimum width=\a,minimum height=\a/4] (P0) {};
  \node[ellipse,draw,minimum width=\b,minimum height=\b/2,above=-1pt of P0] (P1) {};
  \node[ellipse,draw,minimum width=\c,minimum height=\c,above=-1pt of P1] (P2) {};
  \draw (P2) ++(-30:.2) arc(-30:-150:.2);
  \draw[fill=black] (P2) +(.1,0) circle(.04) +(-.1,0) circle(.04);
  \begin{pgfinterruptboundingbox}
      \path [name path=M1] (P1) ++(10:.4*\snowpersonminimumtorsowidth) --++(20:2*\snowpersonminimumtorsowidth);
      \path [name path=N1] (P0.east) |- (P2.east);
      \path [name intersections={of=M1 and N1,by=CS1}];
      \path [name path=M2] (P1) ++(170:.4*\snowpersonminimumtorsowidth) --++(160:2*\snowpersonminimumtorsowidth);
      \path [name path=N2] (P0.west) |- (P2.west);
      \path [name intersections={of=M2 and N2,by=CS2}];
  \end{pgfinterruptboundingbox}
  \draw[very thick] (P1) ++(10:.4) -- (CS1);
  \draw[very thick] (P1) ++(170:.4) -- (CS2);
\end{tikzpicture}
\end{document}

get more sensible bounding box

You need 3 lines of code for the intersections because you want the intersection of two distinct paths, each of which must get defined and named somehow. These 3 lines don't necessarily need to be in the tikzpicture, but there is no escaping their being somewhere.

You could use a regular macro but the number of arguments required makes this rather confusing. You could also abuse a pic for this. Personally, I'd make the entire snowperson a pic and be done with it, but your kilometres may vary. Here's the intersection calculation alone as a pic. You need to feed it 5 bits of information in key-value syntax: name of first path, first path, name of second path, second path, prefix for naming intersections.

\documentclass[tikz,multi,border=10pt]{standalone}
\usetikzlibrary{positioning,shapes.geometric,intersections,backgrounds}
\tikzset{%
  pics/calc snowperson intersections/.style={%
    code={%
      \tikzset{%
        snowperson intersections/.cd,
        #1,
      }
      \begin{pgfinterruptboundingbox}
        \path [name path=\snowpersonintersectionsnameone] \snowpersonintersectionspathone;
        \path [name path=\snowpersonintersectionsnametwo] \snowpersonintersectionspathtwo;
        \path [name intersections={of={\snowpersonintersectionsnameone} and {\snowpersonintersectionsnametwo}, by=\snowpersonintersectionsname}];
      \end{pgfinterruptboundingbox}
    }
  },
  snowperson intersections/.search also={/tikz},
  snowperson intersections/.cd,
  name first path/.store in=\snowpersonintersectionsnameone,
  name second path/.store in=\snowpersonintersectionsnametwo,
  first path/.store in=\snowpersonintersectionspathone,
  second path/.store in=\snowpersonintersectionspathtwo,
  name intersections/.store in=\snowpersonintersectionsname,
  name first path=path 1.
  name second path=path 2,
  first path={(-1,0) -- (1,0},
  second path={(0,-1) -- (0,1)},
  name intersections=snowperson,
}
\begin{document}
\begin{tikzpicture}[framed,line width=1pt]
  \def\a{2cm}
  \def\b{1cm}
  \def\c{0.5cm}
  \node[ellipse,draw,minimum width=\a,minimum height=\a/4] (P0) {};
  \node[ellipse,draw,minimum width=\b,minimum height=\b/2,above=-1pt of P0] (P1) {};
  \node[ellipse,draw,minimum width=\c,minimum height=\c,above=-1pt of P1] (P2) {};
  \draw (P2) ++(-30:.2) arc(-30:-150:.2);
  \draw[fill=black] (P2) +(.1,0) circle(.04) +(-.1,0) circle(.04);
  \pic {calc snowperson intersections={name first path=M1, name second path=N1, name intersections=CS1, first path={(P1) ++(10:.4) --++(20:2)}, second path={(P0.east) |- (P2.east)}}};
  \pic {calc snowperson intersections={name first path=M2, name second path=N2, name intersections=CS2, first path={(P1) ++(170:.4) --++(160:2)}, second path={(P0.west) |- (P2.west)}}};
  \draw[very thick] (P1) ++(10:.4) -- (CS1);
  \draw[very thick] (P1) ++(170:.4) -- (CS2);
\end{tikzpicture}
\end{document}

gives the same output as before:

same output

But I'd be more inclined to go the whole hog:

\tikzset{
  /tikz/.cd,
  pics/snowperson/.style={%
    code={%
      \tikzset{%
        inner sep=0pt,
        snowperson/.cd,
        #1,
      }
      \begin{scope}[local bounding box/.expanded=\snowpersonname, line width=\snowpersonlinewidth, pic actions]
        \node [ellipse, draw, pic actions, minimum width=\snowpersonminimumbasewidth, minimum height=\snowpersonminimumbasewidth*\snowpersonbaseratio] (P0) {};
        \node [ellipse, draw, pic actions, minimum width=\snowpersonminimumtorsowidth, minimum height=\snowpersonminimumtorsowidth*\snowpersontorsoratio, above=-\snowpersonlinewidth of P0] (P1) {};
        \node [ellipse, draw, pic actions,minimum width=\snowpersonminimumheadwidth, minimum height=\snowpersonminimumheadwidth*\snowpersonheadratio, above=-\snowpersonlinewidth of P1] (P2) {};
        \draw (P2) ++(-30:.4*\snowpersonminimumheadwidth) arc(-30:-150:.4*\snowpersonminimumheadwidth);
        \draw [fill=black] (P2) +(.2*\snowpersonminimumheadwidth,0) circle(.08*\snowpersonminimumheadwidth) +(-.2*\snowpersonminimumheadwidth,0) circle(.08*\snowpersonminimumheadwidth);
        \begin{pgfinterruptboundingbox}
          \path [name path=M1] (P1) ++(10:.4*\snowpersonminimumtorsowidth) --++(20:2*\snowpersonminimumtorsowidth);
          \path [name path=N1] (P0.east) |- (P2.east);
          \path [name intersections={of=M1 and N1,by=CS1}];
          \path [name path=M2] (P1) ++(170:.4*\snowpersonminimumtorsowidth) --++(160:2*\snowpersonminimumtorsowidth);
          \path [name path=N2] (P0.west) |- (P2.west);
          \path [name intersections={of=M2 and N2,by=CS2}];
        \end{pgfinterruptboundingbox}
        \draw [line width=\snowpersonarmwidth] (P1) ++(10:.4*\snowpersonminimumtorsowidth) -- (CS1);
        \draw [line width=\snowpersonarmwidth] (P1) ++(170:.4*\snowpersonminimumtorsowidth) -- (CS2);
      \end{scope}
    }
  },
  snowperson/.search also={/tikz},
  snowperson/.cd,
  minimum base width/.store in=\snowpersonminimumbasewidth,
  minimum torso width/.store in=\snowpersonminimumtorsowidth,
  minimum head width/.store in=\snowpersonminimumheadwidth,
  base ratio/.store in=\snowpersonbaseratio,
  torso ratio/.store in=\snowpersontorsoratio,
  head ratio/.store in=\snowpersonheadratio,
  line width/.store in=\snowpersonlinewidth,
  arm thickness/.store in=\snowpersonarmwidth,
  minimum base width=20mm,
  minimum torso width=10mm,
  minimum head width=5mm,
  line width=.4pt,
  arm thickness=1pt,
  base ratio=.25,
  torso ratio=.5,
  head ratio=1,
  name/.store in=\snowpersonname,
  name=snow person,
}

Then simply

    \begin{tikzpicture}[framed,line width=1pt]
      \pic {snowperson};
    \end{tikzpicture}

produces

whole hog pic version

Note that the line width of the pic is now independent of the surrounding tikzpicture's line width. To produce the original, 1pt version, we need:

\begin{tikzpicture}[framed]
  \pic {snowperson={line width=1pt}};
\end{tikzpicture}

produces the original

original snowperson

and

\begin{tikzpicture}[framed,line width=1pt]
  \pic {snowperson={name=sam}};
  \pic [right=of sam.south, draw=blue!50!cyan, inner color=blue!50!cyan!25!white, outer color=blue!50!cyan!50!white] {snowperson={name=child, minimum base width=10mm, minimum torso width=5mm, minimum head width=4mm, base ratio=.4, torso ratio=.65}};
  \pic [right=of child.west, ball color=cyan!2!white] {snowperson={name=alex, line width=1pt, arm thickness=2pt}};
\end{tikzpicture}

gives us the beginnings of a snowpeople family:

snowpeople family

[An alternative would be to pick up the default line width active at the time the pic is used, but this version seemed more flexible.]

Complete code for snowperson pic single and triple:

\documentclass[tikz,multi,border=10pt]{standalone}
\usetikzlibrary{positioning,shapes.geometric,intersections,backgrounds}
\tikzset{
  /tikz/.cd,
  pics/snowperson/.style={%
    code={%
      \tikzset{%
        inner sep=0pt,
        snowperson/.cd,
        #1,
      }
      \begin{scope}[local bounding box/.expanded=\snowpersonname, line width=\snowpersonlinewidth, pic actions]
        \node [ellipse, draw, pic actions, minimum width=\snowpersonminimumbasewidth, minimum height=\snowpersonminimumbasewidth*\snowpersonbaseratio] (P0) {};
        \node [ellipse, draw, pic actions, minimum width=\snowpersonminimumtorsowidth, minimum height=\snowpersonminimumtorsowidth*\snowpersontorsoratio, above=-\snowpersonlinewidth of P0] (P1) {};
        \node [ellipse, draw, pic actions,minimum width=\snowpersonminimumheadwidth, minimum height=\snowpersonminimumheadwidth*\snowpersonheadratio, above=-\snowpersonlinewidth of P1] (P2) {};
        \draw (P2) ++(-30:.4*\snowpersonminimumheadwidth) arc(-30:-150:.4*\snowpersonminimumheadwidth);
        \draw [fill=black] (P2) +(.2*\snowpersonminimumheadwidth,0) circle(.08*\snowpersonminimumheadwidth) +(-.2*\snowpersonminimumheadwidth,0) circle(.08*\snowpersonminimumheadwidth);
        \begin{pgfinterruptboundingbox}
          \path [name path=M1] (P1) ++(10:.4*\snowpersonminimumtorsowidth) --++(20:2*\snowpersonminimumtorsowidth);
          \path [name path=N1] (P0.east) |- (P2.east);
          \path [name intersections={of=M1 and N1,by=CS1}];
          \path [name path=M2] (P1) ++(170:.4*\snowpersonminimumtorsowidth) --++(160:2*\snowpersonminimumtorsowidth);
          \path [name path=N2] (P0.west) |- (P2.west);
          \path [name intersections={of=M2 and N2,by=CS2}];
        \end{pgfinterruptboundingbox}
        \draw [line width=\snowpersonarmwidth] (P1) ++(10:.4*\snowpersonminimumtorsowidth) -- (CS1);
        \draw [line width=\snowpersonarmwidth] (P1) ++(170:.4*\snowpersonminimumtorsowidth) -- (CS2);
      \end{scope}
    }
  },
  snowperson/.search also={/tikz},
  snowperson/.cd,
  minimum base width/.store in=\snowpersonminimumbasewidth,
  minimum torso width/.store in=\snowpersonminimumtorsowidth,
  minimum head width/.store in=\snowpersonminimumheadwidth,
  base ratio/.store in=\snowpersonbaseratio,
  torso ratio/.store in=\snowpersontorsoratio,
  head ratio/.store in=\snowpersonheadratio,
  line width/.store in=\snowpersonlinewidth,
  arm thickness/.store in=\snowpersonarmwidth,
  minimum base width=20mm,
  minimum torso width=10mm,
  minimum head width=5mm,
  line width=.4pt,
  arm thickness=1pt,
  base ratio=.25,
  torso ratio=.5,
  head ratio=1,
  name/.store in=\snowpersonname,
  name=snow person,
}
\begin{document}
\begin{tikzpicture}[framed,line width=1pt]
  \pic {snowperson};
\end{tikzpicture}
\begin{tikzpicture}[framed]
  \pic {snowperson={line width=1pt}};
\end{tikzpicture}
\begin{tikzpicture}[framed,line width=1pt]
  \pic {snowperson={name=sam}};
  \pic [right=of sam.south, draw=blue!50!cyan, inner color=blue!50!cyan!25!white, outer color=blue!50!cyan!50!white] {snowperson={name=child, minimum base width=10mm, minimum torso width=5mm, minimum head width=4mm, base ratio=.4, torso ratio=.65}};
  \pic [right=of child.west, ball color=cyan!2!white] {snowperson={name=alex, line width=1pt, arm thickness=2pt}};
\end{tikzpicture}
\end{document}
  • Any particular reason for not using the already defined pgfinterruptboundingbox environment? (Just curios, not meant as any criticism.) – Torbjørn T. Mar 21 '16 at 7:34
  • @TorbjørnT. To be honest, I'm not sure. I took this from something I used in a pic I created to use in a handout. I don't know if I originally used these particular commands because those are what I found or if I used them for some particular reason. That is, it could just be that I didn't know about pgfinterruptboundingbox. I know I tried using something else, but that might have been resetting the bounding box. It could also be that I needed an alternative there but not here.... – cfr Mar 21 '16 at 13:33
  • @TorbjørnT. Great answer, can we remove hard code line width 1pt but use a variable? then if use changed the line width, no need to change code line such as "above=-1pt of P0". – lucky1928 Mar 21 '16 at 14:50
  • @lucky1928 I didn't write this answer ... – Torbjørn T. Mar 21 '16 at 15:07
  • @lucky1928 I added variables for line width and arm thickness. – cfr Mar 22 '16 at 1:11

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.