13

I am trying to define a collection of shapes that are useful in drawing Riemann surfaces. For this, I am heavily indebted to Andrew Stacey's tqft package.

I am having some trouble defining a shape to represent the standard short-hand we use to indicate genus on a surface. I want the shape to draw the arcs, but when I fill it, only fill the "hole". I have a hack solution at present (code snippet below), but it works by filling the hole in white and ignoring any fill.

Abstracted away from my specific situation:

How can I define the stroke and fill parts of my shape separately?

EDIT: I am including more detail in my example.

First, a toy example: I have a shape called "sun" that consists of a disk with rays and smaller circles outside. When I fill it, I want it to give me a green main disk but unfilled outer circles. (i.e. a hybrid between the two pictures below: the outer circles should match the left, and the inner disk should match the right).

unfilled and filled sun shape

Here is code for the sun:

\documentclass[convert]{standalone}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes,calc}

\makeatletter

\pgfdeclareshape{sun}{
 \savedanchor{\centre}{ \pgfpointorigin}
 \anchor{center}{\centre}
 \savedanchor{\top}{
    \pgf@x = 0cm
    \pgf@y = 0.15cm
  }
  \anchor{top}{\top}
  \savedanchor{\bottom}{
    \pgf@x = 0cm
    \pgf@y = -0.375cm
    }
  \anchor{bottom}{\bottom}

\backgroundpath{


    \foreach \x in {0,45,...,360} {
    \pgfpathmoveto{\pgfpointpolarxy{ \x }{ 1.2 }}
    \pgfpathlineto{ \pgfpointpolarxy{\x }{2 } }
    \pgfpathcircle{\pgfpointpolarxy{\x}{2.2}}{2pt}
        }   
    \pgfpathcircle{\pgfpointorigin}{1cm}
}
    }

    \makeatother

\begin{document}
\begin{tikzpicture}
\node [sun, draw, scale=.5, fill=green] at (2,0) {};
\node [sun, draw, scale=.5, fill=none] at (-2,0) {};

  \end{tikzpicture}
\end{document}

Now, the real example: the picture with genus. I have four shapes: genuspic, hackgenuspic, strokegenuspic and fillhackgenuspic. From top to bottom in the picture: genuspic with fill=green, hackgenuspic with fill=green, fillhackgenuspic with fill=green, fillhackgenuspic with fill=none, strokegenuspic. Genuspic draws my arcs as I want them. Hackgenuspic draws my arcs as I want them, but also draws a path that bounds the hole. It then fills that path in (in white, ignoring fill=green).

How can I have a shape that does what hackgenuspic does, but where the fill colour pays attention to fill=green?

pictures of genus

Here is the code for the Riemann surface example:

\documentclass[convert]{standalone}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes}

\makeatletter

\pgfdeclareshape{genuspic}{
 \anchor{center}{\pgfpointorigin}
\backgroundpath{
     \pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.5cm}{-.5cm}}
        {\pgfpoint{0.5cm}{-.5cm}}
        {\pgfpoint{1cm}{0cm}}

     \pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.75cm}{-0.15cm}}
    }
    }

\pgfdeclareshape{strokegenuspic}{
 \anchor{center}{\pgfpointorigin}
\backgroundpath{
     \pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.5cm}{-.5cm}}
        {\pgfpoint{0.5cm}{-.5cm}}
        {\pgfpoint{1cm}{0cm}}

     \pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.75cm}{-0.15cm}}
        \pgfusepath{stroke}
    }
    }


\pgfdeclareshape{hackgenuspic}{
 \anchor{center}{\pgfpointorigin}
\backgroundpath{
         \pgfpathmoveto{\pgfqpoint{-0.78cm}{-.17cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.35cm}{-.44cm}}
        {\pgfpoint{0.35cm}{-.44cm}}
        {\pgfpoint{.78cm}{-0.17cm}} 
     \pgfpathmoveto{\pgfqpoint{-0.78cm}{-0.17cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.78cm}{-0.17cm}}
        \pgfsetfillcolor{white}
        \pgfusepath{fill}

     \pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.5cm}{-.5cm}}
        {\pgfpoint{0.5cm}{-.5cm}}
        {\pgfpoint{1cm}{0cm}}

     \pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.75cm}{-0.15cm}}
              \pgfusepath{stroke}
    }
    }

    \pgfdeclareshape{fillhackgenuspic}{
 \anchor{center}{\pgfpointorigin}
\backgroundpath{
         \pgfpathmoveto{\pgfqpoint{-0.78cm}{-.17cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.35cm}{-.44cm}}
        {\pgfpoint{0.35cm}{-.44cm}}
        {\pgfpoint{.78cm}{-0.17cm}} 
     \pgfpathmoveto{\pgfqpoint{-0.78cm}{-0.17cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.78cm}{-0.17cm}}
        \pgfusepath{fill}

     \pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.5cm}{-.5cm}}
        {\pgfpoint{0.5cm}{-.5cm}}
        {\pgfpoint{1cm}{0cm}}

     \pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.75cm}{-0.15cm}}
              \pgfusepath{stroke}
    }
    }



    \makeatother

\begin{document}
\begin{tikzpicture}[scale=.5]
\fill[color=black!10] (-6,3) arc (-180:0:2 cm and 1 cm) 
 (-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(2,3)  arc (-180:0:2 cm and 1 cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);

\node [genuspic, draw, scale=1, fill=green] at (0,0) {};
\node [hackgenuspic, draw, scale=1, fill=green] at (0,-2) {};
\node [fillhackgenuspic, draw, scale=1, fill=green] at (0, -4) {};
\node [fillhackgenuspic, draw, scale=1, fill=none] at (0, -6) {};
\node [strokegenuspic, draw, scale=1, fill=green] at (0, -8) {};


\draw (-4,3) ellipse (2 cm and 1 cm) 
 (-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(4,3)  ellipse (2cm and 1cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);
\end{tikzpicture}



\end{document}
9
  • Well the question can be answered with [draw=black,fill=green] but it might not be as simple as that I guess. I think I don't understand the question. Adding draw=none,fill=green options to the first ellipse fills it.
    – percusse
    Feb 23, 2012 at 20:41
  • The point is that my shape has a drawing that does not bound the area I want to have filled. Instead, I want to specify a different region to fill from the region that is drawn.
    – Sam Lisi
    Feb 23, 2012 at 21:08
  • @percusse, Here is an even simpler example of what I want: suppose I want to create a shape of a sun. It would consist of a disk with rays sticking out of it. I want [draw=black, fill=green] to fill the disk in green, but leave the outline and rays in black.
    – Sam Lisi
    Feb 23, 2012 at 21:47
  • I get that but when I comment the white filling lines I get the desired fill in green. Do you want to swap the colors? A simple picture would clear out any ambiguity.
    – percusse
    Feb 23, 2012 at 22:02
  • 1
    A fill will only work on a closed path. So, after drawing your paths, you need to use [draw=none, fill=yellow] option on a closed path where you want the fill to be applied, and the fill be applied to the enclosed region. Feb 24, 2012 at 0:03

3 Answers 3

10

TikZ doesn't allow you to apply the fill and draw commands to separate pieces of a path. The relevant part is in the macro \tikz@finish. On a normal path, the drawing and filling is done by the piece:

  \edef\tikz@temp{\noexpand\pgfusepath{%
    \iftikz@mode@fill fill,\fi%
    \iftikz@mode@draw draw,\fi%
    \iftikz@mode@clip clip,\fi%
    }}%
  \tikz@temp%

In a node shape, the \backgroundpath is the path used at this point. Now you don't want this: you want to be able to specify part of the \backgroundpath as for draw and part for fill. So you have to specify two paths. Neither can actually be in place at the point where TikZ normally draws or fills since neither should have both actions applied. Normally, I'd recommend using the fact that a node shape can have several paths associated to it (\beforebackgroundpath and so forth), but then you get into complications with passing styles to the different parts (in the TQFT package I use this to style the various parts of a cobordism, but there it is reasonable as there are different components. Here you just have one thing and splitting it is less user-friendly.).

So we just need to subvert the normal TikZ mechanism and replace it with our own. This means using the \backgroundpath at definition time rather than leaving it to later. You do this already in the hackgenuspic (and other) shapes. The only bit you don't do is use it conditionally.

At the time that the \backgroundpath is processed, all of TikZ's path options have been processed. So TikZ knows the fill colour, the line width, the draw colour, and so forth. The only bit it doesn't know is whether or not to fill or draw the path. This is only figured out at the finish. (This is reasonable: the definition of the path can depend on the options, but doesn't usually depend on the action to be taken.) Fortunately, it isn't hard to find out what the desired actions are: we simply execute \tikz@mode. This sets a few conditionals, namely \iftikz@mode@fill and \iftikz@mode@draw (there's also \iftikz@mode@clip and a few more - for a truly robust solution we should consider them as well).

And from that, the rest is fairly easy. We execute \tikz@mode and if we need to fill the path, we define and fill the hole, and if we need to draw it, we define and draw the outer part. Given that we're using \tikz@mode a little earlier than usual, I've put it inside a group. I suspect I'm just being overcautious on this, though, as TikZ is fairly careful about not making assumptions.

Here's my code. The shape is based on hackgenuspic (but I renamed it to just genus). It's quite likely that I've missed something - I haven't stress-tested it! Please let me know if it breaks. (If not, it would seem a reasonable addition to the TQFT package - what do you think?)

\documentclass[convert]{standalone}
%\documentclass{article}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes}

\makeatletter

\pgfdeclareshape{genus}{
 \anchor{center}{\pgfpointorigin}
\backgroundpath{
    \begingroup
    \tikz@mode
    \iftikz@mode@fill

         \pgfpathmoveto{\pgfqpoint{-0.78cm}{-.17cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.35cm}{-.44cm}}
        {\pgfpoint{0.35cm}{-.44cm}}
        {\pgfpoint{.78cm}{-0.17cm}} 
     \pgfpathmoveto{\pgfqpoint{-0.78cm}{-0.17cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.78cm}{-0.17cm}}
        \pgfusepath{fill}
        \fi

        \iftikz@mode@draw
     \pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.5cm}{-.5cm}}
        {\pgfpoint{0.5cm}{-.5cm}}
        {\pgfpoint{1cm}{0cm}}

     \pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
     \pgfpathcurveto %
            {\pgfpoint{-0.25cm}{.25cm}}
        {\pgfpoint{.25cm}{.25cm}}
        {\pgfpoint{0.75cm}{-0.15cm}}
              \pgfusepath{stroke}
        \fi
        \endgroup
    }
    }

    \makeatother

\begin{document}
\begin{tikzpicture}[scale=.5]
\fill[color=black!10] (-6,3) arc (-180:0:2 cm and 1 cm) 
 (-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(2,3)  arc (-180:0:2 cm and 1 cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);

\node [genus, scale=1, fill=green] at (0,0) {};
\node [genus, draw, scale=1, fill=orange] at (0,-2) {};
\node [genus, draw, ultra thick, scale=1, fill=green] at (0, -4) {};
\node [genus, draw, scale=.7] at (0, -6) {};
\node [genus, draw, dashed, scale=.5, fill=green] at (0, -8) {};


\draw (-4,3) ellipse (2 cm and 1 cm) 
 (-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(4,3)  ellipse (2cm and 1cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);
\end{tikzpicture}
\end{document}

with result:

Riemann surface with genus

4
  • Wow, this is great! That answers my question exactly. I think it would be great if you genus to the tqft package. I made the picture of the handle by trial and error to get something that looked like what I would draw by hand... there is surely a better way of obtaining a picture than that.
    – Sam Lisi
    Feb 26, 2012 at 13:32
  • @SamLisi: It's something I keep coming back to: trying to work out the equation for those curves. But I'm not sure that the mathematically right equation is visually right. So "looks right" might be the best guide here. Feb 26, 2012 at 19:01
  • @AndrewStacey, is there anything that prevents \tikz@finish from using the background path to draw it or to fill it again, when \tikz@mode is executed and the path is drawn of filled explicitly in \backgroundpath, like in your answer? Or it may happen twice: in the definition of \backgroundpath and \tikz@finish?
    – Romildo
    Nov 3, 2013 at 14:59
  • @Romildo I'd have to check, but I think that once the path is used then it is discarded so to carry out two actions on the same path at different times then you have to save the path in between. But it would need a bit of experimenting to be sure of this. Nov 4, 2013 at 8:09
8

As mentioned in the comments, a fill is only applied to a region that within a closed by a path. Hence, if you want to appply a fill to a region that is not a closed path, you need to provide an additional path that defines the enclosed region.

Here is a simple example where the path defined by \MyPath is not closed, and to fill it you define a new path that is closed.

enter image description here

\documentclass[border=2pt]{standalone}
\usepackage{tikz}

\newcommand{\MyPath}{(0,0) -- (1,0) -- (2,2)}%
\begin{document}
\begin{tikzpicture}
    \draw [draw=blue, ultra thick] \MyPath;
\end{tikzpicture}
%
\begin{tikzpicture}
    \fill [draw=none, fill=yellow] \MyPath -- cycle;
    \draw [draw=blue, ultra thick] \MyPath;
\end{tikzpicture}
\end{document}
1
  • 1
    Almost. If you specify a fill option on an unclosed path then TikZ will close it first and then fill it. Feb 24, 2012 at 7:43
7

Only for the fun under the sun, a code to fill inside or outside. The useful code here is

 \ifsunfill \pgfusepath{fill,stroke} \else \pgfusepath{stroke} \fi

filled in and fillout suns

\documentclass{article}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes,calc}
\newif\ifsunfill \sunfillfalse 
\makeatletter

\pgfdeclareshape{sun}{%
 \savedanchor{\centre}{ \pgfpointorigin}
 \anchor{center}{\centre}
 \savedanchor{\top}{
    \pgf@x = 0cm
    \pgf@y = 0.15cm
  }
  \anchor{top}{\top}
  \savedanchor{\bottom}{
    \pgf@x = 0cm
    \pgf@y = -0.375cm
    }
  \anchor{bottom}{\bottom}

\backgroundpath{
    \foreach \x in {0,45,...,360} {%
    \pgfpathmoveto{\pgfpointpolarxy{ \x }{ 1.2 }}
    \pgfpathlineto{ \pgfpointpolarxy{\x }{2 } }
    \pgfpathcircle{\pgfpointpolarxy{\x}{2.2}}{2pt}
        } 
    \ifsunfill \pgfusepath{fill,stroke} \else \pgfusepath{stroke} \fi
    \pgfpathcircle{\pgfpointorigin}{1cm} 
    \ifsunfill \pgfusepath{stroke} \else \pgfusepath{fill,stroke} \fi  
}}
\makeatother

\begin{document} 
\tikzset{fill out/.is if=sunfill}
\begin{tikzpicture}
  \node [sun, draw, scale=.5, fill=green] at (2,0) {};
  \node [sun, draw, scale=.5, fill=green,fill out] at (-2,0) {};
\end{tikzpicture}
\end{document}

(Added in edit by Andrew Stacey: using the .is if handler means that one can write fill out=true and fill out=false to switch back and forth making it easier to use in styles. Now you could write every node/.style={fill out} to set a global default and fill out=false on a specific instance to override it. Putting fill out with no argument defaults to fill out=true.)

5
  • Very nice example. One ever-so-minor modification: pgfkeys has the .is if handler and this would be a nice demonstration of its use. Then, also, one could write fill out=false in case you'd done an every node/.style={fill out} earlier. Feb 24, 2012 at 14:23
  • @AndrewStacey thanks! you can edit and make the modifications if you want because I don't have time today. Feb 24, 2012 at 14:35
  • Done (and some more random letters; my favourite letters today are q, r, å, and g.) Feb 24, 2012 at 14:48
  • Yet another reason with my love towards Tikz.
    – azetina
    Feb 24, 2012 at 16:32
  • Wow. Thank you. I've learned a lot from this example.
    – Sam Lisi
    Feb 26, 2012 at 13:24

You must log in to answer this question.

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