92

I want to reproduce the following diagram in Tikz.

enter image description here

LaTeX code of my MWE is below:

\documentclass[tikz]{standalone}
\usepackage{pgfplots}

\begin{document}

\begin{tikzpicture}

\draw [-latex, thick] (0, 0) -- (0, 6) node  [above] {\Large{$e$}};
\draw [-latex, thick] (0, 3) -- (6, 3) node  [right] {\Large{$\widehat{Y}$}};

\draw [thick] (0, 0.5) -- (5, 4);
\draw [thick] (0, 1.75) -- (5, 5.25);

\end{tikzpicture}

\end{document}

enter image description here

I wonder how to fill specified area by dotted points. Any help will be highly appreciated. Thanks

Edited

How about this one?

enter image description here

MYaseen208
  • 8,587
  • What's the equation for the curves in the second diagram? – Jake Nov 20 '13 at 10:36
  • In your comment to my answer you mention "quadratic curves", but the screenshot seems to show circular segments. Could you clarify what kinds of curves you need? Do you have the equations for the curves you want? – Jake Nov 20 '13 at 12:15
  • Equations of these graphs are not available. I'm not interested in any specific form, the only requirement is graph of second degree equation. – MYaseen208 Nov 20 '13 at 12:41

7 Answers7

136

To fill the area between two curves with random points, you can plot the average of the two functions and jitter the markers using random noise with a range that's equal to the distance between the curves.

To demonstrate, here's what the plot for the random dots looks like without the noise component:

In this case, the distance between the two curves is constant, so the noise can be generated using a uniform distribution centered halfway between the curves with a range equal to the offset between the curves:

To get a denser pattern, simply increase the number of samples:


The same approach works for more complicated functions:


However, things get more complicated when you have two curves that aren't a constant distant apart: In that case, the density of the random dots will not be constant throughout the domain:

To achieve a constant density, you'll need to adjust the horizontal distribution of the random points. So instead of the points being a noisy function of x, we'll use a parametric equation ( x(t), 0.5*a(x(t)) + 0.5*b(x(t)) + U(a(x(t)), b(x(t))).

The horizontal distance between two adjacent points has to be inversely proportional to the distance between the two bounding curves (since you need more points to fill a wider band).

To find the horizontal positions for the points that fulfill that requirements, you can use the antiderivative of the inverse of the difference of the two bounding curves.

In this case, for example, you would need the antiderivative of f(x) = 1/(a(x)-b(x)) = 1 / (2*(x/4)^2+1). I think I should be able to do this by hand, but I cheated and used Wolfram Alpha. The required function is F(x) = 2 sqrt(2) (atan(x/(2 sqrt(2)))). In order to stretch this function over the plot domain, you can normalize it using the factor x_max/F(x_max). The function for the horizontal positions of the points is thus

F*(t) = 2 sqrt(2) (atan(t/(2*sqrt(2)))) * 5 / 2.99

Using this to plot the random dots without the noise component, we get:

Adding the noise:


Code for the two parallel lines:

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=0.75*\x-2;},
    declare function={b(\x)=0.75*\x-1;}
]
\begin{axis}[
    domain=0:5,
    axis lines=middle,
    axis equal image,
    xtick=\empty, ytick=\empty,
    enlargelimits=true,
    clip mode=individual, clip=false
]
\addplot [red, only marks, mark=*, samples=300, mark size=0.75]
    {0.5*(a(x)+b(x)) + 0.5*rand*(a(x)-b(x))};
\addplot [thick] {a(x)};
\addplot [thick] {b(x)};
\end{axis}
\end{tikzpicture}

\end{document}

Code for the parabolas:

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=-(0.5*\x-1.5)^2+1;},
    declare function={b(\x)=-(0.5*\x-1.5)^2;},
]
\begin{axis}[
    domain=1:5,
    axis lines=middle,
    axis equal image,
    xtick=\empty, ytick=\empty,
    enlargelimits=true,
    clip mode=individual, clip=false
]
\addplot [red, only marks, mark=*, samples=300, mark size=0.75]
    {0.5*(a(x)+b(x)) + 0.5*rand*(a(x)-b(x))};
\addplot [thick] {a(x)};
\addplot [thick] {b(x)};
\end{axis}
\end{tikzpicture}

\end{document}

Code for the funnel:

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=(\x/4)^2;},
    declare function={b(\x)=-(\x/4)^2-1;},
    declare function={f(\x) = 2*sqrt(2)*rad(atan(\x/(2*sqrt(2))))*5/2.99;}
]
\begin{axis}[
    domain=0:5, xmin=0,
    axis lines=middle,
    axis equal image,
    xtick=\empty, ytick=\empty,
    enlargelimits=true,
    clip mode=individual, clip=false
]
\addplot [red, only marks, mark=*, samples=500, mark size=0.75]
    (f(x), {0.5*(a(x)+b(x)) + rand * ( a(f(x)) - b(f(x))) / 2});
\addplot [thick] {a(x)};
\addplot [thick] {b(x)};
\end{axis}
\end{tikzpicture}

\end{document}

Original answer:

You can use a plot to draw random dots that lie inside the band:

\documentclass[tikz]{standalone}
\usepackage{pgfplots}

\begin{document}

\begin{tikzpicture}

\draw [-latex, thick] (0, 0) -- (0, 6) node  [above] {\Large{$e$}};
\draw [-latex, thick] (0, 3) -- (6, 3) node  [right] {\Large{$\widehat{Y}$}};

\draw [thick] (0, 0.5) -- (5, 4);
\draw [thick] (0, 1.75) -- (5, 5.25);

\draw plot [only marks, mark=*, mark size=0.5, domain=0:5, samples=700] (\x,{rnd*1.25+3.5/5*\x+0.5});

\end{tikzpicture}

\end{document}
Jake
  • 232,450
  • 9
    If I could I would vote +2 for this very smart solution! – Benedikt Bauer Nov 20 '13 at 10:27
  • 2
    +1 for elegant solution. Can I use the same command to get random dots between two quadratic curves? – MYaseen208 Nov 20 '13 at 10:31
  • @MYaseen208: Sure, but with more complicated bounds, you might need to filter out points in order to get an even distribution of points. – Jake Nov 20 '13 at 10:32
  • Thanks @Jake for your help. I've added a diagram which had two quadratic curves. I'd highly appreciate if you give some hint to fill the specified area between these two quadratic curves. Thanks – MYaseen208 Nov 20 '13 at 10:37
  • @Jake: Your provided code solve my this problem. Thanks for all your help. I'm struggling to get the meaning of your code. Could you shed light on explaining your provided code? Actually I've to reproduce two other graphs. One of them is funnel shaped and I've to fill that funnel shaped with random dots. I hope your explanation could guide me to resolve that problem too. Thanks – MYaseen208 Nov 20 '13 at 12:45
  • The original image (in the question) has a strange distribution of points. They don't cluster. I think they are drawn by hand, and the illustrator kept the points from becoming too close to one another. That could get complicated. Reminds me of the potential energy in an atom, which keeps them apart if they get too close, but pulls them together too. – Benjamin McKay Nov 20 '13 at 13:26
  • @MYaseen208: The slope of both curves is 3.5/5, and the vertical distance between them is 1.25. Thus, (since rnd returns a number in the interval [0,1]) you get a function that for each x in the domain [0,5] a random point between the two lines. – Dror Nov 20 '13 at 14:09
  • Thanks @Dror for your explanation. What about if we have two lines with different slopes and intercepts? – MYaseen208 Nov 20 '13 at 14:26
  • @MYaseen208: I believe this method works fine as long as the two curves are graphs and have the same domain. Then, you have to parameterize each vertical curve between the two curves (think of compute an area using integration). – Dror Nov 20 '13 at 14:27
  • There are no empty patches on the image in the question either. The regularity of the dot spacings is not due to uniform distribution. When you draw dots by hand, you don't cluster the dots or leave ``visually empty'' spots. Tricky. – Benjamin McKay Nov 20 '13 at 16:16
  • @BenjaminMcKay: You're right, the distribution isn't nearly as pretty as the presumably hand-generated one in the OP's example. I've added a new answer that uses Poisson disc sampling that looks much nicer, but requires an external tool for generating the points. – Jake Nov 20 '13 at 16:50
  • Very sophisticated! I added also this one to the PGFplots example gallery - and more of your great plotting codes. If you would like to show further plots made by you, let me know - this would be great! Also, if you sometimes might think about a guest blog post on http://pgfplots.net, to share some pgfplots tricks, I would be glad. – Stefan Kottwitz Mar 17 '14 at 12:06
  • Is there any way to modify the original answer (using \draw plot approach) to support changing the color of the dots to red? Or can that only be done using the newer \addplot approach? – EthanAlvaree Mar 04 '23 at 01:31
  • Nevermind, I realized putting mark options={draw=blue, fill=blue} would do the trick. – EthanAlvaree Mar 04 '23 at 02:07
68

One way to get random points that are distributed in a visually appealing manner is to use Poisson disc sampling. This technique is used in cartography to generate patterns for representing sand, for example.

The method isn't particularly complicated, but too computationally demanding to be implemented directly in LaTeX (I'd be happy to be proven wrong on this one). What you can do is to generate the points using an external tool, and then filter and plot them using PGFPlots.

Here's an example of how to do this using the Python code from http://code-spot.co.za/2010/04/07/poisson-disk-sampling-example-code/.

Generate and save the points covering the required domain and range:

import poisson_disk
import numpy as np
points = np.array(poisson_disk.sample_poisson_uniform(5, 5, 0.1, 20))
np.savetxt('poissonpoints.txt', points)

Then filter and plot the points in PGFPlots. For this, you can use the expression

y filter/.code={
    \pgfmathparse{\pgfmathresult / and(\pgfmathresult<a(x), \pgfmathresult>b(x))}
}

which makes use of the fact that the conditional in the divisor evaluates to zero when a point is outside the bounding curves, resulting in a division by zero which causes PGFPlots to discard the point.

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=(\x/4)^2;},
    declare function={b(\x)=-(\x/4)^2-1;},
]
\begin{axis}[
    domain=0:5, xmin=0,
    axis lines=middle,
    axis equal image,
    xtick=\empty, ytick=\empty,
    enlargelimits=true,
    clip mode=individual, clip=false
]

\addplot [thick] {a(x)};
\addplot [thick] {b(x)};
\addplot [only marks, draw=none, mark size=0.5,
    y filter/.code={
        \pgfmathparse{\pgfmathresult/and(\pgfmathresult<a(x), \pgfmathresult>b(x))}
    }
] table [y expr=\thisrowno{1}-3] {poissonpoints.txt};
\end{axis}
\end{tikzpicture}

\end{document}
Hotschke
  • 5,300
  • 5
  • 33
  • 63
Jake
  • 232,450
32

A second solution, based on How to draw a rectangle filled with random size circular pattern? it works by clipping the area of the plot.

Dots' aspect and number can be customized through some keys.

\documentclass[tikz]{standalone}
\usepackage{tikz}

\newif\ifpointsamewidth
\pgfkeys{/tikz/.cd,
     height rect/.store in=\height,
     height rect=10,
     width rect/.store in=\width,
     width rect=10,
     num points/.store in=\numpoints,
     num points=100,
     point width/.store in=\maxpointwidth,
     point width=1.5pt,
     use points equal width/.is if=pointsamewidth,
     use points equal width=false,
     point style type/.is choice,
     point style type/fill/.style={fill=\pointcolor},
     point style type/radial shade/.style={inner color=\pointinnercolor,outer color=\pointoutercolor},
     point style type/vertical shade/.style={top color=\pointtopcolor,bottom color=\pointbottomcolor},
     point style type/horizontal shade/.style={left color=\pointleftcolor,right color=\pointrightcolor},
     point style/.store in=\pointstyle,
     point style={/tikz/point style type/fill},     
     point fill color/.store in=\pointcolor,
     point fill color=blue!30,
     point inner color/.store in=\pointinnercolor,
     point inner color=white,
     point outer color/.store in=\pointoutercolor,
     point outer color=blue!30,
     point top color/.store in=\pointtopcolor,
     point top color=white,
     point bottom color/.store in=\pointbottomcolor,
     point bottom color=blue!30,
     point left color/.store in=\pointleftcolor,
     point left color=blue!30,
     point right color/.store in=\pointrightcolor,
     point right color=white,
}

\pgfkeys{/tikz/random point diagram/.code={
   \path (0,0) rectangle (\width,\height);

   \foreach \point in {1,...,\numpoints}{
     \pgfmathparse{random()}
     \pgfmathsetmacro\xpos{\width*\pgfmathresult}
     \pgfmathparse{random()}
     \pgfmathsetmacro\ypos{\height*\pgfmathresult}

     \ifpointsamewidth%
       \node[circle,inner sep=\maxpointwidth, \pointstyle] (point-\point) at (\xpos,\ypos) {};
       \else%
       \pgfmathparse{random()}
       \pgfmathsetmacro\pointwidth{\maxpointwidth*\pgfmathresult}

       \node[circle,inner sep=\pointwidth pt, \pointstyle] (point-\point) at (\xpos,\ypos) {};
     \fi
   }
  }
}

% that's just an alias for \node
\makeatletter
\def\drawdiagram{\tikz@path@overlay{node}}
\makeatother

\begin{document}

\begin{tikzpicture}

\draw [-latex, thick] (0, 0) -- (0, 6) node  [above] {\Large{$e$}};
\draw [-latex, thick] (0, 3) -- (6, 3) node  [right] {\Large{$\widehat{Y}$}};

\draw [thick] (0, 0.5) -- (5, 4);
\draw [thick] (0, 1.75) -- (5, 5.25);

\clip (5, 4) -- (0, 0.5) -- (0, 1.75) -- (5, 5.25) --cycle; 

\drawdiagram[
 num points=600,
 point fill color=black,
 random point diagram] at (0, 1.75){};
\end{tikzpicture}

\end{document}

The result:

enter image description here

The previous solution has dots with random width. A new key has been introduced to get rid of this behaviour: use points equal width. The second picture adopts this feature:

\begin{tikzpicture}

\draw [-latex, thick] (0, 0) -- (0, 6) node  [above] {\Large{$e$}};
\draw [-latex, thick] (0, 3) -- (6, 3) node  [right] {\Large{$\widehat{Y}$}};

\draw [thick] (0.5, 2.25) arc (160:20:2.5cm);
\draw [thick] (0.5, 1.25) arc (160:20:2.5cm);

\clip (0.5, 2.25) arc (160:20:2.5cm)--++(0,-1) arc(20:160:2.5cm); 

\drawdiagram[
 num points=600,
 use points equal width,
 point width=0.75pt,
 point fill color=black,
 random point diagram] at (0.5, 1.25){};
\end{tikzpicture}

Same concept as before: draw your graph then clip it. Starting the random point diagram in the lower angle ensures to have the random dots within the plot (if the plot exceeds a 10 x 10 grid, change height and width rect.

The result:

enter image description here

32

Just for fun with PSTricks.

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pstricks-add}

\def\x(#1){sin(#1)^3}
\def\y(#1){(13*cos(#1)-5*cos(2*#1)-2*cos(3*#1)-cos(4*#1))/16}

\psset{algebraic,plotpoints=100,linejoin=1}
\pstVerb{realtime srand}

\begin{document}
\psLoop{10}{%
\begin{pspicture}(-3,-3)(3,2)
    \psRandom[color](-3,-3)(3,3){\psparametricplot[unit=3]{0}{TwoPi}{\x(t)|\y(t)}}
    \psparametricplot[unit=3]{0}{TwoPi}{\x(t)|\y(t)}
\end{pspicture}}
\end{document}

enter image description here

  • Holy moly! It's rare to be introduced to a new useful TeX technique like this. TNX. – jlettvin Apr 13 '20 at 17:27
  • I tried it with xelatex to get a pdf with one of these images per page. How do you get animation like this? Please provide the commands and options. – jlettvin Apr 13 '20 at 17:33
  • Okay, for my file pstrix.pdf, the trick I used is to update imagemagick to version 7.0 and then run the command $ convert -delay 50 -loop 0 -density 300 -scale 300 -alpha remove pstrix.pdf pstrix.gif – jlettvin Apr 13 '20 at 17:52
15

With the help of very nice people here and generous efforts and help of @Jake, I came with the following solution.

Code

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=0.75*\x-2.5;},
    declare function={b(\x)=0.75*\x-1;}
]
\begin{axis}[
    domain=0:5,
    axis lines=middle,
    axis equal image,
    xtick=\empty, 
    ytick=\empty,
    enlargelimits=true,
    clip mode=individual, 
    clip=false,
    xlabel={\Large{$\widehat{Y}$}}, xlabel style={at=(xticklabel cs:1.05), anchor=north west},
    ylabel={\Large{$e$}}, ylabel style={at=(yticklabel cs:1), anchor=south east},
]
\addplot [red, only marks, mark=*, samples=300, mark size=0.75]
    {0.5*(a(x)+b(x)) + 0.5*rand*(a(x)-b(x))};


\addplot [thick] {a(x)};
\addplot [thick] {b(x)};

\end{axis}
\end{tikzpicture}

\end{document}

Output

enter image description here



Code

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=0.01*\x-1.25;},
    declare function={b(\x)=0.01*\x+1.25;}
]
\begin{axis}[
    domain=0:5,
    xmin=0,
    ymin=-2,
    xmax=5,
    ymax=2,
    axis lines=middle,
    axis equal image,
    xtick=\empty, 
    ytick=\empty,
    enlargelimits=true,
    clip mode=individual, 
    clip=false,
    xlabel={\Large{$\widehat{Y}$}}, xlabel style={at=(xticklabel cs:1.05), anchor=north west},
    ylabel={\Large{$e$}}, ylabel style={at=(yticklabel cs:1), anchor=south east},
]
\addplot [red, only marks, mark=*, samples=400, mark size=0.75]
    {0.5*(a(x)+b(x)) + 0.5*rand*(a(x)-b(x))};


\addplot [thick] {a(x)};
\addplot [thick] {b(x)};

\end{axis}
\end{tikzpicture}

\end{document}

Output

enter image description here



Code

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=(\x/3)^2+1;},
    declare function={b(\x)=-(\x/3)^2-1;},
    declare function={f(\x) = 2*sqrt(2)*rad(atan(\x/(2*sqrt(2))))*5/2.99;}
]
\begin{axis}[
    domain=0:5,
    xmin=0,
    ymin=-3.5,
    xmax=5,
    ymax=3.5,
    axis lines=middle,
    axis equal image,
    xtick=\empty, 
    ytick=\empty,
    enlargelimits=true,
    clip mode=individual, 
    clip=false,
    xlabel={\Large{$\widehat{Y}$}}, xlabel style={at=(xticklabel cs:1.05), anchor=north west},
    ylabel={\Large{$e$}}, ylabel style={at=(yticklabel cs:1), anchor=south east},
]
\addplot [red, only marks, mark=*, samples=500, mark size=0.75]
    (f(x), {0.5*(a(x)+b(x)) + rand * ( a(f(x)) - b(f(x))) / 2});
\addplot [thick] {a(x)};
\addplot [thick] {b(x)};
\end{axis}
\end{tikzpicture}

\end{document}

Output

enter image description here



Code

\documentclass[tikz, border=5mm]{standalone}
\usepackage{pgfplots}
\usepackage{amsmath}

\begin{document}

\begin{tikzpicture}[
    declare function={a(\x)=-(0.5*\x-1.5)^2+1;},
    declare function={b(\x)=-(0.5*\x-1.5)^2;},
]
\begin{axis}[
    domain=1:5,
    xmin=0,
    ymin=-1.5,
    xmax=5,
    ymax=1.5,
    axis lines=middle,
    axis equal image,
    xtick=\empty, 
    ytick=\empty,
    enlargelimits=true,
    clip mode=individual, 
    clip=false,
    xlabel={\Large{$\widehat{Y}$}}, xlabel style={at=(xticklabel cs:1.05), anchor=north west},
    ylabel={\Large{$e$}}, ylabel style={at=(yticklabel cs:1), anchor=south east},
]
\addplot [red, only marks, mark=*, samples=300, mark size=0.75]
    {0.5*(a(x)+b(x)) + 0.5*rand*(a(x)-b(x))};
\addplot [thick] {a(x)};
\addplot [thick] {b(x)};
\end{axis}
\end{tikzpicture}

\end{document}

Output

enter image description here



MYaseen208
  • 8,587
14

enter image description here

\documentclass[tikz,margin=3pt]{standalone}
\usetikzlibrary{patterns.images}

\usepackage{pgfplots}
\pgfplotsset{compat=1.10}
\usepgfplotslibrary{fillbetween}
\usetikzlibrary{intersections}

\pgfSetupImageAsPattern{Rnd-Patt-20}{Rnd-Patt-20.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-40}{Rnd-Patt-40.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-50}{Rnd-Patt-50.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-60}{Rnd-Patt-60.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-80}{Rnd-Patt-80.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-100}{Rnd-Patt-100.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-250}{Rnd-Patt-250.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-500}{Rnd-Patt-500.jpg}
\pgfSetupImageAsPattern{Rnd-Patt-1000}{Rnd-Patt-1000.jpg}

\tikzset{rand pattern/.style args={#1}{%
    use image as pattern=Rnd-Patt-#1, image as pattern}}    

\begin{document}

\begin{tikzpicture}[%
    declare function={a(\x)=(\x/4)^2;},
    declare function={b(\x)=-(\x/4)^2-1;},
]
\begin{axis}[
    domain=0:5, xmin=0,
    axis lines=middle,
    axis equal image,
    xtick=\empty, ytick=\empty,
    enlargelimits=true,
    clip mode=individual, clip=false
]

\addplot [ name path =A] {a(x)};
\addplot [ name path =B] {b(x)};
\addplot[rand pattern=1000,] fill between[of=A and B];
\end{axis}
\end{tikzpicture}
\end{document}

For details see this answer.

Tarass
  • 16,912
3

The wheelchart package, which I wrote, can be used.

The dots are obtained with the code given to the key discrete pic.

The number of dots is 100 and is determined by the first variable.

enter image description here

\documentclass[border=6pt]{standalone}
\usepackage{wheelchart}
\begin{document}
\begin{tikzpicture}
\wheelchart[
  data=,
  discrete,
  discrete pic={\fill ({0.1*rand},{0.1*rand}) circle[radius=2pt];},
  discrete space at borders=true,
  start angle=135,
  total angle=90
]{100/red}
\draw (135:1.9) arc[start angle=135,end angle=45,radius=1.9];
\draw (135:3.1) arc[start angle=135,end angle=45,radius=3.1];
\end{tikzpicture}
\end{document}
matexmatics
  • 4,819