Learn MetaPost

Equations

Equality operator

MetaPost differs from many other programming languages in that it does not use = to assign values to variables, for that we use the assignment operator :=. The equality operator = is used, just as it is in mathematics, to declare that the right and left hand sides are equal. Whenever we write = we are specifying equations, and MetaPost will try to resolve them by assigning values to unknown variables to make the equality statement true. This difference can seem subtle at first: If we write an unassigned variable left of = the solution is simply to assign the value on the right, and thus = works just like := would. However, having a build in equation solver radically changes how programs are written and its a very powerful component of the MetaPost programming language.

Let's start with an example. Recall that z1 is shorthand for (x1,y1) and try to run this file:

z1 = (1,1);
x1 = 2;

You will get an Inconsistent equation error, since MetaPost cannot resolve 1=2.

1 does not equal 2.

Now try this file instead:

z1 = (1,1);
x1 = a*2;
show a;
end

Here the equation has a solution and MetaPost correctly solves the equation to find the value of the numeric a. We can use show to make MetaPost print the value in its output.

Using show to expect the value of a.

Drawing a triangle

The build-in equation solver is very useful for drawing figures of mathematical concepts without having to assign absolute positions. Consider a simple right triangle with points A,B,C and a right angle at C. If we didn't have an equation solver at hand we might specify something like:

pair A,B,C;
a = b = 1;
C = Origin;
A = C - (a,0);
B = C + (0,b);

However, the right triangle as a concept is not really attached to details like the length of the legs or it's position in the coordinate system. So we can skip those details and just write:

numeric a,b;
pair A,B,C;
A = C - (a,0);
B = C + (0,b);

To actually draw the triangle we would still need to fill in the blanks of course, but when and how is entirely up to us. We might fix a, b, and C as in the first example, but we are also free to instead fix say A,B and C, or A,B and a,b or any other way. Whichever way we choose the remaining variables will be assigned by the equation solver and can be used elsewhere.

By delaying these decision we can easily build complex figures that adapt to changes with minimal work.

For example the only difference in the code for the left and right figure below is whether G=C or G=A.

Moving a well defined triangle

beginfig(1);
  numeric a,b;
  pair A,B,C,G;
  A = C - (a,0);
  B = C + (0,b);
  G = point 4 of fullcircle scaled 2cm;
  a=b=2cm;
  G=A; %Or G=C
  draw A--B--C--cycle;
  draw fullcircle scaled 2cm;
  dotlabel.llft("G",G);
endfig

Intersection

Lets draw a simple X.

X marks the spot.

beginfig(1);
  z1=-z2=(0.58,1);
  z3=-z4=(-.89,1);
  draw z1--z2;
  draw z3--z4;
endfig

Make sure you understand how equality and negation was used to define the four corners of the cross.

Now lets say we wanted to draw a horizontal line from the point of intersection. We could of course calculate where the two lines meet, but MetaPost will do it for us. The syntax fraction[pair1,pair2] denotes the point a fraction along the line from pair1 to pair2. Using this syntax we specify a point I thats is at some fraction of both lines in the cross, the only solution being the intersection point.

numeric a,b;
pair I;
I = a[z1,z2] = b[z3,z4];

Since we won't need the values of a,b going forward we can avoid declaring them by using the special name whatever to stand in for an anonymous variable.

pair I;
I = whatever[z1,z2] = whatever[z3,z4];

Line from intersection.

If we want to find intersections between more complicated not-straight paths we can use the (path) intersectionpoint (path) command. The full code for the crosses above uses a few extra tricks including the dotlabel command and a variable for scaling (u).

outputformat := "png";
outputtemplate := "%j_%c.png";
hppp := 0.25;
vppp := 0.25;
u:=1cm;
pair a,b,c,d;
a=-b=(0.58u,1u);
c=-d=(-.89u,1u);


beginfig(1);
  draw a--b;
  draw c--d;
endfig;

beginfig(2);
  draw a--b;
  draw c--d;
  
  pair I;
  I = whatever[a,b] = whatever[c,d];

  draw I--(I+(1u,0));
  dotlabel.top("I",I);
endfig
end

Exercise

Use what you have seen to draw the following figure:

Multiple intersections.

To draw labeled dots you can use the dotlabel command along with the controls |lft|rt|top|bot|ulft|urt|llft|lrt to set the relative location of a label like so:

dotlabel.rt("label",pair)

Hint

beginfig(3)
  z1=(0,-0.5u);
  z2=(-0.5u,1u);
  z3=(-1u,0.2u);
  z4=(1u,0.2u);
  z5=z3+(0,0.5u);
  z6=z4+(0,0.5u);
  draw z1--z2;
  draw z3--z4;
  draw z5--z6;

  pair a,b;
  a = whatever[z1,z2]=whatever[z3,z4];
  b = whatever[z1,z2]=whatever[z5,z6];

  dotlabel.urt("a",a);
  dotlabel.urt("b",b);
endfig
Last updated on 1 Apr 2021
Published on 1 Apr 2021