[Tex/LaTex] How to draw points in TikZ

tikz-pgf

Very often we need to draw points. Every one of us has it's favorite method to do this.

What is yours ?

Do you use nodes, pics, marks, … ? In a style ?

Some backgrounds

In the tikz/pgf manual this is done very often by \tikz\fill circle (2pt);.

In the example of the key /tikz/insert path we can see this code

\tikz [c/.style={insert path={circle[radius=2pt]}}]
\draw (0,0) -- (1,1) [c] -- (3,2) [c];

There is also an example for the key handler /.pic (p.255) to produce the same filled circle.

These methods has the advantage to be really simple, but are not good for me because:

  • The appearance of the point depend on the path command action (draw, fill, …).
  • When you scale the image the line width is not scaled (nor the font size) but the points are scaled (you can easily overcome this by putting the size in em for example).
  • When you draw a line after drawing the point, the line is drawn over the point.

And more …

What I'm looking for

Here is a list of things that I would like to be able to do with only one definition of "point" (or with more than one definition but with consistent syntax).

For every requirement I gave the test to pass with the expected result. In the test you can replace "point" by your favorite syntax.

1) Points must be scaled properly. And for me this is not clear if the size should be proportional to the line width or to the font size. Probably the best is to size it using the line width and then if necessary ot be able (see point 3)) to set the size in em if we want a font scaling compatibility. What do you think ?

\begin{tikzpicture}
  \foreach[count=\i] \w in {ultra thin, thin, ultra thick} {
  \draw[yshift=-\i em, \w] (0,0) -- (.5,0) "point" -- (1,0);
  }
  \foreach[count=\i] \s in {.2, .5, 1} {
  \draw[xshift=1.5cm, yshift=-\i em, scale=\s] (0,0) -- (.5,0) "point" -- (1,0);
  }
\end{tikzpicture}

enter image description here

2) We should be able to style points easly. For example we sould be able to say something like "draw thick red point".

(see the following point for the test)

3) Draw, fill and opacity of points could be set to inherit in which case this parameters are inherited from the scope/path. But only the draw sould be set to inherit by default, the other defaults (personal taste) should be fill=white and opacity=1.

\begin{tikzpicture}[scale=2, very thick]
  \filldraw[draw opacity=.5, draw=red, fill opacity=.3, densely dotted]
    (0,0) "point" -- (.5,0) "ultra thick point filled in green" -- (.5,.5) "point with inherited draw, fill and opacity" -- cycle;
\end{tikzpicture}

enter image description here

4) The point could be easily named in place to use coordinate and if the point is drawn with node the name should point to the center of the node and not to the node itself.

\begin{tikzpicture}
  \draw[very thick] (0,1) "point" -- (1,0) "thick point filled in green with name=A";
  \draw[ultra thick, purple] (0,0) "point" -- (A);
\end{tikzpicture}

enter image description here

5) Points could be used in every situation where we can normally use another command to draw it.
This is already illustrated with the previous examples, but it will be nice if we could use it with \node at and \coordinate at (which is not obvious because at don't change the current coordinate).

(see the following point for the test)

6) Points are drawn on top of any line (on the foreground layer).

\begin{tikzpicture}
  \node[left] {A} at (0,1) "ultra thick point";
  \coordinate["thick point"] (B) at (1,0);
  \draw (A) -- (B);
\end{tikzpicture}

enter image description here

7) The solution must be non hacky one, so that the definition of "point" could be (hopefully) compatible with future versions of tikz.

What is my personal incomplete solution

I'll publish it as an answer.

Best Answer

Method I (using node)

\tikzset{
  every point/.style = {circle, inner sep={.75\pgflinewidth}, opacity=1, draw, solid, fill=white},
  point/.style={insert path={node[every point, #1]{}}}, point/.default={},
  point name/.style = {insert path={coordinate (#1)}},
}

and some extra stuff :

\tikzset{
  colored point/.style = {point={fill=#1}},
  inherit/.style = {point/.style={insert path={node[circle, inner sep={.75\pgflinewidth}, draw, fill, #1]{}}}}
}
  • Satisfies 1.

  • Satisfies 2 with styling like this [point={fill=red, very thick}]

  • Partially satisfies 3. I don't know how to define draw opacity=inherit or fill=inherit. I define new style inherit which will redefine the entire point by removing the opacity=1 and fill=white, but this is ugly ;).

  • Partially satisfy 4 : you can use point name=A. I would like to be able to use quotes for saying something like [point={red, "A"}] but I don't know how to do this.

  • Almost satisfy 5 : we can put [point] almost anywhere, like (A) [point], or node[point, above]{A}, or coordinate[point](A). But can't be used with \coordinate at or \node at (except if you repeat yourself like this \coordinate (A) at (1,1) (A) [point];)

  • FAILS on 6. I know that there is a hacky solution to put node on layer, but this is in contradiction with 7).

  • Satisfies 7.

The full code of all tests and the result

\documentclass[varwidth,border=50]{standalone}
\usepackage{tikz}

% not clear how to use layers with this method
\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}

\tikzset{
  every point/.style = {circle, inner sep={.75\pgflinewidth}, opacity=1, draw, solid, fill=white},
  point/.style={insert path={node[every point, #1]{}}}, point/.default={},
  colored point/.style = {point={fill=#1}},
  point name/.style = {insert path={coordinate (#1)}},
  inherit/.style = {point/.style={insert path={node[circle, inner sep={.75\pgflinewidth}, draw, fill, #1]{}}}}
}

\begin{document}
  \begin{itemize}

    % ---------------------------------
    \item Test 1 : ok.\\[1em]
    \begin{tikzpicture}
      \foreach[count=\i] \w in {ultra thin, thin, ultra thick} {
      \draw[yshift=-\i em, \w] (0,0) -- (.5,0) [point] -- (1,0);
      }
      \foreach[count=\i] \s in {.2, .5, 1} {
      \draw[xshift=1.5cm, yshift=-\i em, scale=\s] (0,0) -- (.5,0) [point] -- (1,0);
      }
    \end{tikzpicture}

    % ---------------------------------
    \item Test 2 : ok.

    % ---------------------------------
    \item Test 3 : partialy ok, there is no good \texttt{inherit}.\\
    \begin{tikzpicture}[scale=2, very thick]
      \filldraw[draw opacity=.5, draw=red, fill opacity=.3, densely dotted]
        (0,0) [point] -- (.5,0) [point={ultra thick, fill=green}] -- (.5,.5) [inherit, point] -- cycle;
    \end{tikzpicture}

    % ---------------------------------
    \item Test 4 : almost ok (using \texttt{point name})\\
    \begin{tikzpicture}
      \draw[very thick] (0,1) [point] -- (1,0) [point={thick, fill=green, point name=A}];
      \draw[ultra thick, purple] (0,0) [point] -- (A);
    \end{tikzpicture}

    % ---------------------------------
    \item Test 5 : almost ok.

    % ---------------------------------
    \item Test 6 : fails ! (visible in test 4 too)\\
    \begin{tikzpicture}
      \coordinate (A) at (0,1) (A) node[point=ultra thick, left] {A};
      \coordinate (B) at (1,0) (B) [thick, point];
      \draw (A) -- (B);
    \end{tikzpicture}

  \end{itemize}
\end{document}

enter image description here


Method II (using pic)

\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}

\tikzset{
  every point/.style = {radius={\pgflinewidth}, opacity=1, draw, solid, fill=white},
  pt/.pic = {
    \begin{pgfonlayer}{foreground}
      \path[every point, #1] circle;
    \end{pgfonlayer}
  },
  point/.style={insert path={pic{pt={#1}}}}, point/.default={},
  point name/.style = {insert path={coordinate (#1)}}
}
  • FAILS on 1. I don't know how to inherit styles from path to pic. Is there some style like current path style ?

  • Satisfies 2. Same as method I.

  • FAILS on 3. We can style as in method I, but because (1) fails, (3) fails.

  • Partially satisfies 4. Same as method I.

  • FAILS on 5 : as there is a bug in 'pic' we can't use node after it in PGF 3.0. When this bug will be fixed, this method will be equivalent to the first one at this test.

  • Satisfies 6. This is the main interest of this method.

  • Satisfies 7.

The full code of all tests and the result

\documentclass[varwidth,border=50]{standalone}
\usepackage{tikz}

\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}

\tikzset{
  every point/.style = {radius={\pgflinewidth}, opacity=1, draw, solid, fill=white},
  pt/.pic = {
    \begin{pgfonlayer}{foreground}
      \path[every point, #1] circle;
    \end{pgfonlayer}
  },
  point/.style={insert path={pic{pt={#1}}}}, point/.default={},
  colored point/.style = {point={fill=#1}},
  point name/.style = {insert path={coordinate (#1)}}
}

\begin{document}
\begin{itemize}
\item Test 1 : fails for sizing from path width, scale is ok.\\[1em]
\begin{tikzpicture}
  \foreach[count=\i] \w in {ultra thin, thin, ultra thick} {
  \draw[yshift=-\i em, \w] (0,0) -- (.5,0) [point] -- (1,0);
  }
  \foreach[count=\i] \s in {.2, .5, 1} {
  \draw[xshift=1.5cm, yshift=-\i em, scale=\s] (0,0) -- (.5,0) [point] -- (1,0);
  }
\end{tikzpicture}

\item Test 2 : partialy ok, there is no \texttt{inherit} (at all).

\item Test 3 : fails ! Can't inherit style from path.\\
\begin{tikzpicture}[scale=2, very thick, densely dotted]
  \filldraw[draw opacity=.5, draw=red, fill opacity=.3]
    (0,0) [point] -- (.5,0) [point={ultra thick, fill=green}] -- (.5,.5) [point] -- cycle;
\end{tikzpicture}

\item Test 4 : almost ok (using \texttt{point name})\\
\begin{tikzpicture}
  \draw[very thick] (0,1) [point] -- (1,0) [point={thick, fill=green, point name=A}];
  \draw[ultra thick, purple] (0,0) [point] -- (A);
\end{tikzpicture}

\item Test 5 : fails ! (can't put node after [point] )

\item Test 6 : ok.\\
\begin{tikzpicture}
  \path (0,1) node[left] {A} coordinate (A) [point=ultra thick];
  \coordinate (B) at (1,0) (B) [thick, point];
  \draw (A) -- (B);
\end{tikzpicture}
\end{itemize}
\end{document}

enter image description here