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.

It seems that, when drawing an edge between two nodes, TikZ defaults to aligning the edge with the 'center' anchors of the two nodes. More precisely, the edge points from the center of one node to the center of the other node, and the endpoints of the edge sit on the boundary of the nodes. An example is the black arrow in the following diagram.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{matrix}

\begin{document}

\begin{tikzpicture}
  \matrix (m) [matrix of math nodes,row sep={4em,between origins},column sep={5em,between origins},nodes={anchor=mid}]{
    A^B & A_B \\
        & E   \\};
  \draw[->] (m-1-1) -- (m-1-2);
  \draw[->,red] (m-1-1.mid) -- (m-2-2.mid);
\end{tikzpicture}

\end{document}

result picture

On the other hand, the red arrow joins the 'mid' anchors of two nodes. I would like to shorten the red arrow to make it start and end at the boundaries of the nodes (like the black arrow does). Nevertheless, I want the desired shortened arrow to stay on the same line as the above red arrow, and to still have an arrowhead (so simply clipping does not seem to do the trick).

Is there a simple way to shorten the red arrow as requested? Does anyone know how TikZ does it for the black arrow?

share|improve this question
    
It would be interesting to give a minimal example! –  Alain Matthes Apr 8 '11 at 16:53
    
Altermundus' intersection example works with arrows. Also you could use the shorten >=... syntax. –  Caramdir Apr 9 '11 at 3:07
    
For commutative diagrams, you should either add something like [text height=1.5ex, text depth=0.25ex] or [nodes={anchor=center}] to the matrix, so that the arrows are not slanted, see the two example in tex.stackexchange.com/questions/3892 –  Caramdir Apr 9 '11 at 3:12
    
@Caramdir: Unfortunately, anchoring the nodes at the center causes the text of different nodes to not be vertically aligned. Also, using the options 'text height' and 'text depth' causes the arrows to behave poorly with respect to the actual border of the text in the nodes. –  Ricardo Andrade Apr 9 '11 at 3:16
1  
To answer your question about how TikZ calculates the black arrow: There is a special test in the code of line to (and similar commands). For details see texmf/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex. For line to, the code is in \tikz@@lineto, which (in v2.10) starts at line 2187. –  Caramdir Apr 9 '11 at 21:12
show 2 more comments

3 Answers

up vote 4 down vote accepted

I'm not sure to understand exactly the question . I understand that : you want an edge between two nodes but you want to draw the edge only between the intersections of the edges and the shapes of the nodes.

With intersections (the problem is to make a choice between the points) but in the second method I just fill the node's background.

\documentclass{scrartcl}
\usepackage{tikz}
\usetikzlibrary{positioning,intersections}
\begin{document}

\begin{tikzpicture}
  \node[name path= c1] (foo)  at (0,0)[circle] {$x$};
  \node[name path= c2] (bar) at (2,2)[circle] {$x+1$};
  \path[name path=line] (foo.south) -- (bar.center);
  \fill [name intersections={of=c1 and line, name=i, total=\t}]
 [red, opacity=0.5, every node/.style={left=.25cm, black, opacity=1}] 
 \foreach \s in {1,...,\t}{(i-\s) circle (2pt) node {}};
  \fill [name intersections={of=c2 and line, name=j, total=\t}]
 [red, opacity=0.5, every node/.style={left=.25cm, black, opacity=1}] 
 \foreach \s in {1,...,\t}{(j-\s) circle (2pt) node {}};   
  \draw[red] (i-1) -- (j-1);  
\end{tikzpicture} 
\begin{tikzpicture}
  \node[name path= c1] (foo)  at (0,0)[circle] {$x$};
  \node[name path= c2] (bar) at (2,2)[circle] {$x+1$};
  \path[name path=line] (foo.south) -- (bar.center);
  \path [name intersections={of=c1 and line, name=i, total=\t}] ;
  \path [name intersections={of=c2 and line, name=j, total=\t}];  
  \draw[red] (i-1) -- (j-1);  
\end{tikzpicture}
\begin{tikzpicture}
  \node (foo)  at (0,0)[circle] {\hphantom{$x$}};
  \node (bar) at (2,2)[circle] {\hphantom{$x+1$}};
  \draw[red] (foo.south) -- (bar.center);
  \node[circle,fill=white] at (foo.center){$x$} ;
  \node[circle,fill=white] at (bar.center){$x+1$} ; 
\end{tikzpicture}  
\end{document}

enter image description here

share|improve this answer
    
Although this won't work if the background is complicated. Could you make the circles transparent and then draw the line under them? –  Seamus Apr 8 '11 at 15:12
    
@Seamus Yes but it's possible to use layers if the background is complicated but the problem is the same with the clip. You may need another clip zone ! example you can't add ` \path[clip] (1,1) circle(.2cm); ` –  Alain Matthes Apr 8 '11 at 15:30
1  
@Seamus Intersection is easy if the edge starts and ends at the origins of the shapes. In this case we can use \pgfpointborderellipse or \pgfpointborderrectangle (Points on Borders of Objects in the pgfmanual) –  Alain Matthes Apr 8 '11 at 15:38
    
In order to solve my modified question above, how would one change this answer to instead use the border (not drawn) of a math node (as in the question)? –  Ricardo Andrade Apr 9 '11 at 5:52
    
@Ricardo You need only to remove the draw option in the style of nodes. I modified my version .You can also remove the red circle, I will add an example. –  Alain Matthes Apr 9 '11 at 7:08
add comment

Is this the sort of thing you want? I've replaced the code with an example closer to the sort of thing mentioned in the question.

\documentclass[border=1cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning}
\begin{document}
\begin{tikzpicture}
  \node (a) {A};
  \node (b)  [below=of a] {B};
  \node (c)  [right=of a] {C};
  \node (d)  [right=of b] {D};
  \begin{scope}
    \clip (a.north east) -- (a.east) -- (a.south east) -- (c.south west) -- (c.west) -- (c.north west) -- (a.north east);
    \draw (a.mid) -- (c.mid);
    \draw[red] (a)--(c);
  \end{scope}
  \begin{scope}
    \clip (a.south) -- (a.south east) -- (a.east) --(d.north) -- (d.north west) -- (d.west) -- (a.south);
    \draw (a.mid) -- (d.mid);
    \draw[red] (a)--(d);
  \end{scope}
\end{tikzpicture}
\end{document}

Better image

The trick is to clip to a box around the relevant anchors, but keep the clip within a scope so the whole picture doesn't get clipped...

share|improve this answer
    
I think that the line to the lower node ought to stop where it first hits the boundary. (Incidentally, I see horrible black lines on the right and lower edge of your picture but don't see them in the code.) –  Andrew Stacey Apr 8 '11 at 12:17
    
No. That unites two anchors, allowing it to intercept the boundaries of the nodes somewhere in the middle. I do not want that. Also, the case I have in mind would have the anchors placed within the nodes (I specifically mentioned the 'mid' anchor in my question). –  Ricardo Andrade Apr 8 '11 at 12:25
    
@Andrew yes, I used the standalone environment, and then wasn't very good at selecting an area for a screenshot, so I caught some of the border... –  Seamus Apr 8 '11 at 12:32
1  
For the first one you could also use the mid east and mid west anchors instead of clipping. –  Caramdir Apr 8 '11 at 16:27
1  
See the “Shape Library” section of the TikZ manual (chapter 48 in v2.10). Very useful are base and base east/west. –  Caramdir Apr 8 '11 at 16:40
show 5 more comments

Thinking about this mathematically, it is going to be very hard to do properly. I mean, so that \draw[->] (a) -- (b); draws an arrow from a towards the desired point on b and stops at the boundary with the arrowhead. To do that, one is going to have to calculate the point on the boundary where the line from a to the anchor of b reaches the boundary of b and that will depend on many parameters.

Here's a slightly different approach, which is presented more as a "proof of concept" than as usable code. The idea is that for a simple shape such as a rectangle, one can define some auxiliary nodes whose boundary coincides with the boundary of the original node when seen from certain directions but which are centred at the correct place. The trick is then to pick the right one of these auxiliary nodes upon approach (this is the main bit that I haven't thought how to do).

\documentclass{standalone}
\usepackage{tikz}

\makeatletter
\newlength\relocate@minx
\newlength\relocate@maxx
\newlength\relocate@miny
\newlength\relocate@maxy
\def\relocatenode#1#2{%
  \path (#1.south east);
  \pgfgetlastxy{\relocate@east}{\relocate@south}
  \path (#1.north west);
  \pgfgetlastxy{\relocate@west}{\relocate@north}
  \path (#1) ++(#2);
  \pgfgetlastxy{\relocate@x}{\relocate@y}
  \pgfmathsetlength{\relocate@minx}{2 * min(\relocate@east - \relocate@x, \relocate@x - \relocate@west)}
  \pgfmathsetlength{\relocate@maxx}{2 * max(\relocate@east - \relocate@x, \relocate@x - \relocate@west)}
  \pgfmathsetlength{\relocate@miny}{2 * min(\relocate@north - \relocate@y, \relocate@y - \relocate@south)}
  \pgfmathsetlength{\relocate@maxy}{2 * max(\relocate@north - \relocate@y, \relocate@y - \relocate@south)}
  \path (#1) ++(#2) node[minimum width=\relocate@minx,minimum height=\relocate@miny] (#1-minx-miny) {};
  \path (#1) ++(#2) node[minimum width=\relocate@maxx,minimum height=\relocate@miny] (#1-maxx-miny) {};
  \path (#1) ++(#2) node[minimum width=\relocate@minx,minimum height=\relocate@maxy] (#1-minx-maxy) {};
  \path (#1) ++(#2) node[minimum width=\relocate@maxx,minimum height=\relocate@maxy] (#1-maxx-maxy) {};
}
\makeatother
\begin{document}
\begin{tikzpicture}[every path/.style={->}]
\node[draw,align=left] (a) at (0,0) {hello world\\greetings mars};
\relocatenode{a}{1,.3}
\fill (a) circle (2pt);
\fill (a) ++(1,.3) circle (2pt);

\path (a) ++(1,.3) node (b) {};

\foreach \pt in {30,60,...,360} {
  \draw (a) ++(\pt:5) -- (a);
}

\foreach \pt in {0,10,...,80} {
  \draw[red] (a) ++(\pt:5) -- (a-minx-miny);
}
\foreach \pt in {90,100,...,170} {
  \draw[red] (a) ++(\pt:5) -- (a-maxx-miny);
}
\foreach \pt in {180,190,...,260} {
  \draw[red] (a) ++(\pt:5) -- (a-maxx-maxy);
}
\foreach \pt in {270,280,...,360} {
  \draw[red] (a) ++(\pt:5) -- (a-minx-maxy);
}
\end{tikzpicture}
\end{document}

Result:

relocated node

share|improve this answer
    
That was a very good interpretation of my ultimate goals, even though I did not state them. Indeed, I do want to draw arrows and not just segments, in the way you state. –  Ricardo Andrade Apr 9 '11 at 1:11
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.