%%% File: grafbase.mf
%%% A part of mfpic 0.6b 2003/01/02
%%%
string fileversion, filedate;
fileversion:="0.6 beta"; filedate:="2003/01/02";

message "MFpic version " & fileversion & ", " & filedate;
message "";

% ensure that Plain Metafont is loaded.
if unknown base_name:
 input plain;
elseif not string base_name:
 input plain;
elseif base_name <> "plain":
 input plain;
fi

boolean METAFONT,METAPOST;

if known blue:
  message "You are inputting to MetaPOST a file designed for MetaFONT.";
  message "Metapost should be using grafbase.mp not this file (grafbase.mf).";
  message "Make sure Metapost can find grafbase.mp.";
  endinput;
fi
METAFONT:=true;
METAPOST:=false;

% intercept the mode variable, before mode_setup can set proof mode.
if unknown mode:
  errhelp ("Please use \mode=localfont; or a mode known on your system.");
  errmessage ("Unknown METAFONT mode");
fi

mode_setup;

% Don't complain when variables get too large.
% (This has to be after mode_setup, since that sets warningcheck:=1)
interim warningcheck := 0;


% Font identifier, and font coding scheme.
% NB:  These become ALL-CAPITALS in the PL (and TFM) files.

font_identifier := "MFpic graphics";
font_coding_scheme := "Arbitrary";

% Design size: this is somewhat arbitrary, but it needs to be large.

interim designsize := 128pt#;

%% Global Variables.

if not boolean debug:  boolean debug;   fi
if not known debug:    debug := false;  fi

if debug:
  message "DEBUG:  pixels_per_inch = " & decimal pixels_per_inch;
fi

% when the boolean variable `grafbase' is known,
% then grafbase.mf has been input.
% (idea taken from DEK's cmbase.mf)

if known grafbase:
  errhelp ("Please make sure that grafbase.mf is loaded only once.");
  errmessage ("You have loaded grafbase.mf more than once");
else:
  boolean grafbase;
  grafbase := true;
fi

% METAFONT title string t, and message t.

vardef mftitle expr t =
  t;
  message t;
enddef;

% character size variables, and their default values.

numeric unitlen, xscale, yscale;
numeric xneg, xpos, yneg, ypos;

unitlen := 1 pt#;
xscale := 7.227;  % (xscale * unitlen) = 1/10 inch
yscale := 7.227;  % (yscale * unitlen) = 1/10 inch
xneg := 0;
xpos := 10;
yneg := 0;
ypos := 10;

% degrees for angles.  In METAFONT, one degree is the unit of angle.

deg := 1;

% Support for radians: user can say
%             \sector{(0,0),1,0,pi/2*radian}
%           or \sector{(0,0),1,0,90 deg}

% First, a useful constant:
pi := 3.1415926;
numeric radian;
pi*radian = 180*deg;


% Drawing Pen.

% pen width for drawing, in _pixel_ coordinates.
newinternal penwd;
interim penwd:=0.5pt;

% pen for drawing.
pen drawpen;
% macro to resize it:
def resizedrawpen (expr s) =
  interim penwd := s;
  drawpen := pencircle scaled penwd yscaled aspect_ratio;
  pickup drawpen;
enddef;

% Hatching Pen.

% pen width for hatching, in _pixel_ coordinates.
numeric hatchwd;
hatchwd := 0.5pt;

% pen for hatching.
pen hatchpen;
hatchpen := pencircle scaled hatchwd yscaled aspect_ratio;

% Storing a Path.

def store (suffix fs) expr f =
 hide (
   if (not path f) and (not pair f) :
     errhelp ("");
     errmessage ("Second argument to `store' must be a path or pair expression");
   fi
   if not path fs:
     path fs;
   fi
   fs := f;
 )
enddef;

% Clipping.

% clipping state.
boolean ClipOn;
ClipOn := false;

% for clipping whole picture, default is no clip:
boolean clipall; clipall:=false;

% For keeping actual bounding box of the whole picture.
% Not actually implemented in Metafont. Yet.
boolean truebbox; truebbox:=false;

% clipping paths.
path ClipPath[];
numeric ClipPath;
ClipPath = 0;  % initially no clipping paths, and no candidates known.

%% Frank Michielsen's macro, slightly modified.
vardef list (suffix v) (text lst) =
    v := 0;
    for item_= lst:
      v[incr v]:= item_;
    endfor;
enddef;


%% Utility Macros.

% (Reference for map macro: Abelson & Sussman,
% _Structure & Interpretation of Computer Programs_,
% page 250.)

% Generate new text list by applying proc to each item in given
% text list.  The new list must be separated by commas, since I
% wish to use it as the list in a `for' loop; this requirement
% is difficult to reconcile with other needs.

% NOTE WELL:  Text arguments are particularly pernicious,
% because they can inadvertently include a local name
% used in the body of the macro.  Also, METAFONT then gives
% rather uninformative error messages.

def map (text proc) (text lst) =
  hide(_map_:=0;)
  for a_=lst:
    if _map_= 0:
      hide(_map_:=1;) proc (a_)
    else :
      , proc(a_)
    fi
  endfor
enddef;

% apply procedure proc to each member of array p[] with p members.

def maparr (text proc) (suffix p) =
  for i_=1 upto p:
    proc (p[i_]);
  endfor
enddef;

% convert text pairs list t to array p.
% best used inside a vardef.
% note:  t must be a comma-separated list,
% because the `for' needs it.

%HERE test "numeric" instead of "save". Calling routine should save.
% 1. Could be problems if p had a suffix, not allowed to save those.
% 2. User might for get that textpairs saved and be surprised
%    when his suffix disappeared. (No problem with internal use.)
def textpairs (suffix p) (text t) =
 numeric p; pair p[];
 list (p) (t)
enddef;

% chpair : `change pair'.
% apply macro proc (which maps numeric -> numeric)
% to each part of pair p, return resultant pair.

vardef chpair (text proc) (expr p) =
  (proc (xpart p), proc (ypart p))
enddef;

vardef floorpair (expr p) =
  chpair (floor) (p)
enddef;

vardef ceilingpair (expr p) =
  chpair (ceiling) (p)
enddef;

vardef hroundpair (expr p) =
  chpair (hround) (p)
enddef;

% Return the pair comprising the minimum x and minimum y coordinates
% of all pairs in the array p[], where p itself is a numeric count of
% the members in p[].

vardef minpair (suffix p) =
 save x_, y_; numeric x_, y_;
 x_ := xpart p1;
 y_ := ypart p1;
 for i_=2 upto p:
   x_ := min (x_, xpart p[i_]);
   y_ := min (y_, ypart p[i_]);
 endfor
 (x_,y_)
enddef;

% Similarly, but for the maximum coordinates.

vardef maxpair (suffix p) =
 save x_, y_; numeric x_, y_;
 x_ := xpart p1;
 y_ := ypart p1;
 for i_=2 upto p:
    x_ := max (x_, xpart p[i_]);
    y_ := max (y_, ypart p[i_]);
 endfor
 (x_, y_)
enddef;


%% Coordinate Conversion.
% (affine transformation)
% graph -> sharp
% xneg, xpos, yneg, ypos are in _graph_ coordinates.
% plain MF's beginchar defines the _sharp_ values charwd and charht
% as well the _pixel_ quantities w and h.
% (Reference:  _The METAFONTbook_, page 275, definition of beginchar.)
% _graph_ and _sharp_ coordinates coincide iff ztr is the identity
% transform.

transform ztr, vtr;

% currenttransform (.t_) takes care of the aspect ratio.
% (Also, I may try coordinate transformations of currenttransform.
%  Consider bcoords ... ecoords.)

def setztr =
 if debug:
  message " DEBUG ";
  message "charwd = " & decimal charwd & "pt#";
  message "charht = " & decimal charht & "pt#";
  message "w = " & decimal w_ & " pixels";
  message "h = " & decimal h_ & " pixels";
  message "xneg = " & decimal xneg;
  message "xpos = " & decimal xpos;
  message "yneg = " & decimal yneg;
  message "ypos = " & decimal ypos;
  message "xscale = " & decimal xscale;
  message "yscale = " & decimal yscale;
  message "unitlen = " & decimal unitlen & "pt#";
  message "hppp = " & decimal hppp;
  message " GUBED ";
 fi
% help keep coordinate transforms local:
 save ztr, vtr;
 transform ztr, vtr;
 vtr := identity
   xscaled (xscale*unitlen*hppp)
   yscaled (yscale*unitlen*hppp);
 ztr := identity
   shifted (-(xneg,yneg)) transformed vtr;
 if debug:
   message " DEBUG ";
   message "ztr: ";
   show ztr;
   message "vtr: ";
   show vtr;
   message " GUBED ";
 fi
enddef;

% zconv converts a variety of expressions from graph -> sharp coords.
% The expressions include pairs, paths, and transforms.
% This is an affine transform.

vardef zconv (expr a) =
  a transformed ztr
enddef;

% invzconv converts a variety of expressions from sharp -> graph coords.
% The expressions include pairs, paths, and transforms.
% This is an affine transform.

vardef invzconv (expr a) =
 a transformed (inverse ztr)
enddef;

% vconv converts a vector v from graph -> sharp coords.
% This is a linear (ie, vector) transform.

vardef vconv (expr v) =
 v transformed vtr
enddef;

% invvconv converts a vector from sharp -> graph coords.
% This is a linear (ie, vector) transform.

vardef invvconv (expr v) =
 v transformed (inverse vtr)
enddef;

%% Initial Setup.

% active_plane is the active drawing plane.
% currentpicture is unknown at this stage
% (because it's set in beginchar),
% so use a def, not a picture assignment.

def active_plane = currentpicture enddef;

vardef image(text t) =
  save active_plane;
  picture active_plane;
  active_plane := nullpicture;
  t;
  active_plane
enddef;

def initpic =
 setztr;

 % Set drawpen to standard shape and size.
 drawpen := pencircle scaled penwd yscaled aspect_ratio;
 % Sets currentpen to drawpen.
 pickup drawpen;

 if ClipOn:
   ClipPath := 1;
   % Specified character boundary.
   ClipPath[1] := rect (origin, (w_,h_));
 fi
 if debug:
   message " DEBUG ";
   message "Drawing Nominal Bounding Box Around MF Picture";
   safedraw rect (origin, (w_,h_));
   message " GUBED ";
 fi
enddef;

%% Compatibility with older graphbase.mf (for fig2dev's genmf.c)

def mfpicenv = enddef;
def endmfpicenv = enddef;

def bounds (expr a,b,c,d) =
 xneg := a;
 xpos := b;
 yneg := c;
 ypos := d;
enddef;

%% Character Wrapper.

def beginmfpic (expr ch) =
  begingroup
  gcode:=ch; % Compatability
  save h_,w_,d_;
  charcode:=if known ch: byte ch else: 0 fi;
  charwd:=(xpos-xneg)*xscale*unitlen;
  charht:=(ypos-yneg)*yscale*unitlen;
  chardp:=0;
  w_:=hround(charwd*hppp); h_:=vround(charht*hppp); d_:=vround(chardp*hppp);
  w:=w_; h:=h_; d:=d_;
  charic:=0; clearxy; clearit; clearpen;

  initpic;
enddef;

def endmfpic =
 if debug:
  message " DEBUG ";
  message "TFM charwd = " & decimal charwd & "pt#";
  message "TFM charht = " & decimal charht & "pt#";
  message " GUBED ";
 fi
 if proofing>0: makebox(proofrule); fi
 chardx:=w_;     % desired width of the character in pixels
 if clipall :
    save p_; path p_;
    p_:=rect((0,0),(w_,h_));
    clipto(active_plane) p_;
 fi
 if ClipOn :
   clipsto(active_plane, ClipPath);
 fi
 shipit;
 if displaying>0: makebox(screenrule); showit; fi
 endgroup
enddef;

% Couple of values passed from TeX to mf so paths can be fit
% to text.
pair label_adjust; label_adjust:=(0,0);
numeric label_sep; label_sep:=0;

% Define rectangle to surround some text:
%  -- dims = (wd,ht), the height and width of the text, (passed by TeX,
%     in pixel coordinates).
%  -- rad is the radius of the rounded corners in pixel coordinates
%  -- point is the center of the text bbox _in_graph_coordinates
%
% The size of path is determined as follows: If rad = 0 and
% label_sep = 0, then it is exactly the bbox of the text. Otherwise,
% the amount of label_sep is then added on all 4 sides. If rad > 0, the
% rounded corners are arranged so that the path passes through the same
% points where the corners would be without the rounding. The whole path
% is shifted by label_adjust.
%
% The path is returned in graph coordinates for use with prefix macros.

vardef textrect (expr dims, rad, point) =
  save zz, rr, ll; pair zz, ll;
  % avoid overflow: convert dimensions to graph coords early.
  zz:=invvconv(.5dims+ label_sep *(1,1));
  rr:= abs (invvconv(rad*dir 45));
  ll:=invvconv(label_adjust);
  if rr=0 :
    (rect(-zz,zz) shifted ll) shifted point
  else:
    save p,d,f,q;
    pair d,p[];  path f,q;
    d := (rr,rr)/(sqrt 2);
    p1 := zz - d;             % center of upper right arc
    p2 := p1 xscaled -1;      % upper left
    p3 := -p1;                % lower left
    p4 := p1 yscaled -1;      % lower right
    q  := quartercircle scaled 2rr;
    f  := (q shifted p1) -- (q rotated 90 shifted p2)
            -- (q rotated 180 shifted p3) -- (q rotated -90 shifted p4)
            -- cycle;
    (f shifted ll) shifted point
  fi
enddef;

% textoval is similar, except it is an ellipse.
%  -- dims and point as in textrect
%  -- mult adjusts the width to height ratio of the ellipse.
% If mult=1, the ratio is the same as the ratio of width to height of
% the text bbox, taking label_sep into consideration. If mult = 0,
% textrect is called with rad = 0. For 0 < mult < 1, the width
% of the ellipse is decreased and the height increased, and for r > 1,
% the reverse. This is arranged so that the new wd/ht is mult times the
% old. The ellipse passes through the same corner points as mentioned
% above.

vardef textoval (expr dims, mult, point) =
  if mult = 0:
    textrect (dims,0,point)
  else:
    save ww, hh, ll, zz; pair ll,zz;
    % avoid overflow: convert dimensions to graph coords early.
    zz := invvconv(.5dims+ label_sep *(1,1));
    ll := invvconv(label_adjust);
    ww := xpart(zz);
    hh := ypart(zz);
    if (ww = 0) or (hh = 0): % make a line:
      ((-zz--zz) shifted ll) shifted point
    else:
      save a,b;
      a := 1 ++ mult;
      b := a*hh/mult;
      a := ww*a;
      (ellipse(origin,a,b,0) shifted ll) shifted point
    fi
  fi
enddef;

% textellipse is similar, to textoval, except an absolute ratio of
% width to height is specified (so a circle results if rat = 1).
%  -- dims and point as in textrect
%  -- rat is the width to height ratio of the ellipse.
% The ellipse passes through the same corner points as mentioned above.

vardef textellipse (expr dims, rat, point) =
  if rat = 0:
    textrect (dims,0,point)
  else:
    save ww, hh, a, b, zz, ll; pair zz, ll;
    % avoid overflow: convert dimensions to graph coords early.
    zz:=invvconv(.5dims+ label_sep *(1,1));
    ww := xpart(zz);
    hh := ypart(zz);
    ll:=invvconv(label_adjust);
    if (ww = 0) or (hh = 0): % make a line
      ((-zz--zz) shifted ll) shifted point
    else:
      a := ww ++ (hh*rat);    % a = x-radius of ellipse
      b := a/rat;
      (ellipse(origin,a,b,0) shifted ll) shifted point
    fi
  fi
enddef;

%%%
% Extra Trigonometric Functions:
%  tand, cotd, secd, cscd, and inverses acos, asin, atan.
% Also versions which take angles in radians:
%  sin, cos, tan, cot, sec, csc, and inverses invsin, invcos, and invtan.
% Exponentials, logarithms and hyperbolic functions:
%  exp, ln, log(=ln), cosh, sinh, tanh, acosh, asinh, and atanh.
% Complex variable functions: zexp and Log (pair to pair),
%  Arg (pair to numeric), cis (numeric to pair).
%  Arg returns (and cis accepts) angle in radians.
%%%
%%HERE
% NOTE: eps/2 + epsilon is the smallest value with reciprocal less than
%       infinity. I set nottoosmall a speck bigger to ensure that
%       the same is true of 2*(nottoosmall/2).
newinternal nottoosmall; nottoosmall := eps/2 + 2epsilon;
newinternal reallysmall; reallysmall := 3epsilon; % 1/(2epsilon)=2^15 :
                                                  % arithmetic overflow
vardef secd primary X =
  save temp; temp:=cosd(X);
  if abs(temp) < reallysmall :
    errmessage "Secant too large; truncating.";
    temp := if temp<0 : - fi reallysmall;
  fi
  1/temp
enddef;

vardef tand primary X =
  sind(X)*secd(X)
enddef;

vardef cscd primary X =
  save temp; temp:=sind(X);
  if abs(temp) < reallysmall :
    errmessage "Cosecant too large; truncating.";
    temp := if temp<0 : - fi reallysmall;
  fi
  1/temp
enddef;

vardef cotd primary X =
  cosd(X)*cscd(X)
enddef;

vardef acos primary x = angle (x, 1+-+x) enddef;
vardef asin primary y = angle (1+-+y, y) enddef;
vardef atan primary z = angle (1,z) enddef;

% Trig functions with arg in radians:
vardef sin primary X = sind (X*radian) enddef;
vardef cos primary X = cosd (X*radian) enddef;
vardef tan primary X = tand (X*radian) enddef;
vardef cot primary X = cotd (X*radian) enddef;
vardef sec primary X = secd (X*radian) enddef;
vardef csc primary X = cscd (X*radian) enddef;

vardef invcos primary x = (acos (x))/radian enddef;
vardef invsin primary y = (asin (y))/radian enddef;
vardef invtan primary z = (atan (z))/radian enddef;

vardef exp primary X = (mexp (256 * X)) enddef;

% Complex exponential (Z is a pair (X,Y)):
%   returns e^X(cos Y, sin Y)
vardef cis primary T = dir(radian*T) enddef;
vardef zexp primary Z = (exp (xpart Z))*(cis(ypart Z)) enddef;

vardef ln  primary X = (mlog X) / 256 enddef;
def log = ln enddef;

% Complex logarithm (Z is a pair):
%   returns the pair (ln |Z|, Arg Z)
vardef Arg primary Z = (angle Z)/radian enddef;
vardef Log primary Z = (ln(abs(Z)), Arg (Z)) enddef;

vardef cosh primary X =
  save temp; temp:= exp(-abs(X));
  if temp < reallysmall :
    message "Cosh too large; truncating.";
    temp :=  reallysmall; % makes 1/(2temp) < infinity.
  fi
  temp/2 + 1/(2temp)
enddef;

vardef sinh primary X =
  save temp; temp:=exp(-abs(X));
  if temp <= reallysmall :
    message "Sinh to large; truncating.";
    temp :=  reallysmall;
  fi
  if X < 0 : - fi
  (1/(2temp) - temp/2)
enddef;

vardef tanh primary X =
  save temp; temp := exp(-2abs (X));
  if X < 0 : - fi (1 - temp)/(1 + temp)
enddef;

vardef acosh primary y =
  if y < 1 :
    errmessage "Undefined function acosh "&decimal y;
    errmessage "Using 0, expect more errors later";
    0
  else:
    ln (y + (y+-+1))
  fi
enddef;

vardef asinh primary y =
  ln (y + (y++1))
enddef;

vardef atanh primary y =
  if abs (y) < 1:
  (ln(1+y) - ln(1-y))/2
  else:
    errmessage "Undefined function atanh "& decimal y ;
    errmessage "Using +/-infinity, expect more errors later";
    if y < 0 : - fi infinity
  fi
enddef;

%% Coordinate Systems and Transformations.

% Coordinate Nesting.
% Make this local, by using a (global) transform stack;
% and transparent to METAFONT syntax, by using hide.
% (For a primary expression, would use gobble instead of hide.)

transform T_stack[];
T_stack := 0;

def T_push (expr T) =
  T_stack[incr T_stack] := T;
enddef;

def T_pop (suffix $) =
  if T_stack > 0:
    $ := T_stack[T_stack];
    T_stack := T_stack - 1;
  fi
enddef;

def bcoords =
  hide (T_push (ztr))
  hide (T_push (vtr))
enddef;

def ecoords =
  hide (T_pop (vtr))
  hide (T_pop (ztr))
enddef;

% Coordinate Changes.
% Example:  `apply_t (rotated theta);'
%   where Transformer is `rotated theta'.

def apply_t (text Transformer) =
  ztr := identity Transformer transformed ztr;
  vtr := ztr shifted -zconv((0,0));
enddef;

let xslant = slanted;  % (x+sy, y).

def yslant primary s =  % (x, y+sx).
  transformed
   begingroup
     save T_;
     transform T_;
     origin transformed T_ = origin;
     (1,0)  transformed T_ = (1,s);
     (0,1)  transformed T_ = (0,1);
     T_
   endgroup
enddef;

def zslant primary p =  % (xu+yv, xv+yu), where p = (u,v).
  transformed
    begingroup
      save T_;
      transform T_;
      xpart T_ = ypart T_ = 0;
      xxpart T_ = yypart T_ = xpart p;
      xypart T_ = yxpart T_ = ypart p;
      T_
    endgroup
enddef;

def xyswap =  % swap X and Y axes.
  zslant (0,1)
enddef;

def boost primary X =  % boosts for special relativity.
  zslant (cosh X, sinh X)
enddef;

% Path transformation.
% f and data are in _graph_ coordinates, returned path is also.

% Rotate path f about point p by angle th in degrees.
% We have to do it this way in case xscale and yscale differ.
vardef rotatedpath (expr p,th) expr f =
 f transformed vtr rotatedaround (p transformed vtr, th) transformed
 (inverse vtr)
enddef;

% Scale path f by factor s with fixed point p.
vardef scaledpath (expr p, s) expr f =
  f shifted -p scaled s shifted p
enddef;

% slant a path f by amount s keeping line y=b fixed.
vardef xslantedpath (expr b, s) expr f =
 f shifted (0,-b) slanted s shifted (0,b)
enddef;
def slantedpath = xslantedpath enddef;

% yslant a path f by amount s keeping line x=a fixed.
vardef yslantedpath (expr a, s) expr f =
 f shifted (-a,0) yslant s shifted (0,a)
enddef;

% xscale a path f by factor s keeping line x=a fixed.
vardef xscaledpath (expr a, s) expr f =
  f shifted (-a,0) xscaled s shifted (a,0)
enddef;

% yscale a path f by factor s keeping line y=b fixed.
vardef yscaledpath (expr b, s) expr f =
  f shifted (0,-b) yscaled s shifted (0,b)
enddef;

% shift a path f by vector v.
vardef shiftedpath (expr v) expr f =
  f shifted v
enddef;

% reflect a path f through line p--q.
% transformed vtr in case xscale and yscale differ.
vardef reflectedpath (expr p,q) expr f =
 f transformed vtr reflectedabout (p transformed vtr, q transformed vtr)
    transformed (inverse vtr)
enddef;
% no vtr so horizontal will become vertical, and vice versa.
vardef xyswappedpath expr f =
  f reflectedabout ((0,0),(1,1))
enddef;

%% Bitmaps, Clipping and Rendering.

% Bitmap to Bitmap --- Bitwise Operations.


% Change a picture u to {0,1} (`monochrome') form.
% Apply this before the other monochrome operations.

def mono (suffix u) =
  cull u keeping (1, infinity);
enddef;

% bitwise and.

def andto (suffix u) (expr v) =
 addto u also v;
 cull u keeping (2,2);
enddef;

primarydef u picand v =
 begingroup
  save t; picture t;
  t=u+v;
  cull t keeping (2,2);
  t
 endgroup
enddef;

% inclusive or.

def orto (suffix u) (expr v) =
 addto u also v;
 cull u keeping (1,2);
enddef;

primarydef u picor v =
 begingroup
  save t; picture t;
  t=u+v;
  cull t keeping (1,2);
  t
 endgroup
enddef;

% exclusive or.

def xorto (suffix u) (expr v) =
 addto u also v;
 cull u keeping (1,1);
enddef;

primarydef u picxor v =
 begingroup
  save t; picture t;
  t=u+v;
  cull t keeping (1,1);
  t
 endgroup
enddef;

% (nonsymmetric) difference.

def subto (suffix u) (expr v) =
 addto u also -v;
 cull u keeping (1,1);
enddef;

primarydef u picsub v =
 begingroup
  save t; picture t;
  t=u-v;
  cull t keeping (1,1);
  t
 endgroup
enddef;

% Contour to Bitmap --- Clipping and Filling.

% (safely filled) interior of contour c,
% where c is in _pixel_ coordinates;
% adapted from _The METAFONTbook_'s "safefill".

% plain.mf defines `.t_' as `transformed currenttransform';
% currenttransform compensates for the aspect ratio of _pixels_,
% and is set in mode_setup, which is called near the top of
% grafbase.mf; see above.
% (Reference:  _The METAFONTbook_, page 269.)

vardef interior expr c =
  save vs; picture vs; vs=nullpicture;
  interim turningcheck := 0;
  addto vs contour (c.t_) withpen nullpen;
  cull vs dropping (0,0);
  vs
enddef;

% (safely filled) interiors of contours cc[],
% where cc[] are in _pixel_ coordinates;

vardef interiors suffix cc =
 save vss_; picture vss_; vss_=nullpicture;
 for i_=1 upto cc:
  orto (vss_, interior cc[i_]);
 endfor
 vss_
enddef;

% derived bitmap operations.

% clip picture vt to interior of cycle c,
% where c is in _pixel_ coordinates.

def clipto (suffix vt) expr c =
 mono (vt);
 andto (vt, interior c);
enddef;

% clip picture vt to interiors of cycles in path array cc,
% where cc[] are in _pixel_ coordinates.

def clipsto (suffix vt, cc) =
 mono (vt);
 andto (vt, interiors cc);
enddef;


% clip copy of picture vt to interior of cycle c,
% where c is in _pixel_ coordinates,
% return result (which is a `subpicture' of vt).

vardef clipped (suffix vt) expr c =
  mono (vt);
  vt picand (interior c)
enddef;

def clip = clipped enddef;

% (return) reverse video of vt inside c,
% where c is in _pixel_ coordinates.

vardef picneg (suffix vt) expr c =
  mono (vt);
  (interior c) picsub vt
enddef;

% Rendering Paths --- Drawing and Filling.

% draw path f in picture v using pen q,
% where f is in _pixel_ coordinates.

def shpath (suffix v) (expr q) (expr f) =
  interim turningcheck := 0;
  addto v doublepath (f.t_)
    withpen q;
enddef;

% draw path d safely, return picture of drawing,
% where d is in _pixel_ coordinates.
% (Courtesy of Uwe Bonnes.)

numeric minpenwd;
minpenwd := 0.01pt;

vardef picpath expr d =
 save vs; picture vs; vs=nullpicture;
 if penwd > minpenwd:
   shpath (vs, drawpen) (d);
   mono (vs);
 fi
 vs
enddef;

% drawing a path d safely,
% where d is in _pixel_ coordinates.
def safedraw expr d =
  addto active_plane also (picpath d);
  if ClipOn:
    clipsto (active_plane, ClipPath);
  fi
enddef;

% filling a region, the interior of c, safely,
% where c is in _pixel_ coordinates.
def safefill expr c =
  if cycle c:
    mono (active_plane);
    orto (active_plane, interior c);
    if ClipOn:
      clipsto (active_plane, ClipPath);
    fi
  else:  % so we can see something
    safedraw c;
  fi
enddef;

% erasing a region (the interior of c) safely,
% where c is in _pixel_ coordinates.
% - really works this time -
def safeunfill expr c =
  if cycle c:
    mono (active_plane);
    subto (active_plane, interior c);
    if ClipOn:
      clipsto (active_plane, ClipPath);
    fi
  else:  % so we can see something
    safedraw c;
  fi
enddef;

% clipping to a region (the interior of c) safely,
def safeclip expr c =
  if cycle c:
    clipto (active_plane) c;
  else:  % so we can see something
    safedraw c;
  fi
enddef;

% draw path f and return path f,
% where f is in _graph_ coordinates.

vardef drawn expr f =
 safedraw zconv (f);
 f
enddef;

% fill contour c and return contour c,
% where c is in _graph_ coordinates.

vardef filled expr c =
 safefill zconv (c);
 c
enddef;

% unfill contour c and return contour c,
% where c is in _graph_ coordinates.

vardef unfilled expr c =
 safeunfill zconv (c);
 c
enddef;

% clip to a contour c and return contour c,
% where c is in _graph_ coordinates.

vardef CLIP expr c =
  safeclip zconv(c);
  c
enddef;

%% Shading and Hatching.

% Shading
% - expressed mainly in _pixel_ coordinates.

% A closed path representing one dot of unit size
% (one pixel across is recommended).
path dotpath;

% Initially, set dotpath to a circle.
dotpath := fullcircle;

% Return picture that renders specified apath path to specified scale.
% We use ceiling to ensure visibility. (?)

vardef setdot (expr apath) (expr scale) =
  if cycle apath:
    interior
  else:
    picpath
  fi
  (apath scaled ceiling (scale))
enddef;

% Picture containing one dot.
picture onedot;

% Initially, set onedot to picture of initial dotpath scaled 0.5pt.
onedot := setdot (dotpath, 0.5pt);

% Draw picture w at position p in picture v,
% where p is in _pixel_ coordinates.

def picdot (suffix v) (expr w) (expr p) =
  addto v also (w shifted (hroundpair (p.t_)));
enddef;

% Whether to show bounding boxes (for debugging purposes).

boolean showbbox;
showbbox := false;

% Calculate tight bounding box points (ll, ur) for path g.
% g is in _pixel_ coordinates.
% This is only called by tbbox, which is never used!
vardef tightbbox (expr g) (suffix ll, ur) =
  % vertical tangent:
  def vtang (expr x) =
    (x, -infinity)--(x, infinity)
  enddef;
  % horizontal tangent:
  def htang (expr y) =
    (-infinity, y)--(infinity, y)
  enddef;
  % xlimit(x) = true iff vtang(x) does not intersect path g:
  vardef xlimit (expr x) =
    (xpart (g intersectiontimes (vtang(x))) < 0)
  enddef;
  % ylimit(y) = true iff htang(y) does not intersect path g:
  vardef ylimit (expr y) =
    (xpart (g intersectiontimes (htang(y))) < 0)
  enddef;
  save min_x, max_x, min_y, max_y;
  numeric min_x, max_x, min_y, max_y;
  min_x := (solve xlimit (-infinity, xpart point 1 of g));
  max_x := (solve xlimit ( infinity, xpart point 1 of g));
  min_y := (solve ylimit (-infinity, ypart point 1 of g));
  max_y := (solve ylimit ( infinity, ypart point 1 of g));
  ll := (min_x, min_y);
  ur := (max_x, max_y);
  if showbbox:
    safedraw (rect (ll, ur));
  fi
enddef;

% calculate tight bounding box points (ll, ur) for path array g.
% g is in _pixel_ coordinates. Not used.
%
vardef tbbox (suffix g) (suffix ll, ur) =
  save gll_, gur_;
  pair gll_[], gur_[];
  for i_=1 upto g:
    tightbbox (g[i_], gll_[i_], gur_[i_]);
  endfor
  gll_ = gur_ = g_;
  ll := minpair (gll_);
  ur := maxpair (gur_);
  if showbbox:
    safedraw (rect (ll, ur));
  fi
enddef;

% calculate loose bounding box points (ll, ur) for path g.
% g is in _pixel_ coordinates.
% Numeric to control how tight the bbox:
numeric bbox_segment_split;
bbox_segment_split=2;

vardef bbox (expr g) (suffix ll, ur) =
  ur := ll := point 0 of g;
  save p_; pair p_[];
  for i_=0 upto (length g)*bbox_segment_split:
    j_  := i_/bbox_segment_split;
    p_1 := point j_ of g;
    p_2 := precontrol j_ of g;
    p_3 := postcontrol j_ of g;
    p_ := 4;
    p_4 := ll;
    ll := minpair (p_);
    p_4 := ur;
    ur := maxpair (p_);
  endfor
  if showbbox:
    safedraw (rect (ll, ur));
  fi
enddef;

% shade interior of path f with dots,
% of pre-specified shape and size (see setdot macro),
% spaced sp apart,
% where f is in _graph_ coordinates,
% and sp is in _pixel_ coordinates.
% Return f.

picture shadedot; shadedot=onedot;
numeric shadewd; shadewd:=0.5pt;

vardef shade (expr sp) expr f =
 save g; path g; g := zconv (f);
 if not cycle g:  % so we can see something
  safedraw g;
  message "attempt to shade open curve; drawing curve instead";
 elseif sp<=max(shadewd,0):
  safefill g;
 else:
  save dx,dy;
  dx:= ceiling(0.5sp*sqrt(2));
  dy:= 2dx; save ll, ur;
  pair ll, ur;
  bbox (g, ll, ur);
  ll := floorpair(ll);
  save v,b; picture v, b;
  % b is basic shading tile (2 dots in a square dy on a side)
  b := shadedot shifted ll;
  addto b also b shifted (dx,dx);
  v:=b;
  for sh = dy step dy until xpart(ur-ll):
    addto v also b shifted (sh,0);
  endfor
  b:= v; % now b is a horizontal strip of basic tiles
  for sh = dy step dy until ypart(ur-ll):
    addto v also b shifted (0,sh);   % we stack them vertically.
  endfor
  addto active_plane also clip(v) g;
 fi
 if ClipOn :
   clipsto (active_plane, ClipPath);
 fi
 f
enddef;

polkadotwd=5pt;
picture thepolkadot;
thepolkadot=setdot(dotpath,polkadotwd);

% fill a region with rather large dots with hexagonal pattern
% Based on original version of macro shade (in grafbase.mf)
% Except a hexagonal packing is used.
vardef polkadot (expr sp) expr f =
 save g; path g; g := zconv (f);
 if not cycle g:  % so we can see something
  safedraw g;
  message "attempt to polkadot open curve; drawing curve instead";
 % Spacing is between _centers_
 % fill unless spacing allows some background to show
 elseif sp<= 2*polkadotwd/3 :
  safefill g;
 else:
  save ll, ur; pair ll, ur;
  bbox (g, ll, ur);
  save dx, dy;
  % dx will be horizontal shift of odd rows
  dx := ceiling (sp/2);
  % dy will be spacing between rows
  dy :=  ceiling((sqrt(3))*sp/2);
  % make an effort to limit occurance of partial dots,
  % Therefore, only draw a dot if its center is in the bbox,
  % and equalize any extra space on sides and top with this shift
  hshift := ((xpart (ur - ll)) mod sp)/2;
  vshift := ((ypart (ur - ll)) mod dy)/2;
  save p; pair p[];
  p0 := floorpair (ll + (hshift + dx,vshift));
  p1 := p0 + (-dx,dy);
  save v; picture v[];
  v0 := nullpicture;
  for s = xpart p0 step 2dx until xpart ur:
    addto v0 also thepolkadot shifted (s,0);
  endfor % v0 is now the bottom row (but not yet raised into place)
  v1 := thepolkadot shifted (xpart p1, 0);
  addto v1 also v0 shifted (dx, 0); % second row
  save vv; picture vv;
  vv := nullpicture;
  for s = ypart p0 step 2dy until ypart ur:
    addto vv also v0 shifted (0,s); % add copies of v0
  endfor
  for s = ypart p1 step 2dy until ypart ur :
    addto vv also v1 shifted (0,s); % add copies of v1
  endfor
  addto active_plane also clip (vv) g;
 fi
 if ClipOn :
   clipsto (active_plane, ClipPath);
 fi
 f
enddef;

% Given:
% a picture variable v_c,
% an affine transform CT,
% a spacing sp,
% and an upright box (with edges xa, xb, ya, yb)
% specified in the coordinate system that CT determines.
% Then:
% hatch the interior of the box in the picture,
% using lines that in that coordinate system are horizontal
% and spaced sp units apart.
% Notes:
% 1.  sp must be strictly positive.
% 2.  CT, xa, xb, ya, yb, and sp are in _pixel_ coordinates.
% 3.  xa, xb, ya, and yb are the left, right, bottom and top
%     edges of the box.

vardef thatchf (suffix v_c) (expr CT, sp, xa, xb, ya, yb) =
  % Number of hatch lines. Called by other hatching
  % macros. THEY check that sp is not 0
  save m_; numeric m_;
  m_ := ceiling (abs (yb - ya) / sp);
  save p_; pair p_[];  % In Pixel Coords.
  % Hatch lines have same alignment, spacing and orientation.
  % Displacement from one hatch line to the next.
  p_1 := sp * up;
  % Y coordinate of first hatch line.
  save yc_; numeric yc_;
  yc_ := sp * ceiling (ya/sp);
  % Endpoints of first hatch line.
  p_2 := (xa, yc_);
  p_3 := (xb, yc_);
  for i_=1 upto m_:
   % The next source line does all the drawing.
   shpath (v_c, hatchpen) ((p_2--p_3) transformed CT);
   p_2 := p_2+p_1;
   p_3 := p_3+p_1;
  endfor
enddef;

% Hatch interior of path f
% with lines at angle theta,
% spaced sp apart.
% where f is in _graph_ coordinates,
% and sp is in _pixel_ coordinates.

vardef thatch (expr sp, theta) expr f =
 % g is in _pixel_ coordinates.
 save g; path g; g := zconv (f);
 if not cycle g:  % so we can see something
  message "attempt to hatch open curve; drawing curve instead";
  safedraw g;
 elseif sp<=0:
  safefill g;
 else:
  save v_c; picture v_c;
  v_c := nullpicture;
  save CT; transform CT;
  CT := identity rotated theta;
  save ll, ur; pair ll, ur;
  bbox (g transformed inverse CT, ll, ur);
  save xa, xb, ya, yb; numeric xa, xb, ya, yb;
  (xa, ya) = ll;
  (xb, yb) = ur;
  % Hatch interior of bounding box.
  thatchf (v_c, CT, sp, xa, xb, ya, yb);
  % Clip to interior of path g.
  addto active_plane also clip(v_c) g;
 fi
 f
enddef;


% Hatch interior of path f with horizontal lines spaced sp apart.
% where f is in _graph_ coordinates,
% and sp is in _pixel_ coordinates.

vardef hhatch (expr sp) expr f =
  thatch (sp, 0 deg) f
enddef;

% Hatch interior of path f with vertical lines spaced sp apart.
% where f is in _graph_ coordinates,
% and sp is in _pixel_ coordinates.

vardef vhatch (expr sp) expr f =
  thatch (sp, 90 deg) f
enddef;

% Left-hatch interior of path f with lines spaced sp apart,
% where f is in _graph_ coordinates,
% and sp is in _pixel_ coordinates.
% Return f.

vardef lhatch (expr sp) expr f =
  thatch (sp, -45 deg) f
enddef;

% Right-hatch interior of path f with lines spaced sp apart,
% where f is in _graph_ coordinates,
% and sp is in _pixel_ coordinates.
% Return f.

vardef rhatch (expr sp) expr f =
  thatch (sp, 45 deg) f
enddef;

% Cross-hatch interior of path f with lines spaced sp apart,
% where f is in _graph_ coordinates,
% and sp is in _pixel_ coordinates.

vardef xhatch (expr sp) expr f =
 lhatch (sp) rhatch (sp) f
enddef;


%% Tiles.

% environment for a tile `atile', drawn with unit length
% `unit', with width `width' units and height `height' units.
% If `clipit' is true, clip the drawing to the tile's
% (width x height) boundary.

% DHL: Convert dimensions of tile to device units,
%      and change ztr to use tile units.

def tile (suffix atile) (expr unit, width, height, clipit) =
 picture atile.pic; atile.pic:=nullpicture;
 % for reference by `tess' macro:
 numeric atile.wd, atile.ht;
 atile.wd:=hround(width*unit); % device coordinates
 atile.ht:=hround(height*unit);% We need these to be integer (eventually)
 boolean atile.clipon; atile.clipon:=clipit;
 begingroup
  save active_plane;
  def active_plane = atile.pic enddef;
  save ztr, vtr; transform ztr, vtr;
  ztr := identity scaled unit; %Drawing should now be relative to tile
                               %boundaries.
  vtr := ztr;
  save ClipOn; boolean ClipOn;
  if clipit:
   ClipOn := true;
   save ClipPath; path ClipPath[];
   ClipPath = 1;
   ClipPath[1] =
     zconv ((0,0)--(width,0)--(width,height)--(0,height)--cycle);
  else:
   ClipOn := false;
  fi
enddef;

def endtile =
 endgroup
enddef;

% test whether atile is really a tile.

def is_tile (suffix atile) =
  ((picture atile.pic) and
   (numeric atile.wd) and
   (numeric atile.ht) and
   (boolean atile.clipon))
enddef;

% tesselation of interior of closed path c,
% using tile `atile'.
% c is in _graph_ units.
% return c.

vardef tess (suffix atile) expr c =
 save g_; path g_; g_ := zconv (c);
 if not is_tile (atile):
  errhelp ("Please check the faulty definition of the `tile'");
  errmessage ("Parameter of tess() is invalid tile.");
  safedraw g_;
 elseif not cycle g_:  % so we can see something
  safedraw g_;
 else:
  save ll_, ur_; pair ll_, ur_;
  bbox (g_, ll_, ur_);
  save a_, b_; numeric a_, b_;
  a_ = atile.wd;
  b_ = atile.ht;
  ll_ := floorpair (ll_);
  save v_, vs_;
  picture v_, vs_;
  v_  := atile.pic shifted ll_;
  vs_ := v_;
  % fill vs_ with rectangular tesselation large enough
  % to cover interior of g_:
  for sh_ = a_ step a_ until xpart (ur_ - ll_): %lay down a hoizontal strip
    addto vs_ also (v_ shifted (sh_, 0));
  endfor
  v_:= vs_;
  for sh_ = b_ step b_ until ypart (ur_ - ll_): % now stack the strips
    addto vs_ also (v_ shifted (0, sh_));
  endfor
  % trim tesselation to interior of g:
  clipto (vs_) g_;
  addto active_plane also vs_;
 fi
 if ClipOn :
   clipsto (active_plane, ClipPath);
 fi
 c
enddef;

%% Dots and Dashes.

% Draw dashed curve along path f;
% f is in _graph_ coordinates;
% return f.

if unknown segment_split: segment_split:= 8; fi
if unknown dashsize:     dashsize  := 3pt; fi
if unknown dashgap:      dashgap := 1dashsize + 2penwd; fi
if unknown dash_finish:   dash_finish := .5; fi
if unknown dash_start:    dash_start := .5; fi
if unknown unit_of_length: unit_of_length:= 0.1in; fi

% The main idea is to have a list of lengths represent the repeating
% pattern of dashes and dots. These lengths represent a dash length,
% followed by a gap length, etc., so there are an even number. To start
% dashing a path, we normally take a fraction (dash_start) of the first
% dash, then the rest of the pattern. We continue by repeating the pattern
% as many times as will fit, then we finish off with a fraction
% (dash_finish) of the first dash. A dash of length 0 is a dot. A gap of
% length 0 is OK, but useless unless it's between a dot and a dash, and
% you arrange for the dotsize to be different from penwd.

% We generalize so that pat.start and pat.finish can be any patterns,
% not necessarily related to pat.rep. Also "dots" can be symbols like
% Triangle.

vardef gendashed (suffix pat) expr f =
  if (unknown pat.rep) or (pat.rep<2) : % no "pattern"
    safedraw zconv (f);
  else :
    % Note: can't "save pat" in case pat has a suffix, so we
    % save temp and copy pat to temp later:
    save dl_, temp_, need_dots;
    boolean need_dots;
    need_dots:=false;
    % Compute lengths of patterns. Scale by 1/unit_of_length to avoid
    % overflow. Also set flag if any dots are needed, since they are done
    % differently than dashes (though perhaps they ought not be in MP):
    forsuffixes s = start, rep, finish :
      dl_.s:=0;
      temp_.s:=pat.s;
      for i=1 upto pat.s:
          if (odd i) and (pat.s[i] = 0):
            need_dots:=true;
          fi
          temp_.s[i]:= pat.s[i]/unit_of_length;
          dl_.s:= dl_.s + temp_.s[i];
      endfor
    endfor
    if dl_.rep = 0:
      message "You can't dash a curve if the dash pattern has 0 length!";
      message "Drawing it instead.";
      safedraw zconv (f);
    else:
      if need_dots:
      % dotpath is a grafbase global variable, defaulting to fullcircle.
      % Calling routine may define plot_pic
        save dashingdot; picture dashingdot;
        if picture plot_pic :
          dashingdot := plot_pic;
        else:
          dashingdot := setdot (dotpath, penwd); % same width as dashes
        fi
      fi
      save g_, p_;
      path g_, p_;
      g_ := zconv (f);
      p_ := g_ scaled (1/unit_of_length);

      % First calculate array of lengths of approximating polygon:
      save cur_len, tot_len, n_, sf_;
      tot_len:=makelengtharray(cur_len) p_;

      % now scale the dashes so a whole number make up
      % the approximating polygon:
      sf_:=scale_adjust (n_, dl_)(tot_len);

      % if the path is too short, we do not dash it.
      if n_ < 0 : % Now n_ is # of _repeated_ dashes (one smaller than before)
        safedraw g_;
      else:
        save ct_, _t_, _d_;
        % rescale the patterns:
        forsuffixes s = start, rep, finish :
          for i = 1 upto temp_.s :
            temp_.s[i] := temp_.s[i]*sf_;
          endfor
        dl_.s:=dl_.s*sf_;
        endfor

        % Find times for endpoints of the dashes and draw them:
        ct_:=0;
        % Begin with start pattern
        _d_0:=0;
        _t_0 := 0;
        dashit (temp_.start);

        % And now the repeating pattern
        % _d_0 now points to the end of pat.start, but to be on the safe side
        % we initialize anyway:
        _d_0:= dl_.start;

        % We now just loop over the number of repetitions. BUT to reduce
        % accumulated roundoff error, use dl to re-initialize _d_0.
        for i = 0 upto n_-1 :
          _d_0:= dl_.start+i*dl_.rep;
          _t_0 := gettime(cur_len, ct_) (_d_0);
          dashit(temp_.rep);
        endfor

        % and finally, pat.finish
        % At this point d0 ought to point to the start of
        % pat.finish, but force it there anyway (avoid rounding problems).
        _d_0:= tot_len - dl_.finish;
        _t_0 := gettime(cur_len, ct_) (_d_0);
        dashit(temp_.finish);
        if ClipOn :
          clipsto (active_plane, ClipPath);
        fi
      fi
    fi
  fi
  f
enddef;

% Takes an array name, computes the array of partial lengths,
% returns the total length.
vardef makelengtharray (suffix clen) expr p =
  clen:=segment_split*length p;
  clen[0] := 0;
  for i_ = 1 upto clen :
    clen[i_] := clen[i_-1] + abs ( ( point (i_/segment_split) of p )
                - ( point ((i_-1)/segment_split) of p ) );
  endfor
  clen[clen]
enddef;

% n is an integer suffix defined by the calling routine
% pl.{start,rep,finish} are arrays of lengths of dashing patterns
% lngth is (normalized) length of some path.
% rounds n  to an integer, and returns the scaling factor sf require
% to make  sf*(pl.start + n*pl.rep + pl.finish) equal to lngth.
vardef scale_adjust (suffix n, pl) (expr lngth) =
  n:= (lngth - pl.start - pl.finish)/pl.rep;
  % -1 is a flag
  n:= if n < 0 : -1 else : round(n) fi; % avoids scaling too much.
  lngth/(pl.start + max(n,0)*pl.rep + pl.finish)
enddef;

% arr is an array of lengths , defined by the calling routine
% ct is current index into that array
% lngth ia a length
% returns the (normalized) time on a polygonal path with segments of
% length arr where the accumulated length is lngth.
vardef gettime (suffix arr, ct) (expr lngth) =
  save _d_; numeric _d_;
  % force lngth into range [ arr[ct], arr[arr] ]
  _d_:= max( arr[ct], min ( arr[arr], lngth ) );
  % find segment in which lngth is acheived
  forever:
    exitif ( (_d_ >= arr[ct]) and (_d_ <= arr[ct+1]) );
    ct:=ct+1;
  endfor
  % Within that segment, by linear interpolation.
  % (Could we divide by 0 here? Yes, curve could have a small loop.)
  if arr[ct+1] = arr[ct] :
    ct/segment_split
  else :
    (ct + (_d_ - arr[ct]) / (arr[ct+1] - arr[ct]))/ segment_split
  fi
enddef;

def dashit (suffix pos) =
% gendashed defines array cur_len, path g_, and initializes _d_0, _t_0 and ct_.
  for j_=1 upto pos:
    if odd j_: % draw a dash of length pos[j_]
      if pos[j_]=0 :
        _d_1:= _d_0;
        _t_1:= _t_0;
        picdot (active_plane, dashingdot, point _t_0 of g_);
      else:
        _d_1:= _d_0 + pos[j_];
        _t_1:= gettime (cur_len, ct_) (_d_1);
        safedraw(subpath (_t_0,_t_1) of g_);
      fi
    else: % j_ is even, find the start of the next dash:
      _d_0 := _d_1 + pos[j_];
      _t_0 := gettime(cur_len, ct_) (_d_0);
    fi
  endfor
enddef;

% Convert a text list of lengths to three dash pattern arrays.
% Should we also scale by unit_of_length to avoid doing it for every
% call to gendashed?
def dashpat (suffix pat) (text t) =
  % calling routine is responsible for saving, declaring, etc

  pat.rep := 0;
  for item_ = t:
    pat.rep[incr pat.rep]:= item_;
  endfor;

  % enforce an even number if > 2 in the list:
  if (odd pat.rep) and (pat.rep > 2) : pat.rep:= pat.rep - 1; fi

  % calculate start and finish patterns from repeating pattern
  pat.start:=1;
  pat.start[1]:=pat.rep[1]*dash_start;
  for j=2 upto pat.rep :
    pat.start[incr pat.start]:=pat.rep[j];
  endfor

  pat.finish:=1;
  pat.finish[1]:=pat.rep[1]*dash_finish;
enddef;

vardef DASHED (expr dlen,dgap) expr f =
  save dashes;
  dashpat (dashes) (dlen,dgap);
  gendashed (dashes) f
enddef;

def dashed = DASHED enddef;

% Draw dotted curve along path f;
% f is in _graph_ coordinates;
% return f.

% Generalized to other shapes besides dots.
%%% Try to allow pictures as well as paths:

vardef doplot (expr spath,sc,dgap) expr f =
  save dots;
  dashpat (dots) (0,dgap);
  save plot_pic; picture plot_pic;
  plot_pic := makesymbol (spath, sc);
  gendashed (dots) f
enddef;

vardef dotted (expr dsize, dgap) expr f =
  doplot(dotpath,dsize,dgap) f
enddef;

% Utility: takes *any* spath and returns a picture
vardef makesymbol (expr spath, sc) =
  if picture spath:
    spath
  elseif path spath:
    setdot(spath, sc)
  else:
    message "Undefinable plotting symbol, using dotpath instead.";
    setdot(dotpath, sc)
  fi
enddef;

vardef plotsymbol (expr spath, sc) (text t) =
  save one_symbol; picture one_symbol;
  one_symbol := makesymbol (spath, sc);
  for a_ = t:
    picdot (active_plane, one_symbol, zconv(a_));
  endfor
  if ClipOn :
    clipsto (active_plane, ClipPath);
  fi
enddef;

% Symbols to plot, Taco requested 6 types, here's 9:
% Use common names. (Square is an open square, SolidSquare a solid one.)

path Triangle, Square, Circle, Diamond, Star, Plus, Cross, Star, Asterisk,
    SolidTriangle, SolidSquare, SolidCircle, SolidDiamond, SolidStar;

% Differing scales attempt to equalize area:
Triangle:= (for n=0 upto 2: (up rotated 120n)-- endfor up) scaled .675;
  SolidTriangle := (Triangle & cycle) scaled 1.1;
Square := (for n = 0 upto 3: dir (90n + 45)-- endfor dir 45) scaled .625;
  SolidSquare := (Square & cycle) scaled 1.1;
Circle := (halfcircle & halfcircle rotated 180); % diameter = 1
  SolidCircle := (Circle & cycle) scaled 1.1;
Diamond := (for n = 0 upto 3: dir 90n-- endfor dir 0) yscaled .75 xscaled .52;
  SolidDiamond := (Diamond & cycle) scaled 1.1;
Plus := ( origin for n=0 upto 3: --(up rotated 90n)--origin endfor ) scaled .625;
Cross := Plus rotated 45;
Asterisk := ( origin for n=0 upto 4: --(up rotated 72n)--origin endfor ) scaled .575;
pair zz;
zz = (whatever)[up, up rotated 144];
zz = (whatever)[up rotated 72, up rotated -72];
Star := ( for n=0 upto 4: (up rotated 72n)--(zz rotated 72n)-- endfor up) scaled .675;
SolidStar := (Star & cycle) scaled 1.2;
save zz;
% The above scheme depends on the fact that safefill fills cycles
% and draws noncycles. Only the "Solid..." paths are cycles.


% set up scheme to plot several curves with automatically changing line
% types:

forsuffixes s = start, rep, finish :
  numeric dashtype[].s, dashtype[].s[];
endfor
% gaps exceed dash lengths because circular pens increase the lengths
% by penwd (normally .5pt) and decrease the gaps by penwd.
dashpat (dashtype0) (0);                        % solid
dashpat (dashtype1) (3pt,4pt);                  % dashed
dashpat (dashtype2) (0,4pt);                    % dotted
dashpat (dashtype3) (0pt,4pt,3pt,4pt);          % dot-dash
dashpat (dashtype4) (0pt,4pt,3pt,4pt,0pt,4pt);  % dot-dash-dot
dashpat (dashtype5) (0pt,4pt,3pt,4pt,3pt,4pt);  % dot-dash-dash

path pointtype[];
pointtype0:=Star;
pointtype1:=Triangle;
pointtype2:=SolidCircle;
pointtype3:=Plus;
pointtype4:=Square;
pointtype5:=SolidDiamond;
pointtype6:=Cross;
pointtype7:=Circle;
pointtype8:=SolidTriangle;

%% Points.

% ptwd is a numeric scale factor,
% b is a pair;
% return a path representing
% a point
% with diameter ptwd
% located at b.
% All quantities are in _pixel_ units.

vardef bpoint (expr ptwd, b) =
  fullcircle scaled ptwd shifted b
enddef;

% Draw discs with _pixel_ diameter ptwd
% at the _graph_ locations listed in the text t.
% If filled is true, make the discs black,
% otherwise make them white with a black boundary.

vardef pointd (expr ptwd, filled) (text t) =
 save f_; path f_;  % in _graph_ coords.
 for a_=t:
  f_ := bpoint (ptwd, zconv (a_));
  if filled:
    safefill f_;
  else:
    safeunfill f_;
    safedraw f_;
  fi
 endfor
enddef;

%% Arrows.
% arrowheads are in _pixel_ coordinates.

newinternal hdwdr, hdten;
interim hdwdr := 1;
interim hdten := 1;

boolean hfilled;
hfilled := false;

% wr is in _pixel_ coordinates;
% tens is a pure number;
% fil is _boolean_.

def headshape (expr wr, tens, fil) =
 interim hdwdr := wr;
 interim hdten := tens;
 save hfilled; boolean hfilled; hfilled := fil;
enddef;

% front, back and hdwdr are in _pixel_ coords.

vardef head (expr front, back, hdwdr, tens, filled) =
 if front <> back:
   save p, side;
   pair p[], side;
   side := (hdwdr/2) *
     ((front-back) rotated 90);
   p1 := back + side;
   p2 := back - side;
   save f; path f;
   f :=
     p1..tension tens..{front-back}front{back-front}..tension tens..p2;
   if filled:
     safefill (f--cycle);
   fi
   safedraw f;
 fi
enddef;

% hlen, hrot and hback are in _pixel_ coords.
% f is in _graph_ coords.
% return f.

%% Changed to test for nonzero hlen:
vardef headpath (expr hlen, hrot, hback) expr f =
 if hlen <> 0:
   save g; path g; g := zconv (f);
   save P; pair P[];
   P2 := point     (length g) of g;
   P1 := direction (length g) of g;
   if P1 <> (0,0) :
     P3 := ((unitvector (P1)) rotated hrot);
     P4 := P2 - (hback * P3);
     P5 := P4 - (hlen  * P3);
     head (P4, P5, hdwdr, hdten, hfilled);
   fi
 fi
 f
enddef;

% hlen is in _pixel_ coordinates;
% f is in _graph_ coords.

def arrowdraw (expr hlen) (expr f) =
 store (trash) drawn headpath (hlen, 0, 0pt) f;
enddef;

%% Axes, Axis Tic Marks, and Grid.

% hlen is in _pixel_ coords.
% axes are described by xneg, xpos, yneg and ypos,
% which are in _graph_ coords.

def xaxis (expr hlen) =
 arrowdraw (hlen) ((xneg, 0)--(xpos, 0));
enddef;

def yaxis (expr hlen) =
 arrowdraw (hlen) ((0, yneg)--(0, ypos));
enddef;

def axes (expr hlen) =
 xaxis (hlen);
 yaxis (hlen);
enddef;

%%%%%%  Axes at borders
% amounts to shift border axes inward, default 0.
laxis:=0; baxis:=0; raxis:=0; taxis:=0;

vardef axisline.x = (xneg + laxis, 0)--(xpos - raxis, 0) enddef;
vardef axisline.y = (0, yneg + baxis)--(0, ypos - taxis) enddef;
vardef axisline.l = axisline.y shifted (xneg + laxis, 0) enddef;
vardef axisline.b = axisline.x shifted (0, yneg + baxis) enddef;
vardef axisline.r = axisline.y shifted (xpos - raxis, 0) enddef;
vardef axisline.t = axisline.x shifted (0, ypos - taxis) enddef;

vardef axis@# (expr len) =
  headpath (len, 0, 0pt) axisline@#
enddef;
%%%%%% End axes at borders

%%%%%% Tick marks on all axes
% flags for changing tick marks from default.
numeric inside, outside, centered, onleft, onright, ontop, onbottom;
% sign of flag is used to determine if direction is relative
% (inside/outside) or absolute
inside:=-2; outside:=-1;
onright:=1; centered:=1.5; onleft:=2;
onbottom:=1; ontop:=2;

% defaults:
ltick:=inside; rtick:=inside; ttick:=inside; btick:=inside;
xtick:=centered; ytick:=centered;

% Utility macro to draw tick marks on an arbitrary axis.
% It needs info about the marks and the axis:
% inang is angle one must rotate the axis to point inside.
% tp    is the tick position (normally inside, outside, etc).
% loc   is the location of the 0-point of the axis (graph coordinates).
% pdir  is the positive direction on the axis (right or up).
% len   is length of tick mark.
% t     is the list of positions.

vardef axismarks (expr inang, tp, loc, pdir) (expr len) (text t) =
 save tp_, U_, P_, tic_, ticang_;
 pair U_, P_, tic_[];
 % if onleft, onright, ontop or onbottom, don't examine inang
 % otherwise use it to determine what inside/outside mean.
 %
 ticang_ := if tp<0: inang else: 90 fi;
 tp_:=abs(tp) - 1;
 % Axis Tic Marks are at right angles to axes,
 % U_ should point in direction of inside, onleft or ontop.
 U_:=unitvector (vconv (pdir)) rotated ticang_;
 tic_1:= (tp_ - 1) * len * U_; % start of mark
 tic_2:= tp_ * len * U_;       % end of mark
 for a_=t:
   P_ := zconv (loc + a_*pdir);
   safedraw ((tic_1--tic_2) shifted P_);
 endfor
enddef;

def xmarks =
  axismarks (90,xtick,(0,0),right)  % 90, same as bottom axis
enddef;

def ymarks =
  axismarks (-90,ytick,(0,0),up)    % -90, same as left axis
enddef;

def lmarks =
  axismarks (-90,ltick,(xneg+laxis,0),up)
enddef;

def bmarks =
  axismarks (90,btick,(0,yneg+baxis),right)
enddef;

def rmarks =
  axismarks (90,rtick,(xpos-raxis,0),up)
enddef;

def tmarks =
  axismarks (-90,ttick,(0,ypos-taxis),right)
enddef;
%%%%%% End tick marks on axes

% Grid.

% Draw dots at each grid coordinate.
% xspace, yspace = spacings between grid coordinates, in _graph_ coords.

def grid = vgrid (0.5pt) enddef;
vardef vgrid (expr dsize) (expr xspace, yspace) =
 save griddot; picture griddot; griddot:=setdot(dotpath,dsize);
 for n = ceiling(xneg/xspace) upto floor(xpos/xspace):
   for m = ceiling(yneg/yspace) upto floor(ypos/yspace):
     picdot (active_plane, griddot, zconv((n*xspace,m*yspace)));
   endfor
 endfor
 if ClipOn :
   clipsto (active_plane, ClipPath);
 fi
enddef;

% Grid of lines.
vardef gridlines (expr xspace, yspace) =
 for n = ceiling(xneg/xspace) upto floor(xpos/xspace):
   safedraw
   (hroundpair(zconv((n*xspace,yneg)))--hroundpair(zconv((n*xspace,ypos))));
 endfor
 for n = ceiling(yneg/yspace) upto floor(ypos/yspace) :
   safedraw
   (hroundpair(zconv((xneg,n*yspace)))--hroundpair(zconv((xpos,n*yspace))));
 endfor
enddef;

%%%%%% Polar grid:
% plrpatch draws arcs with radii beginning rstart, stepping by rstep,
% and with starting angle tstart and ending angle tstop. Also draws
% radial lines from rstart to rstop, at angles beginning with tstart and
% stepping by tstep. All 6 parameters in graph coordinates.

vardef plrpatch (expr rstart,rstop,rstep,tstart,tstop,tstep) =
  save rs_;
  rs_:= if rstart = 0 : rstep else: rstart fi;
  for rad=rs_ step rstep until rstop:
    store (trash) drawn arcplr ((0,0),tstart,tstop,rad);
  endfor
  for ang=tstart step tstep until tstop:
    store (trash) drawn (((rstart,0)--(rstop,0)) rotated ang);
  endfor
enddef;

% polargrid: Draws the smallest plrpatch that covers the whole graph,
% where arcs have radii that are multiples of rstep and radial lines
% have angles that are multiples of tstep. Then clips it to the graph
% boundaries. Both parameters in graph coordinates.

vardef polargrid (expr rstep,tstep) =
  save p,r,t,rmax,rmin,tmax,tmin;
  pair p[];
  % Four corners:
  p0:=(xneg,yneg); p1:=(xneg,ypos); p2:=(xpos,ypos); p3:=(xpos,yneg);
  % Find max and min of radius and angles.
  r0:=abs(p0); rmax:=r0;
  for j = 1 upto 3 :
    r[j]:=abs(p[j]);
    if rmax < r[j] : rmax:=r[j]; fi
  endfor
  % If origin is in graph do rmin and angles one way:
  if (xneg <=0) and (xpos>=0) and (yneg <=0) and (ypos>=0):
    tmin:=0; tmax:=360; rmin:=0;
  else:
    t0:= angle(p0);
    rmin:=r0; tmax:=t0; tmin:=t0;
    for j=1 upto 3:
      t[j]:=t0 + angle (p[j] rotated -t0)
      if rmin > r[j] : rmin:=r[j]; fi
      if tmax < t[j] : tmax:=t[j]; fi
      if tmin > t[j] : tmin:=t[j]; fi
    endfor
  fi
  % force rmin and tmin to be multiples of rstep and tstep
  rmin:=rstep*floor(rmin/rstep);
  tmin:=tstep*floor(tmin/tstep);
  save v; picture v;
  v:= image(plrpatch( rmin, rmax, rstep, tmin, tmax, tstep ));
  clipto (v) zconv(rect((xneg,yneg),(xpos,ypos)));
  addto active_plane also v;
enddef;

vardef polarpatch (expr rstart, rstop, rstep, tstart, tstop, tstep) =
  plrpatch (rstart, rstop, rstep, tstart, tstop, tstep)
  % make sure the ending boundaries are drawn, too:
  store (trash) drawn arcplr ((0,0),tstart,tstop,rstop);
  store (trash) drawn (((rstart,0)--(rstop,0)) rotated tstop);
enddef;

%% Upright Rectangles.
% coordinate-independent.

vardef rect (expr ll, ur) =
  ll--(xpart ur, ypart ll)--
    ur--(xpart ll,ypart ur)--cycle
enddef;


%% Path Construction.
% coordinate-independent.

% general path construction

vardef mkpath (expr smooth,cyclic) (suffix pts) =
 if smooth:
  mksmooth (cyclic, pts)
 else:
  mkpoly (cyclic, pts)
 fi
enddef;


%% Polylines, including Polygons.
% coordinate-independent.

% polyline construction
% pts is both an array of pairs or paths,
% and a count of its members (from 1 to pts).

vardef mkpoly (expr cyclic) (suffix pts) =
 for i_=1 upto pts-1:
  pts[i_]--
 endfor
 pts[pts]
 if cyclic:
  --cycle
 fi
enddef;

vardef polyline (expr cyclic) (text t) =
 save p_;textpairs (p_,t);
 mkpoly (cyclic, p_)
enddef;

%% Smooth Curves.
% coordinate-independent.

% smooth path construction

numeric cur_tension; cur_tension := 1;

vardef mksmooth (expr cyclic) (suffix pts) =
 if cyclic:
  pts[1]{pts[2]-pts[pts]}
 else:
  pts[1]
 fi
 for i_=2 upto pts-1:
  ..tension cur_tension..pts[i_]{pts[i_+1]-pts[i_-1]}
 endfor
 ..tension cur_tension..pts[pts]
 if cyclic:
  {pts[1]-pts[pts-1]}..tension cur_tension..cycle
 fi
enddef;

% The following for backward compatability:
def curve (expr cyclic) = tcurve (cur_tension,cyclic) enddef;

vardef tcurve  (expr tens, cyclic) (text t) =
 save p_;textpairs (p_,t);
 save cur_tension; cur_tension:=tens;
 mksmooth (cyclic, p_)
enddef;

% quadratic B-splines.
% p[] == B-spline control points,
% p in number.

vardef openqbs (text t) =
  save p_;textpairs (p_,t);
  for i=1 upto p_-2:
    0.5[p_[i],p_[i+1]]
    ..controls 1/6[p_[i+1],p_[i]]
           and 1/6[p_[i+1],p_[i+2]]..
  endfor
  0.5[p_[p_-1],p_[p_]]
enddef;

vardef closedqbs (text t) =
  save p_;textpairs (p_,t);
  p_[p_+1]:=p_1;
  p_[p_+2]:=p_2;
  for i=1 upto p_:
    0.5[p_[i],p_[i+1]]
    ..controls 1/6[p_[i+1],p_[i]]
           and 1/6[p_[i+1],p_[i+2]]..
  endfor
  cycle
enddef;

% cubic B-splines.

vardef mkopencbs (suffix b) =
  for i_=1 upto b-3:
    (b[i_]+4b[i_+1]+b[i_+2])/6
    ..controls 1/3[b[i_+1],b[i_+2]]
           and 2/3[b[i_+1],b[i_+2]]..
  endfor
  (b[b-2]+4b[b-1]+b[b])/6
enddef;

vardef opencbs (text t) =
  save b_;textpairs (b_,t);
  mkopencbs (b_)
enddef;

vardef mkclosedcbs (suffix b) =
  b[b+1]:=b1;
  b[b+2]:=b2;
  for i_=1 upto b:
    (b[i_]+4b[i_+1]+b[i_+2])/6
    ..controls 1/3[b[i_+1],b[i_+2]]
           and 2/3[b[i_+1],b[i_+2]]..
  endfor
  cycle
enddef;

vardef closedcbs (text t) =
  save b_;textpairs (b_,t);
  mkclosedcbs (b_)
enddef;

%% Path Closure.

% With uclosed, the original curve is drawn unchanged. Only a closing
% Bezier is added that meets the endpoints smoothly.
vardef uclosed expr f =
 f
 if not cycle f:
 & (point infinity of f){direction infinity of f}..
    {direction 0 of f}(point 0 of f)&cycle
 fi
enddef;

% path f closed by line segment.
vardef lclosed expr f =
 f
 if not cycle f:
   --cycle
 fi
enddef;

% close path f in manner of mksmooth.

vardef sclosed expr f =
 if cycle f:
  f
 else:
  save n; numeric n;
  n := length f;  % number of Bezier segments in f
  if n <= 1:  % single Bezier segment (n=1), or single point (n=0)
   f..cycle
  else:  % (n >= 2)
   (point 0 of f){(point 1 of f)-(point n of f)}
   ..(subpath (1,n-1) of f)
   ..(point n of f){(point 0 of f)-(point (n-1) of f)}
   ..cycle
  fi
 fi
enddef;

% path f closed by bezier.

vardef bclosed expr f =
 f
 if not cycle f:
   ..cycle
 fi
enddef;

% conversion of Bezier segment key points, z,
% to cubic B-spline control points, b.

def ztob (suffix z, b) =
  save b; pair b[];
  b := 4;
  b1 = 6z1-7z2+2z3;
  b2 =     2z2- z3;
  b3 =    - z2+2z3;
  b4 =     2z2-7z3+6z4;
enddef;

% closure of path f by a cubic B-spline.

vardef cbclosed expr f =
 if cycle f:
  f
 else:
  save p, n; pair p[];
  p1 := point 0 of f;
  p2 := postcontrol 0 of f;
  p3 := precontrol 1 of f;
  p4 := point 1 of f;
  ztob (p,a);
  n := length f;
  p1 := point (n-1) of f;
  p2 := postcontrol (n-1) of f;
  p3 := precontrol n of f;
  p4 := point n of f;
  ztob (p,b);
  b1 := b3;
  b2 := b4;
  b3 := a1;
  b4 := a2;
  f..mkopencbs(b)..cycle
 fi
enddef;


%% Circles and Ellipses.
% coordinate-independent.

vardef ellipse (expr center, radx, rady, angle) =
 save t;
 transform t;
 t:=identity xscaled (2*radx)
   yscaled (2*rady) rotated angle
   shifted center;
 fullcircle transformed t
enddef;

vardef circle (expr center,rad) =
 ellipse (center,rad,rad,0)
enddef;


%% Circular Arcs.
% coordinate-independent.

% center-point-sweep form of arc

vardef arc (expr center, from, sweep) =
 save f; path f;
 f := from;
 if (center<>from) and (sweep<>0):
   n := ceiling (abs(sweep)/45) + 1;
   if n<3: n := 3; fi
   theta := sweep/(n-1);
   save p,q; pair p,q;
   p := from;
   for i=2 upto n:
     p := p rotatedabout (center, theta);
     q := (p-center) rotated if theta<0: - fi 90;
     f := f..p{unitvector q};
   endfor
 fi
 f
enddef;

vardef arccenter (expr from, to, sweep) =
  if (sweep mod 360) = 0:
  message "The central point of this arc is undefined.";
  message "Using midpoint of chord.";
  fi
 ((0.5)[from,to]
  if (sweep mod 360) <> 0:
    + ((cotd (sweep/2)) * ((to-from) rotated 90))/2
  fi)
enddef;

% point-point-sweep form of arc

vardef arcpps (expr from, to, sweep) =
 if ((sweep mod 360)=0) or (from=to) :
   message "Undefined arc. A line segment will be used.";
   from--to
 else:
   save center; pair center;
   center := arccenter (from,to,sweep);
   arc (center, from, sweep)
 fi
enddef;

% modified polar --

% center, angle, angle, radius

vardef arcplr (expr center, frtheta, totheta, rad) =
 save from; pair from;
 from:=center+rad*(dir frtheta);
 arc (center, from, totheta-frtheta)
enddef;

% center-point-sweep form

vardef arccps (expr center, from, theta) =
 arc (center, from, theta)
enddef;

% point-point-point form
% new definition changes point of view: draw arc of smallest circle
% containing all three points.
vardef arcppp (expr first, second, third) =
 if (first=second) and (second=third):
   message "Undefined arc. ";
   first--third % trivial path.
 elseif (first=second) or (second=third): % but first <> third
   arc(0.5[first,third],first, 180)
 elseif first = third: % but second is different
   arc(0.5[first,second],first, 360)
 else: % all three different
   save w, sweep, hs, critical;
   pair w[]; numeric sweep, hs[], critical;
   w1 := first - third;
   w2 := second - first;
   w3 := second - third;
   % new code for finding sweep, never goes over 360
   hs1:=angle( w3 rotated (-angle w1)); % angle of corner at third
   hs2:=angle(-w1 rotated (-angle w2)); % angle of corner at first
   sweep:=2(hs1+hs2);                   % between -360 and 360
   critical:=5;
   if abs(sweep) <= critical:  % center may blow out
    % new code is actually a circular arc (almost)
    first{w2 rotated -hs1}..second{w2 rotated hs1}..third{-w3 rotated hs2}
   else:
    save m, d, center; pair m[], d[], center;
    m1:=(0.5)[first,second]; d1:=(second-first) rotated 90;
    m2:=(0.5)[second,third]; d2:=(third-second) rotated 90;
    center = m1+whatever*d1 = m2+whatever*d2;
    arc (center,first,sweep)
   fi
 fi
enddef;

%% Polar Coordinates.
% coordinate-independent.

% polar to rectangular conversion:  (r,th) -> (x,y) = r * dir (th).

vardef polar (expr p) =
  (xpart p) * dir (ypart p)
enddef;


%% Turtle.
% coordinate-independent.

% Argument t is a list of graph coord. pairs.
% First item in list t is starting point,
% rest are successive moves.

vardef turtle (text t) =
 save p_; pair p_[]; p_:=0;
 for a_=t:
  p_[incr p_] := if p_<>1:  p_[p_-1] +  fi  a_;
 endfor
 mkpoly (false, p_)
enddef;


%% Sectors.
% coordinate-independent.

vardef sector (expr center, rad, frtheta, totheta) =
 save from; pair from;
 from:=center+rad*(dir frtheta);
 center -- arc (center,from,totheta-frtheta) -- cycle
enddef;

% This is just for fun.
% piechart calculates the wedges from the text parameter data (a list
% of positive numerics)
% sgn    is +/-1, -1 means to proceed clockwise to the next wedge
% angle  is the starting angle of the starting wedge.
% cent   is the center of the pie
% rad    is its radius
% the value of the center is left in piecenter, the wedges are paths
% piewedge1, piewedge2, etc. The direction down the center of
% piewedge[j] is piedirection[j] and the starting angle is pieangle[j].
vardef piechart (expr sgn, ang, cent, rad) (text data) =
 save sum_, tot_;
 numeric piewedge; piewedge:=0;
 numeric sum_[]; sum_[0]:=0;
 % Get the partial sums. These will be converted to angles, so if
 % the piechart goes clockwise, use negative values
 for val_ = data :
   sum_[incr piewedge] := sum_[piewedge-1] + sgn*val_;
 endfor
 tot_ := sgn*sum_[piewedge]; % tot_ is positive.
 pair piecenter;
 piecenter:=cent;
 % Now define the wedges and record the direction of each:
 path piewedge[];
 pair piedirection[];
 numeric pieangle[]; pieangle1:=ang;
 for n_ = 1 upto piewedge:
   pieangle[n_ + 1] := ang + sum_[n_]/tot_*360;
   piewedge[n_] =
       sector(cent, rad, pieangle[n_], pieangle[n_+1]);
   piedirection[n_] := dir(0.5[ pieangle[n_], pieangle[n_+1] ]);
 endfor
enddef;

% I'm told that there are better ways (than piecharts) to represent
% quantitative data. Perhaps barcharts are what they meant.
% barchart calculates the bars from the text parameter, data.
% start  is the location (on the axis) of the start of the first bar.
%  sep   is the separation between bar centers.
%   r    is barwidth/sep
% vert   is boolean: true if bars are vertical, growing up from x-axis.
% data   is a list of heights.
def barchart (expr start, sep, r, vert)(text data) =
 numeric barlength[], barstart[], chartbar, barwd;
 path chartbar[];
 chartbar := 0;
 barwd := r*sep;
 for item_ = data :
   barlength[incr chartbar] := item_;
   barstart[chartbar] := start + sep*(chartbar-1);
   if vert :
     chartbar[chartbar] :=
       rect ((barstart[chartbar], 0),
             (barstart[chartbar] + barwd, barlength[chartbar]) );
   else :
     chartbar[chartbar] :=
       rect ( (0, barstart[chartbar]),
              (barlength[chartbar], barstart[chartbar] + barwd) );
   fi
 endfor
enddef;

%% Utility Functions.
% coordinate-independent.

% the identity function.
def id (expr x) =
  x
enddef;


%% Plotting of functions.
% coordinate-independent.

% In these macros, if the boolean argument `smooth' then the path
% returned will be a B\'ezier, otherwise it will be a polyline.
% If a tens parameter exists, then the smooth version will have
% that value of tension, otherwise the MF default of 1 is used.

% In mkfcn, for example, the parameter values determining the points
% through which the path interpolates begin at bmin, and step by bst
% until bmax is reached or exceeded. If bmax - bmin is not a whole
% number of bst, then bst is adjusted so it is.

% mkfcn: convert function `pf : (x, y) = pf (t)' to a path.
% pf : numeric -> pair.
% domain interpreted as parameter values,
% codomain as cartesian coordinate pairs.

vardef mkfcn (expr smooth, tens) (expr bmin, bmax, bst) (text pf) =
 save p_; pair p_[]; p_ := 0;
 save dx_,n_,bv_; numeric dx_,n_,bv_;
 % Handle errors and ensure p doesn't exceed infinity:
 if bmax > bmin :
    dx_ := max(abs(bst), nottoosmall*(bmax - bmin));
    dx_ := min(dx_, bmax - bmin);
 elseif bmax < bmin : % dx_ will be negative
    dx_ := min(-abs(bst), nottoosmall*(bmax - bmin));
    dx_ := max(dx_, bmax - bmin);
 else :
    dx_ := 1;
 fi
 n_:=round((bmax-bmin)/dx_);
 if not (n_=0) :
   dx_:=(bmax-bmin)/n_;
   bv_:=bmin;
 fi
 for i = 0 upto n_-1 :
   p_[incr p_] := pf(bv_);
   bv_:= bv_ + dx_;
 endfor
 % force n_th iteration to be at bmax (dx_ could be rounded up)
 p_[incr p_] := pf(bmax);
 save cur_tension; cur_tension:=tens;
 mkpath (smooth, false, p_)
enddef;

let tfcn = mkfcn;

% parafcn: like tfcn, but the text argument is a literal pair
% expression `(fa(t), fb(t))' in the literal `t'.

def parafcn (expr smooth) =
  tparafcn (smooth, cur_tension)
enddef;
vardef tparafcn (expr smooth, tens) (expr bmin, bmax, bst) (text pf) =
   save fp_;
   vardef fp_ (expr t) =
     pf
   enddef;
   mkfcn (smooth,tens) (bmin, bmax, bst) (fp_)
enddef;

% xfcn: convert function `fx : y = f (x)' to a path.
% fx : numeric -> numeric.
% domain interpreted as X coordinate,
% codomain as Y coordinate.
% Return the path.

vardef xfcn (expr smooth) (expr xmin, xmax, st) (text fx) =
   save fp_;
   vardef fp_ (expr x) =
     (x, fx(x))
   enddef;
   mkfcn (smooth,cur_tension) (xmin, xmax, st) (fp_)
enddef;

% function: like xfcn, but the text argument is a literal numeric
% expression `fx(x)' in the literal `x'.

def function (expr smooth) =
  tfunction (smooth, cur_tension)
enddef;
vardef tfunction (expr smooth, tens) (expr xmin, xmax, st) (text fx) =
   save fp_;
   vardef fp_ (expr x) =
     (x, fx)
   enddef;
   mkfcn (smooth,tens) (xmin, xmax, st) (fp_)
enddef;

% rfcn: convert function `ft : r = ft (\theta)' to a path.
% ft : numeric -> numeric.
% domain interpreted as angle in degrees,
% codomain as radial distance from origin.

vardef rfcn (expr smooth) (expr tmin, tmax, st) (text ft) =
   save fq_;
   vardef fq_ (expr t) =
     (ft(t)) * (dir t)
   enddef;
   mkfcn (smooth,cur_tension) (tmin, tmax, st) (fq_)
enddef;

% plrfcn: like rfcn, but the text argument is a literal numeric
% expression `ft(t)' in the literal `t' which represents the
% angle \theta.

def plrfcn (expr smooth) =
   tplrfcn (smooth, cur_tension)
enddef;
vardef tplrfcn (expr smooth, tens) (expr tmin, tmax, st) (text ft) =
   save fq_;
   vardef fq_ (expr t) =
     (ft) * (dir t)
   enddef;
   mkfcn (smooth, tens) (tmin, tmax, st) (fq_)
enddef;

% Creating a path with increasing x from a list of points
% with increasing x coordinates.

numeric fcn_tension; fcn_tension:=1;

vardef fcncontrol (expr X,Y,Z) =
  save dl, dr, U; pair U;
  dl:= xpart (Y - X);
  dr:= xpart (Z - Y);
  % U is the direction the curve is to travel at Y,
  % it is a weighted average of the slopes on either side,
  % the shorter x-interval given more weight:

  U := unitvector ((Z - Y)*dl/dr + (Y - X)*dr/dl);

  % The following is sufficient to keeps the curve moving right:
  % The x-values of the control points lie in the interval
  % xpart Y to xpart Z.
  %
  % Since U is a unit vector, the following puts the
  % control point not more than xpart(Z-Y) from Y, provided
  % fcn_tension >= 1.
  % This also keeps extreme points of the curve within a similar
  % distance of the extreme points of the list of points.

  Y + U*abs(xpart (Z - Y))/fcn_tension
enddef;

vardef functioncurve (expr ftens)(text t) =
  save fcn_tension; fcn_tension:=max(ftens, eps);
  save p_;textpairs (p_,t);
  mkcontrolledfcn (p_)
enddef;

vardef  mkcontrolledfcn (suffix pts) =
  % Extend pts by one on each side by symmetry
  pts[0] := 2pts[1] - pts[2];
  pts[pts+1] := 2pts[pts] - pts[pts-1];
  for i_ = 1 upto pts - 1 :
    pts[i_]..controls fcncontrol (pts[i_-1], pts[i_], pts[i_+1]) and
    fcncontrol (pts[i_+2],pts[i_+1], pts[i_])..
    endfor pts[pts]
enddef;

% This is a useful little utility to draw the points on top of the
% curve through them.

vardef plotnodes (expr symbol, size) expr f =     % f is a path
  save one_symbol; picture one_symbol;
  one_symbol := makesymbol (symbol, size);
  for i = 0 upto (length f) if cycle f: -1 fi :
    picdot (active_plane, one_symbol, zconv(point i of f));
  endfor
  if ClipOn:
    clipsto (active_plane, ClipPath);
  fi
  f
enddef;

%% Overlays - taken from MFbook, p 295.  (Bruce Leban)

picture totalpicture;
boolean totalnull, currentnull;

def clearit =
  currentpicture := totalpicture := nullpicture;
  currentnull := totalnull := true;
enddef;

def keepit =
  mono (currentpicture);
  addto totalpicture also currentpicture;
  currentpicture := nullpicture;
  totalnull := currentnull;
  currentnull := true;
enddef;

def addto_currentpicture =
  currentnull := false;
  addto currentpicture
enddef;

def mergeit (text do) =
  if totalnull:
    do currentpicture
  elseif currentnull:
    do totalpicture
  else:
    begingroup
      save v_; picture v_;
      v_:=currentpicture;
      mono (v_);
      addto v_ also totalpicture;
      do v_
    endgroup
  fi
enddef;

def shipit =
  mergeit (shipout)
enddef;

def showit_ =
  mergeit (show_)
enddef;

def show_ suffix v =
  display v inwindow currentwindow
enddef;

% initialize gcode for mfpic files,
numeric gcode; gcode:=0;

%%%
%%%  end  grafbase.mf
%%%
