13

I have a shortened curved line drawn between 2 points.
I would like to obtain the coordinates of the beginning and end of that shortened line.

I tried with the following code below
A and B are the coordinates used to define the bended line (red dots)
I would like C and D (black dots) to be the coordinates at the beginning and ending of the shortened line but currently A and C are the same coordinates. Same for B and D as shown in the screenshot enter image description here

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{
  arrows.meta,calc,decorations.markings,math,arrows.meta,positioning,automata}

\begin{document}
\begin{tikzpicture}
  \coordinate (A) at (3.5,3.5); 
  \coordinate (B) at (5.1,2.); 

  \draw[color=red,fill=red] (A) circle (0.15cm);
  \draw[color=red,fill=red] (B) circle (0.15cm);
 
  \draw (A) edge[out=-20,in=140,shorten >=15pt, shorten <=15pt] coordinate[pos=0.]  (C)  coordinate[pos=1] (D)  (B); 

  \draw[black,fill=black] (C) circle (0.05cm);
  \draw[black,fill=black] (D) circle (0.05cm);
\end{tikzpicture}
\end{document}

More generally I would like

  • pos = 0 to correspond to the beginning of the shortened line
  • pos = 1 to correspond to the end of the shortened line

So that I can position points on the shortened line wherever I want (like 25% of the shortened line for instance).

Would someone know a solution? Many thanks in advance

1
  • 1
    The shortening is done by PGF, the node placing (via the pos key) is done by TikZ. Thus, the positioning uses only the start, end and control points to calculate the positions of the nodes while PGF draws something else. And then, your path could be combined of multiple segments, the shortening is only done to the very first and the very last part which doesn't make it easy to catch all possibilities in TikZ. Aug 3 at 16:22

4 Answers 4

9

The issue here is that shortening paths is done very late in the path's construction, so things like positioning nodes doesn't notice the shortening. I suspect this is because the shortening of shorten >=15pt is done by the same mechanism as when an arrow tip is added and generally this shouldn't affect the positioning of things along the path.

So one way to achieve this is to force the shortening to happen earlier. Ideally, one would do this before the positioning stuff was calculated, but I don't know where that is done and I happen to know an alternative way to specify points on a path after the path has been defined, so I'm using that instead.

Life is made slightly more complicated by your use of edge, since that creates a separate path that is drawn inside its own scope so we need to use a few globals to get things out of those scopes.

Here's two ways to achieve your goal. In the first, we save the shortened the path and then use the spath3 library to place the circles at its ends. In the second, we save the original path and then use the spath3 library to shorten it - this has the advantage that the shortening is genuinely along its path (the green lines in each picture are the original unshortened paths).

In both cases, we use the spath3 TikZ library to then access coordinates at positions along the shortened path, using the syntax (spath cs:<path name> <position>).

\documentclass{article}
%\url{https://tex.stackexchange.com/q/652865/86}
\usepackage{tikz}

\usetikzlibrary{spath3,intersections}

\makeatletter
\tikzset{
  shorten path early/.code={
    \tikz@addmode{%
      \pgf@prepare@end@of@path
      \pgf@prepare@start@of@path
      \pgfsetshortenstart{0pt}%
      \pgfsetshortenend{0pt}%
    }%
  },
  shorten then name path/.style={
    shorten path early,
    spath/save global=#1
  }
}
\makeatother


\begin{document}
\begin{tikzpicture}
% Version 1: save the shortened path
\begin{scope}
  \coordinate (A) at (3.5,3.5); 
  \coordinate (B) at (5.1,2.); 

  \draw[color=red,fill=red] (A) circle[radius=0.15cm];
  \draw[color=red,fill=red] (B) circle[radius=0.15cm];

  \draw[ultra thick, green] (A) edge[out=-20,in=140] (B); 


  \draw (A) edge[out=-20,in=140,shorten >=15pt, shorten <=15pt, shorten then name path=short] coordinate[pos=0] (C) coordinate[pos=1] (D) (B); 

  \draw[black,fill=black] (C) circle[radius=0.05cm];
  \draw[black,fill=black] (D) circle[radius=0.05cm];

\fill[magenta] (spath cs:short 0) circle[radius=0.05cm];
  \fill[magenta] (spath cs:short 1) circle[radius=0.05cm];
\end{scope}

% Version 2: save the path and shorten it afterwards
\begin{scope}[xshift=3cm]
  \coordinate (A) at (3.5,3.5); 
  \coordinate (B) at (5.1,2.); 

  \draw[color=red,fill=red] (A) circle[radius=0.15cm];
  \draw[color=red,fill=red] (B) circle[radius=0.15cm];

  \draw (A) edge[ultra thick, green, out=-20,in=140, spath/save global=short] coordinate[pos=0] (C) coordinate[pos=1] (D) (B); 

\tikzset{
  spath/shorten at both ends={short}{15pt}
}

\draw[spath/use=short];

  \draw[black,fill=black] (C) circle[radius=0.05cm];
  \draw[black,fill=black] (D) circle[radius=0.05cm];

\fill[magenta] (spath cs:short 0) circle[radius=0.05cm];
  \fill[magenta] (spath cs:short 1) circle[radius=0.05cm];
\end{scope}

\end{tikzpicture}

\end{document}

Two shortened paths with marked endpoints


On reflection, I think that the spath3 library should apply the shortening before it saves the path. I've implemented it in the development version (on github). With that version, the following works:

\begin{tikzpicture}
  \coordinate (A) at (3.5,3.5); 
  \coordinate (B) at (5.1,2.); 

  \draw[color=red,fill=red] (A) circle[radius=0.15cm];
  \draw[color=red,fill=red] (B) circle[radius=0.15cm];

  \draw (A) edge[ultra thick, green, out=-20,in=140,shorten >=15pt, shorten <=15pt, spath/save global=short] coordinate[pos=0] (C) coordinate[pos=1] (D) (B); 

  \draw[black,fill=black] (C) circle[radius=0.05cm];
  \draw[black,fill=black] (D) circle[radius=0.05cm];

\fill[magenta] (spath cs:short 0) circle[radius=0.05cm];
  \fill[magenta] (spath cs:short 1) circle[radius=0.05cm];
\end{tikzpicture}
1
  • 1
    Many thanks @Andrew Stacey for the solution that is the most complete one. The version 2 it is very nice to see that one can shorten a path while still being able to position points on that path Aug 4 at 7:03
9

Right now I can only think of accessing the points using markings like this:

\documentclass[tikz, border=1cm]{standalone}
\usetikzlibrary{decorations.markings}
\begin{document}
\begin{tikzpicture}
\coordinate (A) at (3.5,3.5); 
\coordinate (B) at (5.1,2.); 
\fill[red] (A) circle[radius=0.15cm];
\fill[red] (B) circle[radius=0.15cm];
\draw[
shorten >=15pt, shorten <=15pt,
postaction=decorate, 
decoration={markings, 
  mark=at position 0 with {\coordinate (C) at (15pt,0);},
  mark=at position 1 with {\coordinate (D) at (-15pt,0);} 
},
] (A) to[out=-20,in=140]  (B); 
\fill (C) circle[radius=0.05cm];
\fill (D) circle[radius=0.05cm];
\end{tikzpicture}
\end{document}

Curve with red and black circles

5
  • the points C and D are not in the initial curve, \begin{tikzpicture} \coordinate (A) at (3.5,3.5); \coordinate (B) at (5.1,2.); \fill[red] (A) circle(.15) (B) circle(.15); \def\initialcurve{(A) to[out=-20,in=140] (B)} \draw[green] \initialcurve; \draw[ shorten >=15pt, shorten <=15pt, postaction=decorate, decoration={markings, mark=at position 0 with {\coordinate (C) at (15pt,0);}, mark=at position 1 with {\coordinate (D) at (-15pt,0);} }, ] \initialcurve; \fill (C) circle(.05) (D) circle(.05); \end{tikzpicture}
    – Black Mild
    Aug 4 at 2:45
  • No - no they are not. -that is how shortening works. Aug 4 at 2:50
  • oh, so your shortening of a curve is different with mine
    – Black Mild
    Aug 4 at 3:15
  • Thanks a lot @hpekristiansen for that solution it is pretty convenient Aug 4 at 7:04
  • @BlackMild Yes, TikZ/PGF has a "quick" way of shortening a curve that moves the endpoints rather than working out exactly where the curve should be cut. Most of the time the difference is so minor, or the user doesn't care enough about the precise curve, as to not be important. There are ways to do it more accurately (see my spath3 library for one) but usually it's not worth the computation time. Aug 4 at 7:04
7

This is an interesting problem (and 15pt is a lot of shortening)!

It might be better to create a custom to path that just moves the start and target/end coordinate the determined amount and do not use the actual shortening. There is probably also a way to do this with the decorations.markings library.

Basically, all the node positioning along a path is done by TikZ. Every time you use a path operator like   (a move to), -- (a line to) (also -| and |-) or .. controls … .., TikZ saves the start coordinate, the end coordinate and, in the latter case, the control points.

Yes, all those bends and outs and ins are at the end of the day just a .. controls (<p1>) and (<p2>) .. path.

We can change this timer, though. However: the timer doesn't know whether its path is at the start of a path (or after a move) and/or at the end of a path (before a move).

This line

\draw[shorten <=15pt] (0,0) node{S} -- (1,0)
   to[out=90, in=30] node[at start]{x} (2,0);

will start at (15pt,0) and the node x should still be placed at (1,0). This is why I'll provide the key shortening position that actually activates the new timer.

If you're only using paths with one segment, this key can be given to a scope, though, of course.

Code

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\tikzset{en/.style={edge node={coordinate[pos=#1/10] (c-#1)}}}
\makeatletter
\def\tikz@timer@curve@shorten{% tikz.code.tex, l 4947
  \pgftransformcurveattime{\tikz@time}
    {\pgfpointlineatdistance{\pgf@shorten@start@additional}{\tikz@timer@start}{\tikz@timer@cont@one}}
    {\tikz@timer@cont@one}{\tikz@timer@cont@two}
    {\pgfpointlineatdistance{\pgf@shorten@end@additional}{\tikz@timer@end}{\tikz@timer@cont@two}}}
\tikzset{shortening position/.code=\let\tikz@timer@curve\tikz@timer@curve@shorten}
\makeatother
\begin{document}
\begin{tikzpicture}[radius=.15cm]
  \filldraw[red] (3.5,3.5) coordinate (A) circle[]
                 (5.1,2.0) coordinate (B) circle[];
  \draw (A) edge[out=-20,in=140,shorten >=15pt, shorten <=15pt, en/.list={0,...,10}, shortening position] (B);

  \foreach \pos in {0,...,10}
    \draw[gray, shorten <=2pt] (c-\pos) --
      node[right,at end,inner sep=0pt,sloped,font=\tiny]
        {\pgfmathprint{!mod(\pos,2)?\pos:""}} +(45:.2);
  
  % the shortening is done at (0,0), the curve timer doesn't know about this
  \draw[shorten <=15pt] (0,0) node{S} -- (1,0) to[out=90, in=30] node[at start]{x} (2,0);
\end{tikzpicture}
\end{document}

Code

enter image description here

2
  • Similar things can be done to -- and the other path operators, too, of course. Aug 3 at 17:08
  • Thanks a lot @Qrrbrbirlbel fo the proposed solution! Aug 4 at 7:01
4

That is an interesting question on path operations. I think TikZ can do that with some efforts, see the above answers.

Here I show that Asymptote can do that easily with the built-in routines: arctime, arcpoint, subpath (see the Asymptote documentation, Paths and Guides). I use the cm unit, so L=15pt/cm; is to change 15pt to cm.

enter image description here

// Determine a point on a given curve 
// via its length on the curve
// http://asymptote.ualberta.ca/
unitsize(1.5cm);
pair A=(3.5,3.5), B=(5.1,2);
path pAB=A..controls A+2dir(-20) and B+2dir(140)..B;
draw(pAB,green+1.5pt);
dot("$A$",align=W,A,red);
dot("$B$",align=E,B,red);

real L=15pt/cm;
real tC=arctime(pAB,L);
pair C=arcpoint(pAB,L);
real tD=arctime(pAB,arclength(pAB)-L);
pair D=arcpoint(pAB,arclength(pAB)-L);
path pCD=subpath(pAB,tC,tD); // a subpath of pAB
draw(pCD);
dot("$C$",align=NE,C);
dot("$D$",align=SW,D);

write("L is ",L);
write("The length of pAB is ",arclength(pAB));
write("The length of pCD is ",arclength(pCD));
write("Checking that 2*L+arclength(pCD) ",2*L+arclength(pCD));
write("that is arclength(pAB) with error 10^{-12}");
shipout(bbox(5mm,invisible)); 

Asymptote can print out its calculations in the interactive mode, as below for checking that C and D is 15pt from 2 endpoints of the initial curve pAB.

enter image description here


Appendix It is even easier to determine a point on a given curve via its relative time (the time on a path at the relative fraction l of its arclength) with point and subpath.

enter image description here

// Determine a point on a given curve 
// via its relative time
// http://asymptote.ualberta.ca/
unitsize(1.5cm);
pair A=(3.5,3.5), B=(5.1,2);
path pAB=A..controls A+2dir(-20) and B+2dir(140)..B;
draw(pAB,green+1.5pt);
dot("$A$",align=W,A,red);
dot("$B$",align=E,B,red);

real tC=.05, tD=.88;         // relative times in [0,1]
pair C=point(pAB,tC);
pair D=point(pAB,tD);
path pCD=subpath(pAB,tC,tD); // a subpath of pAB
draw(pCD);
dot("$C$",align=NE,C);
dot("$D$",align=SW,D);
shipout(bbox(5mm,invisible));
7
  • 1
    That is not the original problem you solve here. Aug 4 at 3:08
  • @hpekristiansen I reread the question and checked my solution. I solved the question, just with Asymptote, instead of TikZ or tikz-based packages
    – Black Mild
    Aug 4 at 3:24
  • 1
    @hpekristiansen there's been a long tradition here of posting answers that solve the underlying question but by means other than requested by the OP, such as pstricks solutions to TikZ questions and vice versa. It's generally felt that this augments the site and makes it more useful for others interested in the same idea but perhaps open to other technologies. It's also a great way to learn about different methods. Aug 4 at 6:59
  • 1
    @BlackMild minor question: is the relative time the parameter used to define the curve or the arc length? I suspect the former, but your use suggests the latter. Aug 4 at 7:01
  • 1
    Thanks a lot @BlackMild it's nice to see there are alternatives to tikz/pgf Aug 4 at 7:05

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.