Is It Possible to Combine TikZ Distance and Line-To Operations?

Using LaTeX and TikZ, is it possible to use a Line-To operation in the same command as a distance operation? I would like to draw a rectangle and then create a named node at the midpoint of the upper edge of the rectangle. To do this I am using a Distance Operation to find the center of the Rectangle and then a Line-To Operation to find the upper edge. See below for the MWE code that I think should work, but is not working.

\documentclass[11pt]{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}

\begin{tikzpicture}
\node (A) at (0,0) {};
\node (B) at (10,10) {};
\node [above right] at (A) {A};
\node [below left] at (B) {B};
\draw [draw=black] (B) rectangle (A);
\node at ($(A)!0.5!(B)$ |- B) {Top of Rect [incorrect]};
\node (ABMid) [below] at ($(A)!0.5!(B)$) {Midpoint AB};
\node (rectTop) [above] at (ABMid|-B) {Top of Rect [correct]};
\end{tikzpicture}
\end{document}

I would not use such mathematical calculation to get a coordinate at the middle of the top line. Nodes on path can be placed quite intelligently.

The problem here is that the node placement on the original rectangle path is only on a straight line between the two coordinates, so the nodes on the two following paths are on the same place with the same <pos>:

\path (c1) --        node[pos=<pos>] {} (c2);
\path (c1) rectangle node[pos=<pos>] {} (c2);

I’ll have three solutions for you.

1. A simply auxiliary coordinate.
2. Replacing the rectangle path with two |-/-| paths.
3. Implementing new timer functions for the rectangle path (packaged in copy-paste-able library).

Solution 1

With an auxiliary coordinate you can simply do

\draw (B) rectangle coordinate (aux1) (A) node[above] at (aux1 |- B) {Top of the Rect};

Code

\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
\coordinate [label=above right:A] (A) at (0,0);
\coordinate [label=below left:B] (B) at (20,10);
\draw (B) rectangle coordinate (aux1) (A) node[above] at (aux1 |- B) {Top of the Rect};
\end{tikzpicture}
\end{document}

Solution 2

Use two combined |- or -| and the position .75 or .25 (respectively). The corners have the position .5 (→ Node on a jointed TikZ path).

Code

\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
\coordinate [label=above right:A] (A) at (0,0);
\coordinate [label=below left:B] (B) at (20,10);
\draw (B) -| node[pos=.25, above] {Top of the Rect} (A) -| (B);
\end{tikzpicture}
\end{document}

Solution 3

A newly written timer for the rectangle path operator. This solution uses the \patchcmd macro from the etoolbox package to easily swap \tikz@timer@line to the new timer \qrr@tikz@timer@rect.

I actually provide two timers, which can be activated with the keys

• rectangle original timer (default)

The original placement between position 0 and 1 is not changed. The rectangle's path can be accessed by positions between 1 and 2.

Corners: 0 = 2, 1.25, 1.5 = 1, 1.75

• rectangle new timer

This style switches the positions between 0 and 1 with those between 1 and 2.

Corners: 0 = 1, 0.25, 0.5 = 2, 0.75

Code

\documentclass[tikz,convert=false]{standalone}
\usetikzlibrary{paths.rectangle}
\begin{document}
\begin{tikzpicture}
\coordinate [label=above right:A] (A) at (0,0);
\coordinate [label=below left:B] (B) at (20,10);
\draw (B) rectangle (A) node[pos=1.875, above] {Top of the Rect};
\end{tikzpicture}
\end{document}

tikzlibrarypaths.rectangle.code.tex

% This is the TikZ library paths.rectangle
%
% This small library extends the node placing algorithm (a.k.a. timer)
% so that nodes can be placed on the path of an rectangle.
%
% Styles are:
%  - rectangle original timer
%    The original placement between position 0 and 1 are not changed.
%    The rectangle's path can be accessed by positions between 1 and 2.
%  - rectangle new timer
%    This style switches the positions between 0 and 1 with those
%    between 1 and 2.

\RequirePackage{etoolbox}
\patchcmd{\tikz@rectB}{\tikz@timer@line}{\qrr@tikz@timer@rect}{}{}

\def\qrr@tikz@timer@rect@origline{%
\ifdim\tikz@time pt>1pt
\pgfutil@tempdima\tikz@time pt
\ifdim\pgfutil@tempdima<1.5pt
\else
\let\pgf@tempa\tikz@timer@start
\let\tikz@timer@start\tikz@timer@end
\let\tikz@timer@end\pgf@tempa
\fi
\multiply\pgfutil@tempdima2
\edef\tikz@time{\strip@pt\pgfutil@tempdima}%
\expandafter\tikz@timer@vhline
\else\expandafter\tikz@timer@line\fi
}
\def\qrr@tikz@timer@rect@lineafterone{%
\pgfutil@tempdima\tikz@time pt
\ifdim\pgfutil@tempdima<1pt
\ifdim\pgfutil@tempdima<.5pt\else
\let\pgf@tempa\tikz@timer@start
\let\tikz@timer@start\tikz@timer@end
\let\tikz@timer@end\pgf@tempa
\fi
\multiply\pgfutil@tempdima2
\edef\tikz@time{\strip@pt\pgfutil@tempdima}%
\expandafter\tikz@timer@vhline
\else
\edef\tikz@time{\strip@pt\pgfutil@tempdima}%
\expandafter\tikz@timer@line
\fi
}
\tikzset{
rectangle original timer/.code=\let\qrr@tikz@timer@rect\qrr@tikz@timer@rect@origline,
rectangle new timer/.code=\let\qrr@tikz@timer@rect\qrr@tikz@timer@rect@lineafterone,
rectangle original timer
}

Output

• I refrained from using the pos command as a rectangle is not necessarily a square, in which case it will be difficult (well, to a certain extent) to find the top point. – zeroth Apr 2 '13 at 17:08
• @zeroth The middle of the top line lies always at 0.125 (solution A) or 1.875 (updated solution B). I have also changed the coordinate of B to (20,0) to highlight this. Of course, if you want a node at a specific distance on the line (and not a ratio of the path’s length), one should either use directly ([xshift=-<distance>] B), a timer function that calculates the position from a distance or the markings library with mark=at position <distance> with \node {…}. – Qrrbrbirlbel Apr 2 '13 at 17:23
• Great! I had completely forgotten about the pos=.5 == corner thingy! That was what was troubling me! Great! Thanks! :) Maybe if you elaborate on this in the post it will be more clear (as it generally is a non expected feature). I mean quarter way on a path is actually half-way on the first line segment when dealing with line-to operations! – zeroth Apr 2 '13 at 17:32
• I will probably use the first solution with a slight modification: \draw[red, thick, rounded corners=3pt] (A) -| (B) coordinate[pos=0.25] (test) -| (A); – CrazyArm Apr 2 '13 at 18:14
• @zeroth See my update. Maybe \draw (B) rectangle coordinate (aux1) (A) node[above] at (aux1 |- B) {Top of the Rect}; (see my first solution) is the easiest. Though, I do not like solutions where I have to specify one coordinate twice. – Qrrbrbirlbel Apr 2 '13 at 21:39

EDIT: An inline calculation is seen in the bottom.

In principle it is. I haven't really optimized the below, it could be reduced somewhat.

You can utilize the let operator (which also comes from the calc library).

What you want to do is obtain the maximum y coordinate and then select the half x coordinate. So you need the x and y coordinates of each point.

Furthermore you should never specify a coordinate using a node! This can give very unforseen problems. Remember that nodes retain a size, whereas coordinates does not.

I would do your system something like this:

\begin{tikzpicture}
\coordinate (A) at (0,0);
\node[above right] at (A) {A};
\coordinate (B) at (10,10);
\node[below left] at (B) {B};
\draw [draw=black] (A) rectangle (B);
\draw let \p1 = (A),
\p2 = (B),
\n{x} = {(\x1+\x2)/2},
\n{y} = {max(\y1,\y2)} in
(\n{x},\n{y}) node[above] {Top of Rect [incorrect]};
\coordinate (ABMid) at ($(A)!0.5!(B)$);
\node at (ABMid) {Midpoint AB};
\end{tikzpicture}

Of course, if you don't need the coordinate ABMid then just do \node at ($(A)!.5!(B)$) {...};

What the above does is calculating on the path the x and y coordinate of your top point. \p<int> = (<coordinate>) will save x and y in \x<int>,\y<int> for further computation within the let line. The \n{x} line calculates the x-coordinate of your top point, and the \n{y} line calculates the maximum y-coordinate.

The above will yield:

I realized that you really wanted something inline (which is doable but ugly). So what you can do is project B onto the vertical line starting in A, then calculate the middle point between this and B, that will give you the half way on the line you need.

So this can be done via this obscure construct:

\coordinate (ABMidTop) at ($(A)!(B)!($(A)+(0,1)$)!.5!(B)$);
\node[above] at (ABMidTop) {Top of Rect [incorrect]};

It should be read like this:
draw the line A to A+(0,1), then project B onto this line. Then take the halfway point between the previous calculated point and B.