Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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}
share|improve this question
    
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 '12 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 '12 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 '12 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 '12 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. –  Peter Grill Feb 24 '12 at 0:03
show 4 more comments

3 Answers

up vote 6 down vote accepted

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

share|improve this answer
    
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 '12 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. –  Andrew Stacey Feb 26 '12 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 '13 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. –  Andrew Stacey Nov 4 '13 at 8:09
add comment

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}
share|improve this answer
1  
Almost. If you specify a fill option on an unclosed path then TikZ will close it first and then fill it. –  Andrew Stacey Feb 24 '12 at 7:43
add comment

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.)

share|improve this answer
    
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. –  Andrew Stacey Feb 24 '12 at 14:23
    
@AndrewStacey thanks! you can edit and make the modifications if you want because I don't have time today. –  Alain Matthes Feb 24 '12 at 14:35
    
Done (and some more random letters; my favourite letters today are q, r, å, and g.) –  Andrew Stacey Feb 24 '12 at 14:48
    
Yet another reason with my love towards Tikz. –  azetina Feb 24 '12 at 16:32
    
Wow. Thank you. I've learned a lot from this example. –  Sam Lisi Feb 26 '12 at 13:24
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.