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.

Goal

I would like to draw full nodes whose center is not clipped by some path.

How the solution would possibly work?

To be better understood I would describe it in two steps:

  • clip before drawing - "trim" each node which center is out of bounds,
  • draw full nodes (whose center is not clipped by path), i.e. without any clipping.

Is this possible?

At the bottom of the question there is two page document with normal clipping on the first page and what I want to achieve on the second page (but with manual clipping - easy in that case, but not in general, so shown code is obviously not what I am looking for).

Decomposition

It's worth to add that problem can be decomposed to:

Crucial here is being able to iterate over points and curves in path. I looked for it in manual, but without much luck (or, as usual, I was wrongly searching).

Example

\documentclass{minimal}
\usepackage[utf8]{inputenc}
\usepackage[paperwidth=6.25cm,paperheight=6.25cm,margin=0pt]{geometry}
\usepackage{ifthen}
\usepackage{tikz}
\begin{document}
\parindent=0pt
\pagestyle{empty}
\begin{tikzpicture}[remember picture,overlay]
\node at (current page.center) {%
\begin{tikzpicture}[overlay]
    \clip[draw] (0.35,0) circle (3.45);
    \foreach \y in {-3,-2,...,3}
        \foreach \x in {-3,-2,...,3} {
            \node[draw] at (\x,\y) {};
            \draw[fill] (\x,\y) circle (0.1pt);
        }
\end{tikzpicture}
};
\end{tikzpicture}
\newpage
\begin{tikzpicture}[remember picture,overlay]
\node at (current page.center) {%
\begin{tikzpicture}[overlay]
    \draw (0.35,0) circle (3.45);
    \foreach \y in {-3,-2,...,3}
        \foreach \x in {-3,-2,...,3} {
            \pgfmathsetmacro{\radiusc}{3.45^2 - (\x - 0.35)^2 - (\y - 0)^2}
            \ifthenelse{\lengthtest{\radiusc pt>0pt}\OR\lengthtest{\radiusc pt=0pt}}{
            \node[draw] at (\x,\y) {};
            \draw[fill] (\x,\y) circle (0.1pt);
            }{}
        }
\end{tikzpicture}
};
\end{tikzpicture}
\end{document}

normal clipping smart clipping

share|improve this question
    
I'd add clipping tag here, but I am unable to create new one (rep < 300). –  przemoc Jun 27 '11 at 15:17
    
I would write "is not clipped" not "is unclipped", but I'm not an English expert. The latter however, sounds quite wrong to me and is confusing. –  Martin Scharrer Jun 27 '11 at 15:31
2  
@Martin you're right. "unclipped" implies actually undoing some "clipping" while "not clipped" just implies nothing is done to "clip" –  romeovs Jun 27 '11 at 16:23
    
@Martin, @romeovs: Thanks for pointing that out. Fixed! –  przemoc Jun 27 '11 at 18:51
1  
@Martin: Yes, it's a shame that it works that way. Still, apart from notification system, it's just a good practice to address who are you talking/replying to. My reply did not require any special further actions from romeovs, so I believe that in this case lack of notification was not really harmful for him or this question. –  przemoc Jun 27 '11 at 19:12
show 2 more comments

1 Answer

up vote 3 down vote accepted

Here's a prototype. The difficulty is that (as I understand it) the clipping is not handled by TeX at all but is done by the final document format (thus it is actually done by the document viewer). So TeX doesn't know what does and doesn't get clipped, if the clipping is done by the usual means, and thus cannot use that information. This means that we have to simulate clipping. To do this, I've use the intersections library. The idea is that if we have a closed path then a point is inside it if a ray starting at that point and ending at "infinity" crosses the path an odd number of times. This is not 100% accurate: tangencies are problematic, as is "infinity". So we want "infinity" to be "far, far away" and we want some way of choosing the rays to avoid tangencies. I've set "far, far away" to be 10cm (though that's easy enough to modify) and for the rays, I've set it so that you (the user) choose some "centre point" and then all the rays would, if continued in the right direction, pass through that point. By varying that point it should be possible to find one that (for a finite number of nodes) avoids all tangencies.

The last problem is how to implement the decision whether or not a node should be drawn. This is difficult since if we're already processing the node then it's a bit tricky to turn round and say, "Actually, forget it." There are too many different groups and things set up for me to easily figure out how to cancel and close them. Instead, I put a command \ifinside which looks as though it takes two arguments. The first is the coordinate to consider, the second is the code to include if the coordinate is inside the clipping path (one could easily have an \ifoutside as well). As a bonus, the coordinate is left as the "current coordinate" so it doesn't have to be respecified inside the arguments. (This could also be modified, also I think a variant where the coordinate considered is the last one mentioned would be useful.)

Putting all that together, we get the following code:

\documentclass{article}
%\url{}
\usepackage{tikz}
\usetikzlibrary{intersections,calc}

\makeatletter
\newdimen\clipper@sx
\newdimen\clipper@sy
\newdimen\clipper@ex
\newdimen\clipper@ey
\newdimen\clipper@len

\def\ifinside#1{%
  \pgfextra{%
    \tikz@scan@one@point\pgfutil@firstofone#1
    \clipper@sx=\pgf@x\relax
    \clipper@sy=\pgf@y\relax
    \pgfmathsetlength{\clipper@ex}{(\clipper@sx -  \pgfkeysvalueof{/tikz/clipper/centre point x})}%
    \pgfmathsetlength{\clipper@ey}{(\clipper@sy -  \pgfkeysvalueof{/tikz/clipper/centre point y})}%
    \pgfmathsetlength{\clipper@len}{veclen(\clipper@ex,\clipper@ey)}%
    \pgfmathsetlength{\clipper@ex}{\clipper@sx + 10cm / \clipper@len *  \clipper@ex}%
    \pgfmathsetlength{\clipper@ey}{\clipper@sy + 10cm / \clipper@len *  \clipper@ey}%
    \edef\tikz@intersect@path@name@radius{\noexpand\pgfsyssoftpath@movetotoken{\the\clipper@sx}{\the\clipper@sy}\noexpand\pgfsyssoftpath@linetotoken{\the\clipper@ex}{\the\clipper@ey}}%
    \tikzset{name intersections={of=clipper@path and radius, total=\clipper@total}}%
    \ifodd\clipper@total
    \let\clipper@next=\pgfutil@firstofone
    \else
    \let\clipper@next=\pgfutil@gobble
    \fi
    \tikz@lastx=\clipper@sx
    \tikz@lasty=\clipper@sy
  }\clipper@next%
}

\tikzset{
  clipper/.style={
    name path=clipper@path,
    set clipper centre={#1},
  },
  clipper/centre point x/.initial={0.1},
  clipper/centre point y/.initial={0.1},
  set clipper centre/.code={%
    \def\clipper@temp{#1}%
    \ifx\clipper@temp\pgfutil@empty
    \else
    \tikz@scan@one@point\pgfutil@firstofone#1%
    \edef\tikz@marshal{\noexpand\tikzset{%
      clipper/centre point x=\the\pgf@x,
      clipper/centre point y=\the\pgf@y,
    }}%
    \tikz@marshal
    \fi
  }
}

\makeatother

\begin{document}
\begin{tikzpicture}
\draw[red,thick,clipper={(0.1,0.1)}] (0,0) circle[radius=3cm];
\foreach \nx in {-4,-3.5,...,4} {
  \foreach \ny in {-4,-3.5,...,4} {
    \fill[green] (\nx,\ny) circle[radius=1pt];
    \path \ifinside{(\nx,\ny)}{node[draw] {a}};
  }
}
\end{tikzpicture}
\end{document}

with result:

nodes clipped according to centre

I haven't tested it rigorously, though there's nothing wrong with the theory! Moreover, it is slow. But then it's going to have to be to do this with a reasonable degree of accuracy.

share|improve this answer
    
I won't lie saying that I thoroughly analyzed your solution and understand every bit of your work. Nevertheless, knowing difficulty of the problem, I really appreciate your effort, and moreover, the result is gorgeous. Code in use part is also really concise, which I consider a great achievement. –  przemoc Aug 31 '11 at 7:53
    
Have to add that clipper={(0.1,0.1)} initially bugged me, but it's only to show mentioned earlier inaccuracy, right? Ultimately some kind of naming clippers without need to provide any coordinate would be the most useful approach, but it's just technicality. –  przemoc Aug 31 '11 at 8:00
    
@przemoc: When you specify clipper={(0.1,0.1)} then you are both installing the code that will do the clipping and saying what the "centre" of the region should be (for determining the rays). The centre is optional as there's a default value so you could just put clipper. The centre point can be almost anything so long as the rays cross the path and don't have tangency points, but that's why it's good to be able to vary it. At the moment you can only "clip" against a single path, but it wouldn't be hard to make it cumulative. –  Andrew Stacey Aug 31 '11 at 9:27
    
Oh, I see. Thank you for the explanation. –  przemoc Sep 3 '11 at 14:15
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.