Looking to emulate the following sinusoidal wave having random variations in both its filled and outline thickness:

Wavy Line

The wave has two parts: a variably thick filling and a variably thick outline. The MetaPost function function allows creating paths from function calls, such as sin. The initial curve can be created using something like:

path wave;
wave := curved function( 1, "sin(x)", "x", 1, 10, 1 ) xyscaled( 1cm, 1cm );

This produces a fairly close result:

Example Result


I have tried to make a variably thick pen:

  pen variably_thick;
  variably_thick := pencircle yscaled uniformdeviate( 1mm ) rotated 10;

Yet that sets the initial pen thickness -- there are no callbacks to increase or decrease the pen's thickness as the path is drawn.


A working example:



% Randomize the seed without having to delete the tuc file.
\ctxlua{math.randomseed( os.time() )}

% Draws three waves, somewhat evenly spaced, with two inflection points
% per line.
  color base_colour;
  base_colour := \MPcolor{BaseColour};

  % The wavy shape used for creating the wave and its border.
  path wave;

  % The wave "path"
  path underwave;

  % The wave "fill"
  path overwave;

  % Create the base path
  wave := curved function( 1, "sin(x)", "x", 1, 10, 1 ) xyscaled( 1cm, 1cm );

  % Draw the "path" for the wave (the outside edges)
  underwave := wave rotated 90 xscaled 5 yscaled .45;
  draw underwave withpen pencircle scaled 1.25cm withcolor (.7[base_colour,white]);

  % "Fill" the wave.
  overwave := wave rotated 90 xscaled 5 yscaled .5;
  draw overwave withpen pencircle scaled 1cm withcolor base_colour;


  \input knuth


How would you create a curve in MetaPost that produces a variably thick line?


From the MetaPost examples:

  path p;
  p =
  for i=.1 step .1 until 10:
    hide( pair A; A = (i*u, (sind (i*180/3.14))/i *u);
          draw A withpen pencircle scaled 2pt )
    .. A
  draw p;

It should be possible to use the code above (i.e., loop over a sine wave) to draw the curve while varying the pen's thickness throughout the loop, but I was hoping there was cleaner solution.


2 Answers 2


While not an answer the question of variably thick curves, another approach to emulate the desired outcome is to think of the curves as simply two offset sine waves, connected at each end:

  color base_colour;
  base_colour := \MPcolor{BaseColour};

  deviation_dark := uniformdeviate( .025 );
  def dark_colour = deviation_dark [base_colour, black] enddef;

  wave_height := 1.5mm;

  path top_wave;
  top_wave := (0, 0);

  wave_resolution := 20;

  for x = 0 step (1 / wave_resolution) until 2:
    y := sin( x * pi );
    top_wave := top_wave .. (x, y);

  % Duplicate the top wave, but move it up a random amount.
  path bottom_wave;
  bottom_wave := top_wave shifted( 0, wave_height );

  % Offset the waves.
  top_wave := top_wave xyscaled (5.5cm, 5mm);
  bottom_wave := bottom_wave xyscaled (6.5cm, 4.5mm);

  % Create a path for connecting the waves.
  path wave_right_side;
  wave_right_side :=
    (point length(top_wave) of top_wave) --
    (point length(bottom_wave) of bottom_wave);

  % Connect the waves.
  path wave;
  wave := top_wave & wave_right_side & reverse bottom_wave -- cycle;

  % Stretch the wave to extend beyond the page boundaries.
  wave := wave xscaled( 2.5 );

  draw wave withpen pencircle scaled 3mm withcolor .7[base_colour,white];
  fill wave withcolor dark_colour;

This produces a reasonable facsimile:

Double Sine Wave


If there's no obvious way of doing it easily, it's almost always possible to implement missing features. Solution to this problem i came up with was basically: to write a function that generates an offset path to a given path at distance, that is a function of a time on a given path; to generate two such paths at both sides of a given path; connect them; fill the resulting shape. The code (which is a part of a library and, unfortunately, quite long and messy to cite here) you can find here. Once you include this library (input fiziko.mp), you can write code like this:

path p;
p := (0,0){dir(30)}..(5cm, 0)..{dir(30)}(10cm, 0);
draw brush (p)(1cm*sin(offsetPathLength*pi)) withcolor (0.75, 0.75, 1);
draw brush (p)(0.6cm*sin(offsetPathLength*pi)) withcolor (0.5, 0.5, 1);

where function "brush", which actually draws variable width line, has two arguments: first is a path; and second is some arbitrary function of either "offsetPathLength", which is an arclength on a path, or "offsetPathTime", which is a time on a path; and get picture like this: enter image description here

To get more detail you might need to subdivide original path. It seems to work with ConTeXt, but might require some tweaking.

You must log in to answer this question.

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