3

I am looking for a command in tikz which takes points as arguments and, if these points are counter-clockwise the vertices of a convex polygon, draws a convex whose boundary is smooth and whose boundary contains the points I gave as arguments. I expect this to be rather simple (using Bézier curves for example) but I am a beginner and have never written such command on Latex before.

I have already tried commands like plot smooth or Hobby, and it often does great job, but sometimes in bad cases it does not give me a convex and I don't want to think too much each time I give my vertices.

Here is a bad example.

\documentclass[10pt,a4paper]{article}
\usepackage{tikz}
\usetikzlibrary{hobby}

\begin{document}
\begin{tikzpicture}[scale=5]
\coordinate (a) at (0,0);
\coordinate (b) at (3,0);
\coordinate (c) at (3,1);
\coordinate (d) at (1.5,1);
\coordinate (e) at (0,1);

\path[draw,use Hobby shortcut,closed=true]
(a)..(b)..(c)..(d)..(e);

\draw (a) node{$\bullet$};
\draw (b) node{$\bullet$};
\draw (c) node{$\bullet$};
\draw (d) node{$\bullet$};
\draw (e) node{$\bullet$};

\draw (a)--(b)--(c)--(d)--(e)--(a);
\end{tikzpicture}
\end{document}

Image of above code

I added the example because I was asked to, and it was a good remark : I realized that the algorithm I was thinking about would not work because it does not work with the extreme example I put above. So it might be more difficult than I thought, it might even be not worth look at, and better to look for a way not to ask Hobby to display bad settings of points.

A more complex command I am also looking for is a command which does almost the same, except that I can specify at each vertex :

  1. If I want the curve between this vertex and the following to be a straight line (it can forces the convex not to be smooth at some vertices, for example if I ask this condition to be fulfilled for each vertex I get the polygon which does just link one by one the vertices counter-clockwise).

  2. If I want the convex to a bit sharp at the vertex (for example giving a percentage of sharpness), the sharpest convex being of course the polygon which does just link the vertices one by one counter-clockwise.

In fact there cannot be a general answer to my question because one can consider the following very bad situation. The points are counter-clockwise

(0,0) (0.5,0) (1,0) (1,0.5) (1,1) (0.5,1) (0,1) (0,0.5)

Then there is only convex whose boundary contains the points : the square itself, which is not smooth (by the way we see here that convexity can be very rigid, far more than just smoothness since a finite number of points determine entirely the shape of my convex). Sorry for asking sort of non rigorous question. Still there must be a way to have a function which displays smooth convex when it is possible and else adds some singularities (the fewest possible).

Is there a good reference to learn quickly how to build commands like the one I need ?

  • If these points are counter-clockwise the vertices of a CONVEX polygon sorry. – Pierre-Louis May 28 '18 at 16:03
  • 1
    You can smoothen the path with these tex.stackexchange.com/questions/249860/… – percusse May 28 '18 at 16:21
  • Can you post an example of what doesn't work with hobby? This seems what the hobby package is designed to do. It certainly allows you to do 1 and 2. – Loop Space May 28 '18 at 18:47
  • I've added the picture from your code. Can you clarify what you would expect from these points and what you would be prepared to specify manually versus what should be automatic. For example, would you be prepared to specify that you want to draw the top part of your picture as a straight line? – Loop Space May 29 '18 at 10:15
  • 1
    Does \draw[ultra thick, red, use Hobby shortcut] ([out angle=180]e) .. (a)..(b)..([in angle=0]c) -- (d) -- (e); in your code fit what you're looking for? – Loop Space May 29 '18 at 11:40
4

UPDATE: 1st attempt to answer the updated question.

\documentclass[tikz,border=3.14mm]{standalone}

\begin{document}
\begin{tikzpicture}[scale=5]
\coordinate (a) at (0,0);
\coordinate (b) at (3,0);
\coordinate (c) at (3,1);
\coordinate (d) at (1.5,1);
\coordinate (e) at (0,1);

\foreach \X in {a,...,e}
{\fill (\X) circle (0.6pt);}

\draw (a) to[out=-90,in=-90] (b)--(c)--(d)--(e)-- cycle;
\draw[blue] (a) to[out=-60,in=-120] (b);
\draw[red] (a) to[out=-30,in=-150] (b);
\end{tikzpicture}
\end{document}

enter image description here

Just for fun, no competitor of the answers to this question. You can use any smooth plot through the coordinates and draw a contour around them. If the contour is very sharp, you may need to decrease contour step.

\documentclass[tikz,border=7pt]{standalone}
\usetikzlibrary{decorations,decorations.markings} 
\pgfkeys{/tikz/.cd,
    contour distance/.store in=\ContourDistance,
    contour distance=-10pt, % for the other orientation use a +
    contour step/.store in=\ContourStep,
    contour step=1pt,
}

\pgfdeclaredecoration{closed contour}{initial}
{% 
\state{initial}[width=\ContourStep,next state=cont] {
    \pgfmoveto{\pgfpoint{\ContourStep}{\ContourDistance}}
    \pgfcoordinate{first}{\pgfpoint{\ContourStep}{\ContourDistance}}
    \pgfpathlineto{\pgfpoint{0.3\pgflinewidth}{\ContourDistance}}
    \pgfcoordinate{lastup}{\pgfpoint{1pt}{\ContourDistance}}
    \xdef\marmotarrowstart{0}
  }
  \state{cont}[width=\ContourStep]{
     \pgfmoveto{\pgfpointanchor{lastup}{center}}
     \pgfpathlineto{\pgfpoint{\ContourStep}{\ContourDistance}}
     \pgfcoordinate{lastup}{\pgfpoint{\ContourStep}{\ContourDistance}}
  }
  \state{final}[width=\ContourStep]
  { % perhaps unnecessary but doesn't hurt either
    \pgfmoveto{\pgfpointanchor{lastup}{center}}
    \pgfpathlineto{\pgfpointanchor{first}{center}}
  }
}

\begin{document}
  \begin{tikzpicture}
    \draw[decoration={closed contour},decorate] plot[smooth cycle] coordinates {(0,0) (2,0) (3,1) (0,2)};
    \draw plot[smooth cycle,mark=*] coordinates {(0,0) (2,0) (3,1) (0,2)};
  \end{tikzpicture}

  \begin{tikzpicture}
    \draw[decoration={closed contour},decorate] plot[smooth cycle,tension=1.5] coordinates {(0,0) (2,0) (3,1) (0,2)};
    \draw plot[smooth cycle,mark=*,tension=1.5] coordinates {(0,0) (2,0) (3,1) (0,2)};
  \end{tikzpicture}

    \begin{tikzpicture}
    \draw[decoration={closed contour},decorate] plot[smooth cycle,tension=0.5] coordinates {(0,0) (2,0) (3,1) (0,2)};
    \draw plot[smooth cycle,mark=*,tension=0.5] coordinates {(0,0) (2,0) (3,1) (0,2)};
  \end{tikzpicture}
\end{document}

enter image description here

A SECOND METHOD: Based on this answer. I don't know what the real life applications are like, but if this turns out to useful, I'll be happy to make it more user friendly.

\documentclass[margin=3.14mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc,decorations.pathreplacing,decorations.pathmorphing}

\makeatletter
% to produce automaticaly homothetic paths from https://tex.stackexchange.com/a/72753/121799
\newcounter{homothetypoints} % number of vertices of path
\tikzset{
  % homothety is a family...
  homothety/.style={homothety/.cd,#1},
  % ...with some keys
  homothety={
    % parameters
    scale/.store in=\homothety@scale,% scale of current homothetic transformation
    center/.store in=\homothety@center,% center of current homothetic transformation
    name/.store in=\homothety@name,% prefix for named vertices
    % default values
    scale=1,
    center={0,0},
    name=homothety,
    % initialization
    init memoize homothetic path/.code={
      \xdef#1{}
      \setcounter{homothetypoints}{0}
    },
    % incrementation
    ++/.code={\addtocounter{homothetypoints}{1}},
    % a style to store an homothetic transformation of current path into #1 macro
    store in/.style={
      init memoize homothetic path=#1,
      /tikz/postaction={
        decorate,
        decoration={
          show path construction,
          moveto code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1 ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentfirst)$)}
            % name this vertex
            \coordinate[homothety/++](\homothety@name-\arabic{homothetypoints})
            at ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentfirst)$);
          },
          lineto code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1 -- ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$)}
            % name this vertex
            \coordinate[homothety/++] (\homothety@name-\arabic{homothetypoints})
            at ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$);
          },
          curveto code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1
              .. controls ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentsupporta)$)
              and ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentsupportb)$)
              .. ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$)}
            % name this vertex
            \coordinate[homothety/++] (\homothety@name-\arabic{homothetypoints})
            at ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$);
          },
          closepath code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1 -- cycle ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$)}
          },
        },
      },
     },
    store coordinates in/.style={
      init memoize homothetic path=#1,
      /tikz/postaction={
        decorate,
        decoration={
          show path construction,
          moveto code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1 ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentfirst)$)}
            % name this vertex
            \coordinate[homothety/++](\homothety@name-\arabic{homothetypoints})
            at ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentfirst)$);
          },
          lineto code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1 ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$)}
            % name this vertex
            \coordinate[homothety/++] (\homothety@name-\arabic{homothetypoints})
            at ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$);
          },
          curveto code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1 ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$)}
            % name this vertex
            \coordinate[homothety/++] (\homothety@name-\arabic{homothetypoints})
            at ($(\homothety@center)!\homothety@scale!(\tikzinputsegmentlast)$);
          },
          closepath code={
            % apply homothetic transformation to this segment and add result to #1
            \xdef#1{#1 }
          },
        },
      },
    },
  },
}
\makeatother

\begin{document}
\begin{tikzpicture}[font=\bfseries\sffamily]

  % some styles

  % draw a path (and memomize its definition into \mypath with points named A-1, A-2,...)
  \draw[homothety={store in=\mypath,name=A}]
  plot[mark=*] coordinates {(0,0) (2,0) (3,1) (0,2)} -- cycle;
  % compute the barycentric coordinate (can be automatized)
  \coordinate (A-center) at (barycentric cs:A-1=0.25,A-2=0.25,A-3=0.25,A-4=0.25);
  % compute the homothetic hull
  \path[homothety={store coordinates in=\secondpath,scale=1.2,center=A-center}] \mypath;
  % draw a smooth version of the hull
  \draw[blue] plot [smooth cycle] coordinates {\secondpath};
\end{tikzpicture}
\end{document}

enter image description here

  • I now realize thanks to your answer that there is among the arguments of the command plot smooth one parameter called "tension". I should have looked more carefully. Maybe this parameter is enough to force the curve to be convex. I will try. Thanks ! – Pierre-Louis May 28 '18 at 18:05
  • Ok I think I understand now your answer. I asked for "a convex whose boundary is smooth and contains the vertices" and I meant "a convex whose boundary is smooth and whose boundary contains the vertices". Maybe it was ambiguous so I changed my post. I guess that you understood that I wanted "a convex who contains the vertices and whose boundary is smooth". Sorry for the lack of clarity. – Pierre-Louis May 29 '18 at 8:17
  • @Pierre-Louis I made an update, but I'm not quite sure if I understand the question. Please have a look and let me know if this goes in the right direction. – marmot May 29 '18 at 11:39
  • In fact in the case we are looking at (the rectangle with one fake vertex at the middle of an edge), there is actually a smooth convex whose boundary contains the vertex, and that is the convex I would like to draw. Yours is not smooth. However the function (a) to[out=-60,in=-120] (b) can be very useful if it gives straight lines when out=in. As I explained to Loop Space I would like the function to choose automatically the angles of the Béziers curves (Does (a) to[out=-60,in=-120] (b) display a Béziers curve ?) – Pierre-Louis May 29 '18 at 13:06
  • @Pierre-Louis How about \draw[blue] (a) to[out=-60,in=-120] (b) to[out=60,in=0] (c) -- (d) -- (e) to[out=180,in=120] cycle;? Before starting to think about an automatic solution, I first would like to clarify what you want. (BTW, the black curve is smooth.) – marmot May 29 '18 at 13:21
0

Ok after some work I managed to write some code (probably one of the most badly written code in history, I already begin to apologize) which does what I want (I hope). I am going to write it here so that I can say that my question is answered. However I am aware it is not a satisfactory and not easy to understand ; I hope it is better than nothing.

\documentclass[10pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}

\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{xstring}
\usepackage{intcalc}
\usepackage{comment}


\newcounter{mycount}

%a command to compute the length of a list
\newcommand{\length}[1]{
    \setcounter{mycount}{0}
    \foreach \x in {#1}{\stepcounter{mycount}}}


%a command to compute the ith element of a list
\makeatletter
\newcommand{\listnb}[3]{
    \foreach \temp@a [count=\temp@i] in {#1} {
        \IfEq{\temp@i}{#2}{\global\let#3\temp@a\breakforeach}{}}
    \par}
\makeatother


%a command to compute the (i modulo the length of the list)-th element of a list
\newcommand{\listnbmod}[2]{
    \listnb{#1}{\intcalcInc{\intcalcMod{\intcalcDec{#2}}{\themycount}}}}


%a command to get angles
\newcommand{\getanglepoints}[3]{
    \pgfmathanglebetweenpoints{\pgfpointanchor{#2}{center}}
                              {\pgfpointanchor{#3}{center}}
    \global\let#1\pgfmathresult}


%another command to get angles
\newcommand{\getanglelines}[5]{
    \pgfmathanglebetweenlines{\pgfpointanchor{#2}{center}}
                             {\pgfpointanchor{#3}{center}}
                             {\pgfpointanchor{#4}{center}}
                             {\pgfpointanchor{#5}{center}}
    \global\let#1\pgfmathresult}


% a command to get distances
\makeatletter
\newcommand{\getdistance}[3]{
    \pgfpointdiff{\pgfpointanchor{#2}{center}} 
                 {\pgfpointanchor{#3}{center}} 
    \pgf@xa=\pgf@x
    \pgf@ya=\pgf@y
    \pgfmathparse{veclen(\pgf@xa,\pgf@ya)/28.45274/2} 
    \global\let#1\pgfmathresult}
\makeatother


%a command to choose good angles which control the Béziers curves I will glue together
\makeatletter
\newcommand{\chooseangle}[6]{
    \getanglelines{\temp@a}{#1}{#2}{#2}{#3}
    \getanglelines{\temp@b}{#2}{#3}{#3}{#4}
    \getanglelines{\temp@c}{#3}{#4}{#4}{#5}
    \getanglepoints{\temp@d}{#3}{#4}
    \pgfmathsetmacro\temp@e{ifthenelse(greater(\temp@a +\temp@c ,0.001),-\temp@c /(max(\temp@a +\temp@c ,0.001))*\temp@b+\temp@d,-\temp@b /2 +\temp@d)}
    \global\let#6=\temp@e}
\makeatother


%a command to choose good distances to control the Béziers curves
\makeatletter
\newcommand{\choosecontrolpoints}[6]{
    \getanglepoints{\temp@c}{#1}{#2}
    \getdistance{\temp@l}{#1}{#2}
    \pgfmathsetmacro\temp@a{Mod(\temp@c -#3 ,360)}
    \pgfmathsetmacro\temp@b{Mod(#4-\temp@c ,360)}
    \pgfmathsetmacro\temp@la{
        ifthenelse(less(\temp@a,90),
            ifthenelse(less(\temp@b,90),abs(sin(\temp@b))*\temp@l,\temp@l),
            ifthenelse(less(\temp@b,90),abs(sin(\temp@b))*\temp@l,\temp@l))}
    \pgfmathsetmacro\temp@lb{
        ifthenelse(less(\temp@b,90),
            ifthenelse(less(\temp@a,90),abs(sin(\temp@a))*\temp@l,\temp@l),
            ifthenelse(less(\temp@a,90),abs(sin(\temp@a))*\temp@l,\temp@l))}
    \global\let#5=\temp@la
    \global\let#6=\temp@lb}
\makeatother


%the final command
\makeatletter
\newcommand{\cvx}[1]{
    \foreach \i in {1,...,\themycount} {
        \listnbmod{#1}{\intcalcSub{\i}{2}}{\A}
        \listnbmod{#1}{\intcalcSub{\i}{1}}{\B}
        \listnbmod{#1}{\i}{\C}
        \listnbmod{#1}{\intcalcAdd{\i}{1}}{\D}
        \listnbmod{#1}{\intcalcAdd{\i}{2}}{\E}
        \listnbmod{#1}{\intcalcAdd{\i}{3}}{\F}
        \chooseangle{\A}{\B}{\C}{\D}{\E}{\a}
        \chooseangle{\B}{\C}{\D}{\E}{\F}{\b}
        \choosecontrolpoints{\C}{\D}{\a}{\b}{\la}{\lb} 
        \coordinate (G) at ($(\C)+(\a:\la)$);
        \coordinate (H) at ($(\D)+(\b+180:\lb)$);
        \draw (\C) .. controls (G) and (H) .. (\D);}}
\makeatother


%examples where it works
\begin{document}
\begin{tikzpicture}
\coordinate (A) at (0,0);
\coordinate (B) at (1,0);
\coordinate (C) at (0.55,0.55);
\coordinate (D) at (0,1);
\length{A,B,C,D}
\cvx{A,B,C,D}
\end{tikzpicture}

\begin{tikzpicture}
\coordinate (A) at (0,0);
\coordinate (B) at (1,0);
\coordinate (C) at (0.5,0.5);
\coordinate (D) at (0,1);
\coordinate (E) at (0,0.5);
\length{A,B,C,D,E}
\cvx{A,B,C,D,E}
\end{tikzpicture}
\end{document}

And I still don't know how to put images. I am going to look at that soon but not now.

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.