5

I have drawn a blob using hobby following this question and would like to add an arrow denoting the span of my blob. I have tried following the answer to this question to no avail because the bounding box created by scope appears to include the construction of the blob. My current workaround is to eyeball it and use intersections based on this question but this is clearly a scrappy approach and something more repeatable would be better. Thank you for any help.

Here are my first two attempts:

\documentclass[tikz]{standalone}
\usetikzlibrary{arrows, positioning, shapes.misc, arrows.meta}
\usepackage{amsmath, xcolor}
\usetikzlibrary{backgrounds}
\usetikzlibrary{bending, decorations.markings}
\usetikzlibrary{hobby, calc, intersections}

\begin{document}

\begin{tikzpicture}[scale=1, line width=2pt]
    
    
%       range 1, first attempt with bounding box
    \node (r1-1) at (4,5) {.};
    \node (r1-2) at (1,4) {.};
    \node (r1-3) at (1,6) {.};
    \node (r1-4) at (3,9) {.};
    \node (r1-5) at (7,10) {.};
    \node (r1-6) at (8,7) {.};
    
    \begin{scope}[local bounding box=foo]
        \draw[fill=gray, opacity=0.15] (r1-1) to [closed, curve through = {(r1-2) (r1-3) (r1-4) (r1-5) (r1-6)}] (r1-1);
    \end{scope}
    
    \draw[color=blue, line width=1pt] (foo.north east) rectangle (foo.south west);

%       range 2, scrappy workaround
    \node (r2-1) at (14,5) {.};
    \node (r2-2) at (11,4) {.};
    \node (r2-3) at (11,6) {.};
    \node (r2-4) at (13,9) {.};
    \node (r2-5) at (17,10) {.};
    \node (r2-6) at (18,7) {.};

    \node[orange] (top) at (20,10.21) {};
    \node[orange] (bottom) at (20,3.8) {};
    
    \draw[fill=gray, opacity=0.15, name path=range] (r2-1) to [closed, curve through = {(r2-2) (r2-3) (r2-4) (r2-5) (r2-6)}] (r2-1);
    
    \path[name path=AA,overlay] (top) -- +(-20,0);
    \draw[name intersections={of=range and AA},orange] (intersection-2) -- (top);
    \path[name path=BB,overlay] (bottom) -- +(-20,0);
    \draw[name intersections={of=range and BB},orange] (intersection-2) -- (bottom);
    

\end{tikzpicture}

\end{document}

First attempts

5
  • 3
    The bbox library can help find the proper bounding box or curves which doesn't contain the control points. Commented Sep 10 at 16:31
  • Are the orange lines to be tangent to your blob? Commented Sep 10 at 16:41
  • @Qrrbrbirlbel if the orange lines are to denote the extreme top and bottom then they have to be tangential. Commented Sep 10 at 18:43
  • @AndrewStacey That was basically my question. I wasn't sure about that. (Maybe the orange lines should just point to these points from any direction.) Commented Sep 10 at 19:21
  • Thank you both @cis and @Qrrbrbirlbel for your excellent and speedy responses. I ended up relying more heavily on Qrrbrbirlbel's answer for the find bb touches method so have accepted that answer. (and you were correct about my wanting the lines to be tangential) Commented Sep 12 at 10:37

2 Answers 2

4

The bbox library's bezier bounding box will evaluate a bounding box for paths with Bézier curves that will not contain their control points.

The code below finds the intersection between your blob and its bounding box.

Two paths are constructed: one at the top side of the path and one on the bottom side. Because of inprecision of the underlying mathematical engine, the first needs a slight adjustment so that the intersections library will find an intersection. The second is luckier and doesn't need that.

The orange lines can then be drawn from the found intersection points. A counter-correction of the 0.001pt don't seem necessary (it's less than 3 µm).

The second TikZ picture uses the key find bb touches to find all points where the path touches its bounding box on all four sides.

It will try every shifting correction until 0.009pt until it finds at least one intersection. Hopefully, this finds only one per actual touch. But again, due to the inprecision results may vary.

Code

\documentclass[tikz]{standalone}
\usetikzlibrary{bbox, hobby, intersections}
\tikzset{
  name path and bounding box/.style={
    name path={#1}, local bounding box={#1}}}
\tikzset{
  find bb touches/.style={
    /utils/temp/.code n args={5}{
      \pgfmathloop
        \path[overlay, reset cm, name path=bbtouch]
             ([##4shift=+##5.00\inteval{\pgfmathcounter-1}pt]#1.##2)
          -- ([##4shift=+##5.00\inteval{\pgfmathcounter-1}pt]#1.##3);%
        \begingroup
          \tikzset{name intersections={
            of=#1 and bbtouch, sort by=bbtouch, name=#1##1,
            total/.expand once=\csname tikzbb#1##1\endcsname}}%
          \edef\x{\endgroup\def\expandafter\noexpand\csname tikzbb#1##1\endcsname{\csname tikzbb#1##1\endcsname}}\x
        \ifnum\ifnum\pgfmathcounter<10 0\else 1\fi\csname tikzbb#1##1\endcsname=0
      \repeatpgfmathloop},
    /utils/temp={west}{south west}{north west}{x}{},
    /utils/temp={north}{north east}{north west}{y}{-},
    /utils/temp={east}{south east}{north east}{x}{-},
    /utils/temp={south}{south west}{south east}{y}{}}}
\begin{document}

\begin{tikzpicture}[line width=2pt]
\coordinate (r1-1) at (4, 5);
\coordinate (r1-2) at (1, 4);
\coordinate (r1-3) at (1, 6);
\coordinate (r1-4) at (3, 9);
\coordinate (r1-5) at (7,10);
\coordinate (r1-6) at (8, 7);
\path[
  name path and bounding box=blob,
  bezier bounding box,
  draw=black!15, fill=gray!15
] (r1-1) to [closed, curve through = {(r1-2) (r1-3) (r1-4) (r1-5) (r1-6)}] (r1-1);

\path ([yshift=+-.001pt]blob.north west)
   -- ([yshift=+-.001pt]blob.north east)     [name path=topblob];
\path (blob.south west) -- (blob.south east) [name path=botblob];

\tikzset{
  name intersections={of=blob and topblob, by=top},
  name intersections={of=blob and botblob, by=bot}}
\draw[orange] (top) -- (blob.north east) --+(right:1cm)
              (bot) -- (blob.south east) --+(right:1cm);
\end{tikzpicture}

\begin{tikzpicture}[line width=2pt]
\coordinate (r1-1) at (4, 5);
\coordinate (r1-2) at (1, 4);
\coordinate (r1-3) at (1, 6);
\coordinate (r1-4) at (3, 9);
\coordinate (r1-5) at (7,10);
\coordinate (r1-6) at (8, 7);
\path[
  name path and bounding box=blob,
  bezier bounding box,
  draw=black!15, fill=gray!15
] (r1-1) to [closed, curve through = {(r1-2) (r1-3) (r1-4) (r1-5) (r1-6)}] (r1-1);

\tikzset{find bb touches=blob}

\foreach \dir in {west, north, east, south}
  \foreach \i in {1, ..., \UseName{tikzbbblob\dir}}
    \draw[dashed, shift=(blob\dir-\i), rotate=90]
      (\dir:-10mm) -- (\dir:10mm);
\end{tikzpicture}
\end{document}

Output

enter image description here

1
  • A note for anyone else who uses this as a reference. The find bb touches method names the intersections as follows: [name][cardinal direction]-[i] Commented Sep 12 at 10:42
5

So we need to find correct min and max values for x and y of the hobby-curve.
With the tip \usetikzlibrary{bbox} in the comments we can create a correct bounding box

\begin{scope}[bezier bounding box,
local bounding box=foobox]

and then:

enter image description here

\documentclass[tikz, margin=3mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections}
\usetikzlibrary{hobby}
\usetikzlibrary{bbox}% <----- needed for correct bounding box!
\usepackage{amsmath}

\begin{document}
\begin{tikzpicture}[
>=latex, 
]
%  Coordinates (better to highlight them at the end)
\coordinate (r1-1) at (4,5);
\coordinate (r1-2) at (1,4);
\coordinate (r1-3) at (1,6);
\coordinate (r1-4) at (3,9);
\coordinate (r1-5) at (7,10);
\coordinate (r1-6) at (8,7);

% Find correct bounding box for curve =========
\begin{scope}[bezier bounding box,% <---- ! --- here locally, else globally
local bounding box=foobox, 
]
\path[] (r1-1) to [closed, curve through = {(r1-2) (r1-3) (r1-4) (r1-5) (r1-6)}] (r1-1);
\end{scope}

% Draw 'bezier bounding box' with some shifting-tricks =========
% for latter intersections  (I don't know, why this is needed)
\draw[help lines, dashed, blue, name path=mybox,
] (foobox.south west) coordinate(A)
-- (foobox.south east) coordinate(B)
  -- ([xshift=-0.001pt]foobox.north east) coordinate(C)
     -- ([yshift=-0.001pt]foobox.north west) coordinate(D) 
           node[font=\ttfamily, anchor=north west]{bezier bounding box}
          --cycle;    

% Draw curve
\draw[gray, fill=lightgray, thick, name path=mycurve] (r1-1) to [closed, curve through = {(r1-2) (r1-3) (r1-4) (r1-5) (r1-6)}] (r1-1);

% Evaluate intersection between curve and box =========
\path[name intersections={of=mycurve and mybox, name=S, total=\t}];

% Extrema Points
\foreach \N/\Pos in {1/above, 2/right, 3/below, 4/left}{%%
\coordinate(S\N) at (S-\N);
\fill[red] (S-\N) circle[radius=1.5pt] node[\Pos]{$S_\N$}; 
}%%

% Desired measure arrow y
\draw[thin, orange, shorten >=-3mm] (S1) -- ([xshift=9mm]B) coordinate[
label=below:$y_{\min}$](Ymin);
%
\draw[thin, orange, shorten >=-3mm] (S3) -- ([xshift=9mm]C) coordinate[
label=above:$y_{\max}$](Ymax);
%
\draw[<->, orange] (Ymin) -- (Ymax) node[midway, fill=white]{$d_y(S_1,S_3)$};

% Desired measure arrow x
\draw[thin, orange, shorten >=-3mm] (S2) -- ([yshift=-7mm]A) coordinate[
label=left:$x_{\min}$](Xmin);
%
\draw[thin, orange, shorten >=-3mm] (S4) -- ([yshift=-7mm]B) coordinate[
label=right:$x_{\max}$](Xmax);
%
\draw[<->, orange] (Xmin) -- (Xmax) node[midway, fill=white]{$d_x(S_2,S_4)$};

%% Highlight original coordinates
\foreach \N in {1,...,6}{%%
\fill[] (r1-\N) circle[radius=1pt];
}%%
\end{tikzpicture}
\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .