36

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.

  • 1
    Lots of your examples make the impression as if you were actually looking for a special arrow tip. – Ulrike Fischer Dec 13 '14 at 11:00
  • @UlrikeFischer why not! Any solution is ok for me ;) But with arrows, for example, I don't know how to inherit transparancy from the path. And how they will resolve number (6) wich fails deeply in my first method. Can we put arrow on layer ? – Kpym Dec 13 '14 at 12:36
  • Foe me for a mathematical object, a point is represented by \tikz\fill circle (2pt); in a book or a paper and by a cross at the blackboard. Then a point can be used for a vertex and it's possible tu use your representation. With a mathematical object I'm not sure if it's interesting to change the line width or the width of the points. The minimum is required. I think about this problem for tkz-euclid. The main problem was to avoid to scale the points with the picture. – Alain Matthes Apr 13 '16 at 17:13
17

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

  • The current version of the node on layer doesn't look especially hackish, does it? At least, to me it looks no more hackish than anything which satisfies 6 is likely to look. Aren't you inevitably going to need to put it on a different layer? What other possibilities are there? [I guess there is the behind/in front of path stuff but I'm not sure that would do what you want...] – cfr Dec 14 '14 at 2:47
  • You could style the pic using pic actions? – cfr Dec 14 '14 at 2:50
  • @cfr The second method satisfy 6 without hack at all but I don't know how to inherit the path draw options (color, opacity) to the pic without repeating the style. And pic actions are only "copy" of actions explicitly defined in the pic style. For example \draw pic[fill]{point}; will have as pic actions only the fill. That is a main difference between nodes and pics : one can inherit the style, the other can use pgfonlayer. But I want both ;) – Kpym Dec 14 '14 at 6:58
  • @cfr Are you confident that the last version of 'node on layer' will be compatible with PGF 3.1 ? – Kpym Dec 14 '14 at 7:00
  • No. I'm not sure that anything here will be proof against that. (Unless you get an answer from somebody responsible for the package, of course.) – cfr Dec 14 '14 at 16:57
14

Note: I have created a small tikz library based on this answer and named nicepoints that is available on GitHub.

Finally I have a method to draw points that satisfy all criteria (in slightly different way for the color inheritance) and even more.
Before to give you the full solution, let's start from the beginning of the story.

The easiest way

I just realized that probably the best way to make a point is to use a "dot" .:
\tikz\draw[very thin,red] (0,0) -- node{.} (1,0);
enter image description here

The color is inherited in a very useful way I think. The color of the point is the same as the text color.
\tikz\draw[very thin,red,text=violet] (0,0) -- node{.} node[above]{A} (1,0); enter image description here

But points like this are too small for thicker lines.
\tikz\draw[very thick,red,text=violet] (0,0) -- node{.} (1,0); enter image description here

We can scale it by using line width, and automate all this by creating a style.

\tikzset{point/.style={insert path={ node[scale=2.5*sqrt(\pgflinewidth)]{.} }}}

\tikz\draw[very thick,red,text=violet] (0,0) -- node[point,above]{A} (1,0);

enter image description here

The choice of sqrt is a personal taste : in this way for thinner lines the dots are not too small, and for thicker ones, not too fat. Actually, in this way, the surface of the point is proportional to the line width.

More sophisticated points

And if we want to fill the point, we can simply draw another smaller dot over the first one:

\tikzset{
  outer dot/.style = {scale=2.5*sqrt(\pgflinewidth)},
  inner dot/.style = {scale=sqrt(\pgflinewidth),#1},inner dot/.default={white},
  point/.style={insert path={ node[outer dot]{.} node[inner dot=#1]{.}}}
}
\tikz\draw[very thick,blue] (0,0) -- node[point]{} (1,0) [point=red];

enter image description here

The problem that is not solved yet is that the lines drawn after a point can overlap it like this:

\begin{tikzpicture}[scale=2]
  \draw[very thick,blue] (0,0) -- node[point]{} (1,0) [point=red];
  \draw[red] (0,0) -- node[point]{} (1,.2);
  \draw[very thick] (0,.2) -- (1,0);
\end{tikzpicture}

enter image description here

The solution

What we want is to put the point on a top layer, in a way that any line drawn after it stays under the point. For this we can use pgfonlayer but there are two difficulties:

  1. There are not so many ways to automatically insert pgfonlayer. We can use some hacks (but I don't want this). We can use pic, but in this case we have to fix the bug of TikZ 3.0.0 if we want to be able to use nodes after the point. And the third option that I know is to use path picture.
    Here is something tricky : if we draw something inside path picture by changing the layer, the drawing is not clipped because the clipping is applied to the initial layer.

  2. When we change layer, "almost" everything is reset : the line width, the draw and fill colors, the opacity. But I was nicely surprised when writing this question, that the text color and opacity are not reset by the layer change. So the only thing that we have to take care is the line width. But this is not so hard because we have at our disposal pgflinewidth that we can save before the layer change, and use it on the new layer.

Here is the "non quoted" version of the solution to the initial question:


\pgfdeclarelayer{points}
\pgfsetlayers{main,points}

\tikzset{
  set point size/.code={\pgfmathsetmacro{\pointsize}{sqrt(\pgflinewidth)}},
  point size/.style={set point size/.prefix style={line width=#1}},
  every dot/.style = {inner sep=0, outer sep=0,font=},
  outer dot/.style = {every dot, scale=2.5*\pointsize},
  inner dot/.style = {every dot, scale=\pointsize, text=#1}, inner dot/.default={white},
  point fill/.style = {inner dot/.default={#1}},
  point coordinate/.style={insert path={coordinate(#1)}},
  point/.style={insert path={
      node[inner sep=0, overlay, every point/.try, #1, set point size,
        path picture={
          \begin{pgfonlayer}{points}
            \node[outer dot]{.} node[inner dot]{.};
          \end{pgfonlayer}
        }
      ]{}
    }
  }
}

And one test here:

\begin{tikzpicture}[scale=2]
  \draw[blue] (0,0)[point] -- node[thick,point={name=A},below]{$A$} (1,0) [point=red];
  \draw[red] (0,0) -- node[point]{} (1,.2) [point=thick];
  \draw[very thick] (0,.2)[point={point coordinate=B, label=$B$}] -- (1,0);
  \draw[green] (A)--(B);
\end{tikzpicture}

enter image description here

How to ...

  1. How to use point style?
    • simply as (1,1) [point],
    • or inside another node like this node[point,below]{$A$}.
  2. How to put a point in the middle of segment? We can't use -- [point] syntax, but we can :
    • put inside another node like this -- node[point]{}
    • or after the end of the segment, like this --(1,1)[point=midway]
  3. How to set the draw color of a point? Like we set the text color of all nodes.
    • If the color is set on the path like this \path[red] ... then it is inherited by the points on the path(because the text color is set also in this case).
      \tikz\draw[red] (0,0. -- (1,0) [point=midway]; enter image description here
    • If we set only the draw/fill color on the path/scope, but not the text color, the points are by default black (as the text).
      \tikz\path[draw=red] (0,0) -- (1,0) [point=midway]; enter image description here
    • We can specify the point color as in the following example:
  \begin{tikzpicture}[scale=2]
    \draw[fill=blue!14] 
      (0,0) [point] 
        -- (1,0) [point=blue] 
        -- node[red,point,left]{red point and text} 
           (1,1) node[point,above,red]{red text only}
        -- (0,1) node[point=red,below]{red point only};
  \end{tikzpicture}

enter image description here

  1. How to set the fill color of a point ? By using point fill key.
    • If we want to set it for all points on the path, we can do \path[point fill=red] ...
    • If we want to set it for one point, we can do [point={point fill=red}].
  \tikz\path[ultra thick, red, point fill=blue] 
    (0,0) [point] (.5,0) [point={point fill=green}] (1,0) [point];

enter image description here

  1. How to set the point size ? The point inherit its size from the line width of the path.
    • To change it for one point we can use [point=very thick] for example
    • To set it for all points in the scope independently of the line width we can use point size=.8pt for example

And finally, we can use every point to set the default point style.

Extra stuff : quoted points

If we want to be able to say point="A" and to have coordinate set to (A) and the text $A$ displayed next to the point, we can use the quotes library.

Here is the full code for this quoted points.


\usetikzlibrary{quotes}

\pgfdeclarelayer{points}
\pgfsetlayers{main,points}

\tikzset{
  set point size/.code={\pgfmathsetmacro{\pointsize}{sqrt(\pgflinewidth)}},
  point size/.style={set point size/.prefix style={line width=#1}},
  every dot/.style = {inner sep=0, outer sep=0,font=},
  outer dot/.style = {every dot, scale=2.5*\pointsize},
  inner dot/.style = {every dot, scale=\pointsize, text=#1}, inner dot/.default={white},
  point fill/.style = {inner dot/.default={#1}},
  point coordinate/.style={insert path={coordinate(#1)}},
  quotes mean point/.style={'/.style={empty label/.style={node contents=}},
    node quotes mean/.try={point coordinate=##1,
      label={[direction shorthands, every label quotes/.try, ##2,
        node contents=\ensuremath{##1}, empty label/.try]}}},
  point/.style={quotes mean point, insert path={
      node[inner sep=0, overlay, every point/.try, #1, set point size,
        path picture={
          \begin{pgfonlayer}{points}
            \node[outer dot]{.} node[inner dot]{.};
          \end{pgfonlayer}
        }]{}
    }
  }
}

How to use quoted points

  • When you say [point="B"red], first coordinate(B) is inserted and then the equivalent of label={[red]$B$} is used.
  • If you use [point="B"'] (with ') then $B$ is not displayed (empty label is added), but coordinate(B) is inserted.
  • If we want only a label without coordinate, we can use [point={label=$B$}] or node[point,above]{$B$}.

Let's finish this short answer with an example of "quoted points" in use:

\begin{tikzpicture}[scale=2]
  \draw[fill=yellow!30,very thick]
    (0,0) [point="A"] -- (1,0) [point={"B"',blue}] 
    -- node[red,point="C"left]{} (1,1) [point="D"{above,red}]
    -- (0,1) [point={red,"E"}];
  \draw[thick,purple] (E) -- (B) to[bend right] (D) edge[bend right] (C) [point=near start];
\end{tikzpicture}

enter image description here

UPDATE: We can define \point like this

\def\point[#1] at (#2){\path (#2) [point={#1}]}

and then use it like this:

\point["A"below] at (1,1);

UPDATE 2: After the comments of PaulGaborit I added every dot style that reset the font. In this way if we use a font with a dot that is not centerd in the middle of the node we have two choices :

  • force the "dot" font to be the standratd font,
  • or make some shift (in em) to put it in the center.

For example we can put :

\tikzset{
  every dot/.style={inner sep=0, outer sep=0, 
    node font=\usefont{T1}{lmr}{m}{n}\fontsize{10pt}{0pt}\selectfont}
}
  • 1
    Using "dot" character is dangerous: the center of the node may be different of the center of the dot character (depending on the chosen font). – Paul Gaborit Jan 5 '15 at 15:57
  • @PaulGaborit That is bad news. Did you have an example where this will not work ? – Kpym Jan 5 '15 at 16:03
  • I don't have an example of problematic font with "dot" character but read my answer... – Paul Gaborit Jan 5 '15 at 16:05
  • @PaulGaborit Ok. But if this is the case, we can always change the style to put it in the center. So finaly this is not so big problem ;) – Kpym Jan 5 '15 at 16:08
  • Try your examples with every node/.style={font=\itshape}... – Paul Gaborit Jan 5 '15 at 16:10

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.