11

I have a lot of comments to make on different pictures, I can do it with the following command for ex:

\begin{tikzpicture}
    \node[anchor=south west,inner sep=0] (image) at (0,0)
    {\includegraphics[width=0.9\textwidth]{mypicture}};
    \begin{scope}[x={(image.south east)},y={(image.north west)}]
        \draw[red,ultra thick,rounded corners] (0.68,0.01) rectangle (0.97,0.85);
        \node[fill=blue!10,text centered] at (0.83,0.43){Comment one};
        \draw[red,ultra thick,rounded corners] (0.04,0.08) rectangle (0.30,0.55);
         \node[fill=blue!10,text width=2cm,text centered] at
         (0.17,0.31){zone~2 comment~2 multiline};
 \end{scope}
 \end{tikzpicture}

enter image description here

I would like to automate a bit since the text is always centered on the bounding box and shall not go outside.

I haven't a clue where I should start, I have a basic knowledge of TikZ, I tried using a \pgfdeclareshape but without success, don't even kmow if it is the good start! How to specify the decoration of the frame and text separately?

Shall I use a syntax like :

\draw[parameters] (a,b) myshape (c,d){my comment}

Or

\node[theshape, parameters, heigh, width] at (a,b){my comment}

This is far beyond my knowledge today :-(

8

One can grab into a new environment the previous code and declare a couple of commands to create annotations.

So, I created the annotatedtikzpicture as:

\NewDocumentEnvironment{annotatedtikzpicture}{O{width=0.9\textwidth} m}{
\begin{tikzpicture}
   \node[anchor=south west,inner sep=0] (image) at (0,0)
    {\includegraphics[#1]{#2}};
   \begin{scope}[x={(image.south east)},y={(image.north west)}]
}{
 \end{scope}
 \end{tikzpicture}
}

For what concern the commands, both are able to immediately draw the border (with its own options) and the annotated text (with its own options); the differences are:

  1. annote is very tricky for the number of arguments;
  2. xannote is more user-friendly by exploiting keys.

Let's see the definitions:

\NewDocumentCommand{\annote}{o r() r() o r() m}{
\draw[#1] (#2) rectangle (#3);
\node[#4] at (#5) {#6};
}

for \annote, while \xannote and its own keys:

\pgfkeys{/tikz/annotated figure/.cd,
   border width/.initial=0,
   border width/.get=\bwidth,
   border width/.store in=\bwidth,
   border height/.initial=0,
   border height/.get=\bheight,
   border height/.store in=\bheight,
   start border pos/.initial={(0,0)},
   start border pos/.get=\bpos,
   start border pos/.store in=\bpos,
   start text pos/.initial={(0,0)},
   start text pos/.get=\tpos,
   start text pos/.store in=\tpos,
   border options/.code={
    \tikzset{border style/.style={
        #1
      }
    }
   },
   text options/.code={
    \tikzset{text style/.style={
        #1
      }
    }
   },
}

\NewDocumentCommand{\xannote}{r[] m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
\node[coordinate] (x) at \bpos {};
\draw[border style] (x) rectangle ($(x)+(\bwidth,\bheight)$);
\node[text style] at \tpos {#2};
}

Notice that it is also possible to simplify the annotation text position thanks to the key annotation centered set to true. But, to do so, the \xannote command has to be changed into:

\NewDocumentCommand{\xannote}{r[] m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
\node[coordinate] (x) at \bpos {};
\draw[border style] (x) rectangle ($(x)+(\bwidth,\bheight)$);
\ifannotationcentered
  \node[text style] at ($(x)+(0.5*\bwidth,0.5*\bheight)$) {#2};
\else
  \node[text style] at \tpos {#2};
\fi  
}

To make things complete, the following features are added:

  • an \annoteset command is provided: its functionalities are identical to \tikzsetand here is used in order to collect into a style similar options to be passed to \xannote;
  • an \yannote command, different from the previous \xannote since it does not draw first a rectangle and then a node, but rather is just a node. Definition shown later on;
  • a key to compute directly the width of the annotation as asked in How to compute a length and set a text width with pgf; I didn't post the solution there because it's very far from being perfect (actually it won't work under some hypothesis).

So, how it is defined \yannote? It is simply a node in which the minimum height and width are already given:

\NewDocumentCommand{\yannote}{r[] m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
\node[option style, 
   minimum width=\bwidth, 
   minimum height=\bheight] at \tpos {#2}; 
}

The option style is identical to the previous border style and text style and it could be customized by means of the key options. An example:

\yannote[start text pos={(0.5,0.075)},
  border height=0.7,
  border width=0.3,
  options={draw=red,
    fill=blue!10
  },
  ]{A Comment}

In this case, the start border pos is completely useless.

In order to group into a style similar options, it is possible to exploit \annoteset; defined just as:

\NewDocumentCommand{\annoteset}{m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
}

can be used for example to group:

\annoteset{my annotation/.style={
  border options={red,ultra thick,rounded corners},
  annotation centered=true,
  automatic text width=true,
  text options={fill=blue!10,align=center,text width=2.25cm},
  }
}  
\xannote[start border pos={(0.68,0.01)},
  border height=0.77,
  border width=0.29,
  my annotation
  ]{Comment one}  
\xannote [start border pos={(0.04,0.08)},
  border height=0.43,
  border width=0.26,
  my annotation
  ]{zone~2 comment~2 multiline}

Notice the key automatic text width: that's responsible for setting the annotation text width. The width computation is actually performed by the following macro:

% based on Alain's answer:
% https://tex.stackexchange.com/a/38500/13304
% percusse's answer:
% https://tex.stackexchange.com/a/58291/13304
% and the \CalcHeight macro implemented in:
% http://www.ctan.org/pkg/smartdiagram
\makeatletter
\def\CalcWidth(#1,#2)#3{%
\pgfpointdiff{\pgfpointanchor{#1}{west}}{\pgfpointanchor{#2}{east}}
\pgfmathsetmacro{\mywidth}{veclen(\pgf@x,\pgf@y)-4\pgflinewidth}
\global\expandafter\edef\csname #3\endcsname{\mywidth}
}
\makeatother

within the new definition of \xannote:

\NewDocumentCommand{\xannote}{r[] m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
\node[coordinate] (annote-x) at \bpos {};
\draw[border style] (annote-x) rectangle ($(annote-x)+(\bwidth,\bheight)$)coordinate(annote-y);
\coordinate(annote-z) at (annote-x.west-|annote-y.east);
\ifannotationcentered
  \ifautomatictextwidth
    \CalcWidth(annote-x,annote-z){widthborder}
    \node[text style,minimum width=\widthborder] at ($(annote-x)+(0.5*\bwidth,0.5*\bheight)$) {#2};
  \else
    \node[text style] at ($(annote-x)+(0.5*\bwidth,0.5*\bheight)$) {#2};   
  \fi  
\else
  \ifautomatictextwidth
    \CalcWidth(annote-x,annote-z){widthborder}
    \node[text style,minimum width=\widthborder] at \tpos {#2};
  \else
    \node[text style] at \tpos {#2};
  \fi    
\fi  
}

Just an hint on the reasoning: we know that the lower left point of the border is called (annote-x) and we know also the top right point of the border, denoted with (annote-y). To compute the width we basically need to calculate the intersection of the previous coordinates to find the lower right point of the border, (annote-z).

Why I said it's not perfect? Well, just use this approach and set a high text width within text options: it will fail.

A complete example:

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

\NewDocumentEnvironment{annotatedtikzpicture}{O{width=0.9\textwidth} m}{
\begin{tikzpicture}
   \node[anchor=south west,inner sep=0] (image) at (0,0)
    {\includegraphics[#1]{#2}};
   \begin{scope}[x={(image.south east)},y={(image.north west)}]
   \draw(image.south east)rectangle(image.north west);
}{
 \end{scope}
 \end{tikzpicture}
}

% based on Alain's answer:
% https://tex.stackexchange.com/a/38500/13304
% percusse's answer:
% https://tex.stackexchange.com/a/58291/13304
% and the \CalcHeight macro implemented in:
% http://www.ctan.org/pkg/smartdiagram
\makeatletter
\def\CalcWidth(#1,#2)#3{%
\pgfpointdiff{\pgfpointanchor{#1}{west}}{\pgfpointanchor{#2}{east}}
\pgfmathsetmacro{\mywidth}{veclen(\pgf@x,\pgf@y)-4\pgflinewidth}
\global\expandafter\edef\csname #3\endcsname{\mywidth}
}
\makeatother

\NewDocumentCommand{\annote}{o r() r() o r() m}{
\draw[#1] (#2) rectangle (#3);
\node[#4] at (#5) {#6};
}

% predefined styles
\tikzset{border style/.style={draw=none},
  text style/.style={draw=none},
  option style/.style={draw=none},
}

\newif\ifannotationcentered
\newif\ifautomatictextwidth
\pgfkeys{/tikz/annotated figure/.cd,
   border width/.initial=0,
   border width/.get=\bwidth,
   border width/.store in=\bwidth,
   border height/.initial=0,
   border height/.get=\bheight,
   border height/.store in=\bheight,
   start border pos/.initial={(0,0)},
   start border pos/.get=\bpos,
   start border pos/.store in=\bpos,
   start text pos/.initial={(0,0)},
   start text pos/.get=\tpos,
   start text pos/.store in=\tpos,
   border options/.code={
    \tikzset{border style/.style={
        #1
      }
    }
   },
   text options/.code={
    \tikzset{text style/.style={
        #1
      }
    }
   },
   options/.code={
    \tikzset{option style/.style={
        #1
      }
    }
   },
   annotation centered/.is if=annotationcentered,
   annotation centered=false,
   automatic text width/.is if=automatictextwidth,
   automatic text width=false,
}

\NewDocumentCommand{\annoteset}{m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
}

\NewDocumentCommand{\xannote}{r[] m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
\node[coordinate] (annote-x) at \bpos {};
\draw[border style] (annote-x) rectangle ($(annote-x)+(\bwidth,\bheight)$)coordinate(annote-y);
\coordinate(annote-z) at (annote-x.west-|annote-y.east);
\ifannotationcentered
  \ifautomatictextwidth
    \CalcWidth(annote-x,annote-z){widthborder}
    \node[text style,minimum width=\widthborder] at ($(annote-x)+(0.5*\bwidth,0.5*\bheight)$) {#2};
  \else
    \node[text style] at ($(annote-x)+(0.5*\bwidth,0.5*\bheight)$) {#2};   
  \fi  
\else
  \ifautomatictextwidth
    \CalcWidth(annote-x,annote-z){widthborder}
    \node[text style,minimum width=\widthborder] at \tpos {#2};
  \else
    \node[text style] at \tpos {#2};
  \fi    
\fi  
}

\NewDocumentCommand{\yannote}{r[] m}{
\pgfkeys{/tikz/annotated figure/.cd,#1}
\node[option style, 
   minimum width=\bwidth, 
   minimum height=\bheight] at \tpos {#2}; 
}

% something for shadings, taken from:
% https://tex.stackexchange.com/questions/104528/tikz-shade-also-the-border-of-a-node/104541#104541
\tikzset{
 shrink inner sep/.code={
   \pgfkeysgetvalue{/pgf/inner xsep}{\currentinnerxsep}
   \pgfkeysgetvalue{/pgf/inner ysep}{\currentinnerysep}
   \pgfkeyssetvalue{/pgf/inner xsep}{\currentinnerxsep - 0.5\pgflinewidth}
   \pgfkeyssetvalue{/pgf/inner ysep}{\currentinnerysep - 0.5\pgflinewidth}
   }
}

\tikzset{horizontal shaded border/.style args={#1 and #2}{
    append after command={
       \pgfextra{%                 
          \begin{pgfinterruptpath}
                \path[rounded corners,left color=#1,right color=#2]
                ($(\tikzlastnode.south west)+(-\pgflinewidth,-\pgflinewidth)$) 
                rectangle
                ($(\tikzlastnode.north east)+(\pgflinewidth,\pgflinewidth)$);        
           \end{pgfinterruptpath}
        } 
    }
  },
  vertical shaded border/.style args={#1 and #2}{
    append after command={
       \pgfextra{%                 
          \begin{pgfinterruptpath}
                \path[rounded corners,top color=#1,bottom color=#2]
                ($(\tikzlastnode.south west)+(-\pgflinewidth,-\pgflinewidth)$) 
                rectangle
                ($(\tikzlastnode.north east)+(\pgflinewidth,\pgflinewidth)$);        
           \end{pgfinterruptpath}
        } 
    }
  }
}


\begin{document}

\begin{annotatedtikzpicture}[width=0.8\textwidth]{foto}
\yannote[start text pos={(0.5,0.075)},
  border height=0.7,
  border width=0.3,
  options={ultra thick,
    rounded corners,
    vertical shaded border=green!50 and violet!50!magenta,
    top color=green!10,
    bottom color=violet!40!magenta!50
  },
  ]{A Comment}

% a style for grouping similar options  
\annoteset{my annotation/.style={
  border options={red,ultra thick,rounded corners},
  annotation centered=true,
  automatic text width=true,
  text options={fill=blue!10,align=center,text width=2.25cm},
  }
}  
\xannote[start border pos={(0.68,0.01)},
  border height=0.77,
  border width=0.29,
  my annotation
  ]{Comment one}  
\xannote [start border pos={(0.04,0.08)},
  border height=0.43,
  border width=0.26,
  my annotation
  ]{zone~2 comment~2 multiline}
\end{annotatedtikzpicture}

\end{document}

It gives:

enter image description here

  • Picture taken here. – Claudio Fiandrino Jun 4 '13 at 10:29
  • Very nice, many thanks helps a lot. I have adapted your code to specify the frame with corners coords, I am also trying to limit text width to frame width. – Penbeuz Jun 5 '13 at 7:31
  • @Penbeuz: yes, there's still room for improvements. At the moment I left some space for users' decisions with the keys border options and text options: I think is much more better this way since this allows to introduce usual TikZ options. With a bit of care, however, one might define some other keys under the path /tikz/annotated figure/ devoted to customization. Perhaps later I'll give a look deeper on this and on styles... – Claudio Fiandrino Jun 5 '13 at 10:36
  • @Penbeuz: please see the improved solution. :) – Claudio Fiandrino Jun 6 '13 at 7:41
  • thanks for your time and great answer, I will use it and try to understand what you have done :) – Penbeuz Jun 6 '13 at 10:19
1

A stackengine approach. One can use nested versions of \commentfig{x0 to box left}{y0 to text height}{dx-box}{dy-box}{{line 1} {line 2} {...}}{image}

\documentclass{article}
\usepackage{graphicx}
\usepackage{stackengine}
\usepackage{pbox}
\usepackage{xcolor}
\fboxrule=1.5pt
\def\stackalignment{l}
\newlength \vdiff%
\newcommand\commentfig[6]{%
  \setlength\vdiff{#4}%
  \savestack\TextStack{\color{black}\Longstack{#5}}%
  \bottominset{\color{red}\fbox{%
    \rule{0ex}{.5\vdiff}\\%
    \makebox[#3]{\colorbox{white}{\belowbaseline[-.5\ht\TextStackcontent]{\TextStack}}}\\%
    \rule[-.5\vdiff]{0ex}{\vdiff}}}{
  #6
  }{#2}{#1}
}
\begin{document}
\Large
%\commentfig{x0 to box left}{y0 to text}{dx}{dy}{{line 1} {line 2} {...}}{image}
\commentfig{.3in}{1in}{1.45in}{1.3in}{this {is a test} comment}{%
\commentfig{3.75in}{1.35in}{1.45in}{2.5in}{{Comment three}}{%
  \includegraphics{image}%
}
}
\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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