6

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

7
  • I'd add clipping tag here, but I am unable to create new one (rep < 300).
    – przemoc
    Jun 27, 2011 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. Jun 27, 2011 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, 2011 at 16:23
  • @Martin, @romeovs: Thanks for pointing that out. Fixed!
    – przemoc
    Jun 27, 2011 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, 2011 at 19:12

1 Answer 1

3

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.

3
  • 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, 2011 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, 2011 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. Aug 31, 2011 at 9:27

You must log in to answer this question.

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