summaryrefslogtreecommitdiff
path: root/metapost
diff options
context:
space:
mode:
authorHans Hagen <pragma@wxs.nl>2013-05-09 15:23:00 +0200
committerHans Hagen <pragma@wxs.nl>2013-05-09 15:23:00 +0200
commitcc045f4b1cedad65d90a220c1075d9bdaa1e3365 (patch)
tree195b781899214cd7a5ef47680901ab79605f54c1 /metapost
parent9e236867a2a9787c0faa0ede827a1cd9d5031ce9 (diff)
downloadcontext-cc045f4b1cedad65d90a220c1075d9bdaa1e3365.tar.gz
beta 2013.05.09 15:23
Diffstat (limited to 'metapost')
-rw-r--r--metapost/context/base/mp-grap.mpiv929
1 files changed, 924 insertions, 5 deletions
diff --git a/metapost/context/base/mp-grap.mpiv b/metapost/context/base/mp-grap.mpiv
index 6b1f2311f..a101b7ffe 100644
--- a/metapost/context/base/mp-grap.mpiv
+++ b/metapost/context/base/mp-grap.mpiv
@@ -15,10 +15,897 @@ if known context_grap : endinput ; fi
boolean context_grap ; context_grap := true ;
-% Instead we could include graph here and then clean it up as well as use private
-% variables in the grap_ namespace. After all, graph is frozen.
+%input graph.mp ;
+
+% Below is a modified graph.mp, starting with a change of names from "G..._" to "graph_..."
+%
+% Next, the use of marith macros are to be eliminated...
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% $Id: graph.mp,v 1.2 2004/09/19 21:47:10 karl Exp $
+% Public domain.
+
+% Macros for drawing graphs
+
+% begingraph(width,height) begin a new graph
+% setcoords(xtype,ytype) sets up a new coordinate system (log,-linear..)
+% setrange(lo,hi) set coord ranges (numeric and string args OK)
+% gdraw <file or path> [with...] draw a line in current coord system
+% gfill <file or path> [with...] fill a region using current coord system
+% gdrawarrow .., gdrawdblarrow.. like gdraw, but with 1 or 2 arrowheads
+% Mreadpath(<filename>) read path from file and return it in Mlog form
+% augment<path name>(loc) append given coordinates to a polygonal path
+% glabel<suffix>(pic,loc) place label pic near graph coords or time loc
+% gdotlabel<suffix>(pic,loc) same with dot
+% OUT loc value for labels relative to whole graph
+% gdata(file,s,text) read coords from file; evaluate t w/ tokens s[]
+% auto.<x or y> default x or y tick locations (for interation)
+% itick.<bot|top|..>(fmt,u) draw inward tick from given side at u w/ format
+% otick.<bot|top|..>(fmt,u) draw outward tick at coord u; label format fmt
+% grid.<bot|top|..>(fmt,u) draw grid line at u with given side labelled
+% autogrid([itick|.. bot|..],..) iterate over auto.x, auto.y, drawing tick/grids
+% frame.[bot|top..] draw frame (or one side of the frame)
+% endgraph end of graph--the result is a picture
+
+% option `plot <picture>' draws picture at each path knot, turns off pen
+% Gtemplate.<tickcmd> template paths for tick marks and grid lines
+% graph_margin_fraction.low, graph_margin_fraction.high fractions determining margins when no setrange
+% Glmarks[], Gumarks, Gemarks loop text strings used by auto.<x or y>
+% Gmarks, Gminlog numeric parameters used by auto.<x or y>
+% Gpaths tells how to interpret paths: log or linear
+% Autoform is the format string used by autogrid
+
+% Other than the above-documented user interface, all externally visible names
+% are of the form X_.<suffix>, Y_.<suffix>, or Z_.<suffix>, or they start
+% with `graph_' (was: "`G' and end with `_'").
+
+
+if unknown Mzero:
+ begingroup interim % marith.mp starts with `warningcheck:=0'
+ input marith
+ endgroup; % restore warningcheck; we zero it when necessary
+fi
+if unknown mant_font:
+ input format
+fi
+
+
+vardef graph_error(expr x,s) =
+ interim showstopping:=0;
+ show x; errmessage s;
+enddef;
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%% Data structures, begingraph %%%%%%%%%%%%%%%%%%%%%%%%
+
+vardef Z_@# = (X_@#,Y_@#) enddef; % used in place of plain.mp's z convention
+
+def graph_suffix(suffix $) = % convert from x or y to X_ or Y_
+ if str$="x": X_ else: Y_ fi
+enddef;
+
+
+def begingraph(expr w, h) =
+ begingroup
+ save X_, Y_, graph_finished_graph, graph_current_graph, graph_current_bb, graph_autogrid_needed, graph_frame_needed, graph_rescaled;
+ save graph_last_drawn, graph_plot_picture, graph_label, graph_number_of_arrowheads;
+ picture graph_finished_graph, graph_current_graph, graph_current_bb, graph_last_drawn, graph_plot_picture, graph_label[];
+ boolean graph_autogrid_needed, graph_frame_needed, graph_rescaled;
+ graph_finished_graph = nullpicture; % the finished part of the graph
+ graph_current_graph = nullpicture; % what has been drawn in current coords
+ graph_current_bb = nullpicture; % picture whose bbox is graph_current_graph's w/ linewidths 0
+ X_.graph_coordinate_type = Y_.graph_coordinate_type = linear; % coordinate system for each axis
+ Z_.graph_dimensions = (w,h); % dimensions of graph not counting axes etc.
+ X_.sc = Y_.sc = 0; % Mlog(the amount graph_current_graph has been descaled by)
+ graph_autogrid_needed = true; % whether autogrid is needed
+ graph_frame_needed = true; % whether frame needs to be drawn
+ graph_rescaled = false; % set when graph_rescale rescales coordinates
+ graph_last_drawn = nullpicture; % result of last gdraw or gfill
+ graph_number_of_arrowheads = 0; % number of arrowheads for next gdraw
+enddef;
+
+% Additional variables not explained above:
+% Z_.low, Z_.high user-specified coordinate ranges in units used in graph_current_graph
+% graph_plot_picture a picture from the `plot' option known when plot allowed
+% graph_modified_lower, graph_modified_higher pairs giving bounds used in auto<x or y>
+% graph_exponent, graph_comma variables and macros used in auto<x or y>
+% Gc_ temporary macro used in auto<x or y> ????
+% graph_modified_bias an offset to graph_modified_lower and graph_modified_higher to ease computing exponents
+% graph_label[] labels to place around the whole graph when it is done
+% Some additional variables function as constants. Most can be modified by the
+% user to alter the behavior of these macros.
+% Not very modifiable: log, linear, graph_bbox_offset_pair, graph_frame_pair_a, graph_frame_pair_b, graph_margin_pair
+% Modifiable: Gtemplate.suffix, Glmarks[], Gumarks, Gemarks, Gmarks,
+% Gminlog, Gpaths, Autoform
+
+
+newinternal log, linear; % coordinate system codes
+newinternal Gpaths; % path interpretation parameter
+log:=1; linear:=2;
+Gpaths := linear;
+
+
+
+%%%%%%%%%%%%%%%%%%%%%% Coordinates: setcoords, setrange %%%%%%%%%%%%%%%%%%%%%%
+
+% Graph-related usr input is `user graph coordinates' as specified by arguments
+% to setcoords.
+% `Internal graph coordinates' are used for graph_current_graph, graph_current_bb, Z_.low, Z_.high.
+% Their meaning depends on the appropriate component of Z_.graph_coordinate_type:
+% log means internal graph coords = Mlog(user graph coords)
+% -log means internal graph coords = -Mlog(user graph coords)
+% linear means internal graph coords = Mexp(Mlog(user graph coords) Mdiv ?sc)
+% -linear means internal graph coords = -Mexp(Mlog(user graph coords) Mdiv ?sc)
+% (In the last two lines, `?sc' means X_.sc or Y_.sc as appropriate.)
+
+
+vardef graph_set_default_bounds = % Set default Z_.low, Z_.high
+ forsuffixes $=low,high:
+ (if known X_$: whatever else: X_$ fi, if known Y_$: whatever else: Y_$ fi)
+ = graph_margin_fraction$[llcorner graph_current_bb,urcorner graph_current_bb] + graph_margin_pair$;
+ endfor
+enddef;
+pair graph_margin_pair.low, graph_margin_pair.high;
+graph_margin_pair.high = -graph_margin_pair.low = (.00002,.00002);
+
+
+% Set $, $$, $$$ so that shifting by $ then transforming by $$ and then $$$
+% maps the essential bounding box of graph_current_graph into (0,0)..Z_.graph_dimensions. The
+% `essential bounding box' is either what Z_.low and Z_.high imply or the
+% result of ignoring pen widths in graph_current_graph.
+vardef graph_remap(suffix $,$$,$$$) =
+ save p_;
+ graph_set_default_bounds;
+ pair p_, $; $=graph_bbox_offset_pair-Z_.low;
+ p_ = (max(X_.high-X_.low,.9), max(Y_.high-Y_.low,.9));
+ transform $$, $$$;
+ forsuffixes #=$$,$$$: xpart#=ypart#=xypart#=yxpart#=0; endfor
+ (Z_.high+graph_bbox_offset_pair+$) transformed $$ = p_;
+ p_ transformed $$$ = Z_.graph_dimensions;
+enddef;
+graph_margin_fraction.low=-.07; % bbox fraction for default range start
+graph_margin_fraction.high=1.07; % bbox fraction for default range stop
+pair graph_bbox_offset_pair; graph_bbox_offset_pair=epsilon*(3,3); % allowance to avoid numerical trouble
+
+
+def graph_with_pen_and_color(expr q) =
+ withpen penpart q withcolor
+ if colormodel q=1:
+ false
+ elseif colormodel q=3:
+ (greypart q)
+ elseif colormodel q=5:
+ (redpart q, greenpart q, bluepart q)
+ elseif colormodel q=7:
+ (cyanpart q, magentapart q, yellowpart q, blackpart q)
+ fi
+enddef;
+
+% Add picture component q to picture @# and change part p to tp, where p is
+% something from q that needs coordinate transformation. The type of p is pair
+% or path.
+% Pair o is the value of p that makes tp (0,0). This implements the trick
+% whereby using 1 instead of 0 for th the width or height or the setbounds path
+% for a label picture supresses shifting in x or y.
+vardef old_graph_picture_conversion@#(expr q, o)(text tp) =
+ save p;
+ if stroked q:
+ path p; p=pathpart q;
+ addto @# doublepath tp graph_with_pen_and_color(q) dashed dashpart q;
+ elseif filled q:
+ path p; p=pathpart q;
+ addto @# contour tp graph_with_pen_and_color(q);
+ else:
+ interim truecorners:=0;
+ pair p; p=llcorner q;
+ if urcorner q<>p: p:=p+graph_coordinate_multiplication(o-p,urcorner q-p); fi
+ addto @# also q shifted ((tp)-llcorner q);
+ fi
+enddef;
+% TH: new version from code found at sarovar tracker. This makes
+% grdaw clip the result to the window defined with setrange
+vardef graph_picture_conversion@#(expr q, o)(text tp) =
+ save p, tp_geclipt;
+ picture tp_geclipt; tp_geclipt:=nullpicture;
+ if stroked q:
+ path p; p=pathpart q;
+ %%% --- SDV added
+ addto tp_geclipt doublepath tp graph_with_pen_and_color(q) dashed dashpart q;
+ clip tp_geclipt to origin--(xpart Z_.graph_dimensions,0)--Z_.graph_dimensions--(0, ypart Z_.graph_dimensions)--cycle;
+ addto @# also tp_geclipt;
+ %%%
+ %%% --- SDV deleted
+ %%addto @# doublepath tp graph_with_pen_and_color(q) dashed dashpart q;
+ %%%
+ elseif filled q:
+ path p; p=pathpart q;
+ addto @# contour tp graph_with_pen_and_color(q);
+ else:
+ interim truecorners:=0;
+ pair p; p=llcorner q;
+ if urcorner q<>p: p:=p+graph_coordinate_multiplication(o-p,urcorner q-p); fi
+ addto @# also q shifted ((tp)-llcorner q);
+ fi
+enddef;
+
+def graph_coordinate_multiplication(expr a,b) = (xpart a*xpart b, ypart a*ypart b) enddef;
+
+
+vardef graph_clear_bounds@# = numeric @#.low, @#.high; enddef;
+
+
+% Finalize anything drawn in the present coordinate system and set up a new
+% system as requested
+vardef setcoords(expr tx, ty) =
+ interim warningcheck:=0;
+ if length graph_current_graph>0:
+ save s, S, T;
+ graph_remap(s, S, T);
+ for q within graph_current_graph:
+ graph_picture_conversion.graph_finished_graph(q, -s, p shifted s transformed S transformed T);
+ endfor
+ graph_current_graph := graph_current_bb := nullpicture;
+ fi
+ graph_clear_bounds.X_; graph_clear_bounds.Y_;
+ X_.graph_coordinate_type:=tx; Y_.graph_coordinate_type:=ty;
+enddef;
+
+
+% Use scaling command cc to rescale everything in internal graph coords so that
+% if Mlog(user graph coords) is u then the internal graph coord value becomes
+% 10000/128. Assume u>=$sc+4Mten where $ is X_ or Y_, depending on whether cc
+% is xscaled or yscaled.
+vardef graph_rescale@#(expr u)(text cc) =
+ save v, P;
+ v = mexp(4Mten + (@#sc-u));
+ picture P; P=nullpicture;
+ for q within graph_current_graph: graph_picture_conversion.P(q, origin, p cc v cc 1/128); endfor
+ graph_current_graph := P;
+ graph_current_bb := graph_current_bb cc v cc 1/128;
+ forsuffixes $=low, high:
+ if known @#.$: @#.$:=@#.$*v/128; fi
+ endfor
+ @#sc:= Mabs u -1115.72742; % @#sc:=Mabs u+Mlog(128)-4Mten
+ graph_rescaled := true;
+enddef;
+
+
+% Convert x coordinate u from Mlog(user graph coords) to graph_coordinate_type=linear internal
+% graph coords. If the result would be uncomfortably large, use graph_rescale to
+% descale as needed.
+vardef graph_x_conversion primary u =
+ interim warningcheck:=0;
+ if unknown u: u
+ elseif u>X_.sc+4Mten:
+ graph_rescale.X_(u,xscaled);
+ 78.125
+ else: Mexp(u Mdiv X_.sc)
+ fi
+enddef;
+
+vardef graph_y_conversion primary u = % same as graph_x_conversion but u is a y coordinate
+ interim warningcheck:=0;
+ if unknown u: u
+ elseif u>Y_.sc+4Mten:
+ graph_rescale.Y_(u,yscaled);
+ 78.125
+ else: Mexp(u Mdiv Y_.sc)
+ fi
+enddef;
+
+
+% Set Z_.low and Z_.high to correspond to given range of user graph
+% coordinates. The text argument should be a sequence of pairs and/or strings
+% with 4 components in all.
+vardef setrange(text t) =
+ interim warningcheck:=0;
+ save r_; r_=0;
+ string r_[]s;
+ for x_=
+ for p_=t: if pair p_: xpart p_, ypart fi p_, endfor:
+ r_[incr r_] if string x_: s fi = x_;
+ if r_>2:
+ graph_set_bounds if r_=3: X_(graph_x_conversion) else: Y_(graph_y_conversion) fi(
+ r_[r_-2] if unknown r_[r_-2]: s fi, x_);
+ fi
+ exitif r_=4;
+ endfor
+enddef;
+
+
+% @# is X_ or Y_; $ is graph_x_conversion or graph_y_conversion; l and h are numeric or string
+% It would not be OK to set (@#low,@#high) to a pair expression because $ might
+% try to rescale @#low when evaluating the right-hand side for @#high.
+vardef graph_set_bounds@#(suffix $)(expr l, h) =
+ graph_clear_bounds@#;
+ if @#graph_coordinate_type>0:
+ @#low = if abs @#graph_coordinate_type<>log: $ fi Mlog_Str l;
+ @#high = if abs @#graph_coordinate_type<>log: $ fi Mlog_Str h;
+ else:
+ -@#high = if abs @#graph_coordinate_type<>log: $ fi Mlog_Str l;
+ -@#low = if abs @#graph_coordinate_type<>log: $ fi Mlog_Str h;
+ fi
+enddef;
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%% Converting path coordinates %%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Find the result of scanning path p and using macros tx and ty to adjust the
+% x and y parts of each coordinate pair. Boolean parameter c tells whether to
+% force the result to be polygonal.
+vardef graph_scan_path(expr p, c)(suffix tx, ty) =
+ if (str tx="") and (str ty=""): p
+ else:
+ save r_; path r_;
+ forever:
+ graph_rescaled := false;
+ r_ := graph_pair_adjust(point 0 of p, tx, ty)
+ if path p:
+ for t=1 upto length p:
+ if c: --
+ else: ..controls graph_pair_adjust(postcontrol(t-1) of p, tx, ty)
+ and graph_pair_adjust(precontrol t of p, tx, ty) ..
+ fi
+ graph_pair_adjust(point t of p, tx, ty)
+ endfor
+ if cycle p: &cycle fi
+ fi;
+ exitunless graph_rescaled;
+ endfor
+ if pair p: point 0 of fi r_
+ fi
+enddef;
+vardef graph_pair_adjust(expr p)(suffix tx, ty) = (tx xpart p, ty ypart p) enddef;
+
+
+% Convert path p from Mlog(user graph coords) to internal graph coords.
+% Boolean flag f says whether to force the result to be polygonal.
+vardef graph_Mlog_convert_user_to_internal_coordinates(expr f) primary p =
+ graph_scan_path(p, f,
+ if abs X_.graph_coordinate_type=linear: graph_x_conversion fi,
+ if abs Y_.graph_coordinate_type=linear: graph_y_conversion fi)
+ if X_.graph_coordinate_type<0: xscaled -1 fi
+ if Y_.graph_coordinate_type<0: yscaled -1 fi
+enddef;
+
+
+% Convert path p from user graph coords to internal graph coords.
+vardef graph_convert_user_path_to_internal primary p =
+ if Gpaths=log:
+ graph_Mlog_convert_user_to_internal_coordinates((abs X_.graph_coordinate_type<>log) or (abs Y_.graph_coordinate_type<>log)) p
+ else:
+ interim warningcheck:=0;
+ save t, u;
+ t=Mexp(-X_.sc); u=Mexp(-Y_.sc);
+ graph_scan_path(p, (abs X_.graph_coordinate_type<>linear) or (abs Y_.graph_coordinate_type<>linear),
+ if abs X_.graph_coordinate_type=log: Mlog fi,
+ if abs Y_.graph_coordinate_type=log: Mlog fi)
+ transformed (identity
+ if abs X_.graph_coordinate_type=linear: xscaled t fi
+ if abs Y_.graph_coordinate_type=linear: yscaled u fi
+ if X_.graph_coordinate_type<0: xscaled -1 fi
+ if Y_.graph_coordinate_type<0: yscaled -1 fi)
+ fi
+enddef;
+
+
+% Convert label location t_ from user graph coords to internal graph coords.
+% The label location should be a pair, or two numbers/strings. If t_ is empty
+% or a single item of non-pair type, just return t_. Unknown coordinates
+% produce unknown components in the result.
+vardef graph_label_convert_user_to_internal(text t_) =
+ save n_; n_=0;
+ interim warningcheck:=0;
+ if 0 for x_=t_: +1 if pair x_: +1 fi endfor <= 1:
+ t_
+ else:
+ n_0 = n_1 = 0;
+ point 0 of graph_Mlog_convert_user_to_internal_coordinates(true) (
+ for x_=
+ for y_=t_: if pair y_: xpart y_, ypart fi y_, endfor
+ 0, 0:
+ if known x_: Mlog_Str x_
+ else: hide(n_[n_]:=whatever) Mzero
+ fi
+ exitif incr n_=2;
+ ,endfor) + (n_0,n_1)
+ fi
+enddef;
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Reading data files %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Read a line from file f, extract whitespace-separated tokens ignoring any
+% initial "%", and return true if at least one token is found. The tokens
+% are stored in @#1, @#2, .. with "" in the last @#[] entry.
+vardef graph_read_line@#(expr f) =
+ save n_, s_; string s_;
+ s_ = readfrom f;
+ string @#[];
+ if s_<>EOF:
+ @#1 := loptok s_;
+ n_ = if @#1="%": 0 else: 1 fi;
+ forever:
+ @#[incr n_] := loptok s_;
+ exitif @#[n_]="";
+ endfor
+ @#1<>""
+ else: false
+ fi
+enddef;
+
+
+% Execute c for each line of data read from file f, and stop at the first
+% line with no data. Commands c can use line number i and tokens $1, $2, ...
+def gdata(expr f)(suffix $)(text c) =
+ for i=1 upto infinity:
+ exitunless graph_read_line$(f);
+ c
+ endfor
+enddef;
+
+
+% Read a path from file f and return it in Mlog form. The path is terminated
+% by blank line or EOF.
+vardef Mreadpath(expr f) =
+ interim warningcheck:=0;
+ save s;
+ gdata(f, s, if i>1:--fi
+ if s2="": (Mlog i, Mlog_str s1)
+ else: (Mlog_str s1, Mlog_str s2) fi)
+enddef;
+
+
+% Append coordinates t to polygonal path @#. The coordinates can be numerics,
+% strings, or a single pair.
+vardef augment@#(text t) =
+ interim warningcheck := 0;
+ if not path begingroup @# endgroup:
+ Gerr(begingroup @# endgroup, "Cannot augment--not a path");
+ else:
+ def graph_comma= hide(def graph_comma=,enddef) enddef;
+ if known @#: @#:=@#-- else: @#= fi
+ (for p=t:
+ graph_comma if string p: Mexp Mlog_str fi p
+ endfor);
+ fi
+enddef;
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Drawing and filling %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Unknown pair components are set to 0 because glabel and gdotlabel understand
+% unknown coordinates as `0 in absolute units'.
+vardef graph_unknown_pair_bbox(expr p) =
+ if known p: addto graph_current_bb doublepath p;
+ else:
+ save x,y;
+ z = llcorner graph_current_bb;
+ if unknown xpart p: xpart p= else: x:= fi 0;
+ if unknown ypart p: ypart p= else: y:= fi 0;
+ addto graph_current_bb doublepath (p+z);
+ fi
+ graph_current_bb := image(fill llcorner graph_current_bb..urcorner graph_current_bb--cycle);
+enddef;
+
+
+% Initiate a gdraw or gfill command. This must be done before scanning the
+% argument, because that could invoke the `if known graph_plot_picture' test in a following
+% plot option .
+def graph_addto = graph_last_drawn:=graph_plot_picture:=nullpicture; addto graph_last_drawn enddef;
+
+
+% Handle the part of a Gdraw command that uses path or data file p.
+def graph_draw expr p =
+ if string p: graph_Mlog_convert_user_to_internal_coordinates(true) Mreadpath(p)
+ elseif path p or pair p: graph_convert_user_path_to_internal p
+ else: graph_error(p,"gdraw argument should be a data file or a path")
+ origin
+ fi
+ withpen currentpen graph_withlist _op_
+enddef;
+
+
+% Handle the part of a Gdraw command that uses path or data file p.
+def graph_fill expr p =
+ if string p: graph_Mlog_convert_user_to_internal_coordinates(true) Mreadpath(p) --cycle
+ elseif cycle p: graph_convert_user_path_to_internal p
+ else: graph_error(p,"gfill argument should be a data file or a cyclic path")
+ origin..cycle
+ fi graph_withlist _op_
+enddef;
+
+def gdraw = graph_addto doublepath graph_draw enddef;
+def gfill = graph_addto contour graph_fill enddef;
+
+
+% This is used in graph_draw and graph_fill to allow postprocessing graph_last_drawn
+def graph_withlist text t_ = t_; graph_post_draw; enddef;
+
+
+% Set graph_plot_picture so the postprocessing step will plot picture p at each path knot.
+% Also select nullpen to supress stroking.
+def plot expr p =
+ if known graph_plot_picture:
+ withpen nullpen
+ hide (graph_plot_picture:=image(
+ if bounded p: for q within p: graph_addto_currentpicture q endfor % Save memory
+ else: graph_addto_currentpicture p
+ fi graph_setbounds origin..cycle))
+ fi
+enddef;
+
+% This hides a semicolon that could prematurely end graph_withlist's text argument
+def graph_addto_currentpicture primary p = addto currentpicture also p; enddef;
+def graph_setbounds = setbounds currentpicture to enddef;
+
+
+def gdrawarrow = graph_number_of_arrowheads:=1; gdraw enddef;
+def gdrawdblarrow = graph_number_of_arrowheads:=2; gdraw enddef;
+
+
+% Post-process the filled or stroked picture graph_last_drawn as follows: (1) update
+% the bounding box information; (2) transfer it to graph_current_graph unless the pen has
+% been set to nullpen to disable stroking; (3) plot graph_plot_picture at each knot.
+vardef graph_post_draw =
+ save p;
+ path p; p=pathpart graph_last_drawn;
+ graph_unknown_pair_bbox(p);
+ if filled graph_last_drawn or not graph_is_null(penpart graph_last_drawn):
+ addto graph_current_graph also graph_last_drawn;
+ fi
+ if length graph_plot_picture>0:
+ for i=0 upto length p if cycle p: -1 fi:
+ addto graph_current_graph also graph_plot_picture shifted point i of p;
+ endfor
+ picture graph_plot_picture;
+ fi
+ if graph_number_of_arrowheads>0:
+ graph_draw_arrowhead(p, graph_with_pen_and_color(graph_last_drawn));
+ if graph_number_of_arrowheads>1: graph_draw_arrowhead(reverse p, graph_with_pen_and_color(graph_last_drawn)); fi
+ graph_number_of_arrowheads:=0;
+ fi
+enddef;
+vardef graph_is_null(expr p) = (urcorner p=origin) and (llcorner p=origin) enddef;
+
+
+vardef graph_draw_arrowhead(expr p)(text w) = % Draw arrowhead for path p, with list w
+ addto graph_current_graph also
+ image(filldraw arrowhead(
+ graph_arrowhead_extent(precontrol infinity of p, point infinity of p)) w;
+ graph_setbounds point infinity of p..cycle);
+enddef;
+
+vardef graph_arrowhead_extent(expr p, q) =
+ if p<>q: (q - 100pt*unitvector(q-p)) -- fi
+ q
+enddef;
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Drawing labels %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Argument c is a drawing command that needs an additonal argument p that gives
+% a location in internal graph coords. Draw in graph_current_graph enclosed in a setbounds
+% path. Unknown components of p cause the setbounds path to have width or
+% height 1 instead of 0. Then graph_unknown_pair_bbox sets these components to 0 and graph_picture_conversion
+% supresses subsequent repositioning.
+def graph_draw_label(expr p)(suffix $)(text c) =
+ save sdim_; pair sdim_;
+ sdim_ := (if unknown xpart p: 1+ fi 0, if unknown ypart p: 1+ fi 0);
+ graph_unknown_pair_bbox(p);
+ addto graph_current_graph also
+ image(c(p); graph_setbounds p--p+sdim_--cycle) _op_
+enddef;
+
+
+% Stash the result drawing command c in the graph_label table using with list w and
+% an index based on angle laboff$.
+vardef graph_stash_label(suffix $)(text c) text w =
+ graph_label[1.5+angle laboff$ /90] = image(c(origin) w);
+enddef;
+
+
+def graph_label_location primary p =
+ if pair p: graph_draw_label(p)
+ elseif numeric p: graph_draw_label(point p of pathpart graph_last_drawn)
+ else: graph_stash_label
+ fi
+enddef;
+
+
+% Place label p at user graph coords t using with list w. (t is a time, a pair
+% or 2 numerics or strings).
+vardef glabel@#(expr p)(text t) text w =
+ graph_label_location graph_label_convert_user_to_internal(t) (@#,label@#(p)) w; enddef;
+
+
+% Place label p at user graph coords t using with list w and draw a dot there.
+% (t is a time, a pair, or 2 numerics or strings).
+vardef gdotlabel@#(expr p)(text t) text w =
+ graph_label_location graph_label_convert_user_to_internal(t) (@#,dotlabel@#(p)) w; enddef;
+
+
+def OUT = enddef; % location text for outside labels
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%% Grid lines, ticks, etc. %%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Grid lines and tick marks are transformed versions of the templates below.
+% In the template paths, (0,0) is on the edge of the frame and inward is to
+% the right.
+path Gtemplate.itick, Gtemplate.otick, Gtemplate.grid;
+Gtemplate.itick = origin--(7bp,0);
+Gtemplate.otick = (-7bp,0)--origin;
+Gtemplate.grid = origin--(1,0);
+
+vardef itick@#(expr f,u) text w = graph_tick_label(@#,@,false,f,u,w); enddef;
+
+vardef otick@#(expr f,u) text w = graph_tick_label(@#,@,false,f,u,w); enddef;
+
+vardef grid@#(expr f,u) text w = graph_tick_label(@#,@,true,f,u,w); enddef;
+
+
+% Produce a tick or grid mark for label suffix $, Gtemplate suffix $$,
+% coordinate value u, and with list w. Boolean c tells whether Gtemplate$$
+% needs scaling by X_.graph_dimensions or Y_.graph_dimensions, and f gives a format string or a label
+% picture.
+def graph_tick_label(suffix $,$$)(expr c, f, u)(text w) =
+ graph_draw_label(graph_label_convert_user_to_internal(graph_generate_label_position($,u)),,draw graph_gridline_picture$($$,c,f,u,w) shifted)
+enddef;
+
+
+% Generate label positioning arguments appropriate for label suffix $ and
+% coordinate u.
+def graph_generate_label_position(suffix $)(expr u) =
+ if xpart laboff.$=0: u,whatever else: whatever,u fi
+enddef;
+
+
+% Generate a picture of a grid line labeled with coordinate value u, picture
+% or format string f, and with list w. Suffix @# is bot, top, lft, or rt,
+% suffix $ identifies entries in the Gtemplate table, and boolean c tells
+% whether to scale Gtemplate$.
+vardef graph_gridline_picture@#(suffix $)(expr c, f, u)(text w) =
+ if unknown u: graph_error(u,"Label coordinate should be known"); nullpicture
+ else:
+ save p; path p;
+ interim warningcheck:=0;
+ graph_autogrid_needed:=false;
+ p = Gtemplate$ zscaled -laboff@#
+ if c: Gxyscale fi
+ shifted (((.5 + laboff@# dotprod (.5,.5)) * laboff@#) Gxyscale);
+ image(draw p w;
+ label@#(if string f: format(f,u) else: f fi, point 0 of p))
+ fi
+enddef;
+def Gxyscale = xscaled X_.graph_dimensions yscaled Y_.graph_dimensions enddef;
+
+
+% Draw the frame or the part corresponding to label suffix @# using with list w.
+vardef frame@# text w =
+ graph_frame_needed:=false;
+ picture p_;
+ p_ = image(draw
+ if str@#<>"": subpath round(angle laboff@#*graph_frame_pair_a+graph_frame_pair_b) of fi
+ unitsquare Gxyscale w);
+ graph_draw_label((whatever,whatever),,draw p_ shifted);
+enddef;
+pair graph_frame_pair_a; graph_frame_pair_a=(1,1)/90; % unitsquare subpath is linear in label angle
+pair graph_frame_pair_b; graph_frame_pair_b=(.75,2.25);
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%% Automatic grid selection %%%%%%%%%%%%%%%%%%%%%%%%%%
+
+string Glmarks[]; % marking options per decade for logarithmic scales
+string Gumarks; % mark spacing options per decade for linear scales
+string Gemarks; % exponent spacing options for logarithmic scales
+newinternal Gmarks, Gminlog;
+Gmarks := 4; % minimum number marks generated by auto.x or auto.y
+Gminlog := 3.0; % revert to uniform marks when largest/smallest < this
+
+def Gfor(text t) = for i=t endfor enddef; % to shorten the mark templates below
+Glmarks[1]="1,2,5";
+Glmarks[2]="1,1.5,2,3,4,5,7";
+Glmarks[3]="1Gfor(6upto10:,i/5)Gfor(5upto10:,i/2)Gfor(6upto9:,i)";
+Glmarks[4]="1Gfor(11upto20:,i/10)Gfor(11upto25:,i/5)Gfor(11upto19:,i/2)";
+Glmarks[5]="1Gfor(21upto40:,i/20)Gfor(21upto50:,i/10)Gfor(26upto49:,i/5)";
+Gumarks="10,5,2"; % start with 10 and go down; a final `,1' is appended
+Gemarks="20,10,5,2,1";
+
+
+% Determine the X_ or Y_ bounds on the range to be covered by automatic grid
+% marks. Suffix @# is X_ or Y_. The result is log or linear to specify the
+% type of grid spacing to use. Bounds are returned in variables local to
+% begingraph..endgraph: pairs graph_modified_lower and graph_modified_higher are upper and lower bounds in
+% `modified exponential form'. In modified exponential form, (x,y) means
+% (x/1000)*10^y, where 1000<=abs x<10000.
+vardef graph_bounds@# =
+ interim warningcheck:=0;
+ save l, h;
+ graph_set_default_bounds;
+ if @#graph_coordinate_type>0: (l,h) else: -(h,l) fi = (@#low, @#high);
+ if abs @#graph_coordinate_type=log:
+ graph_modified_lower := Meform(Mabs l)+graph_modified_bias;
+ graph_modified_higher := Meform(Mabs h)+graph_modified_bias;
+ if h-l >=mlog Gminlog: log else: linear fi
+ else:
+ graph_modified_lower := Meform(@#sc + Mlog l)+graph_modified_bias;
+ graph_modified_higher := Meform(@#sc + Mlog h)+graph_modified_bias;
+ linear
+ fi
+enddef;
+pair graph_modified_bias; graph_modified_bias=(0,3);
+pair graph_modified_lower, graph_modified_higher;
+
+
+% Scan Glmarks[k] and evaluate tokens t for each m where l<=m<=h.
+def graph_scan_marks(expr k, l, h)(text t) =
+ for m=scantokens Glmarks[k]:
+ exitif m>h;
+ if m>=l: t fi
+ endfor
+enddef;
+
+
+% Scan Gmark[k] and evaluate tokens t for each m and e where m*10^e belongs
+% between l and h (inclusive), where both l and h are in modified exponent form.
+def graph_scan_mark(expr k, l, h)(text t) =
+ for e=ypart l upto ypart h:
+ graph_scan_marks(k, if e>ypart l: 1 else: xpart l/1000 fi,
+ if e<ypart h: 10 else: xpart h/1000 fi, t)
+ endfor
+enddef;
+
+
+% Select a k for which graph_scan_mark(k,...) gives enough marks.
+vardef graph_select_mark =
+ save k;
+ k = 0;
+ forever:
+ exitif unknown Glmarks[k+1];
+ exitif 0 graph_scan_mark(incr k, graph_modified_lower, graph_modified_higher, +1) >= Gmarks;
+ endfor
+ k
+enddef;
+
+
+% Try to select an exponent spacing from Gemarks. If successful, set @# and
+% return true
+vardef graph_select_exponent_mark@# =
+ numeric @#;
+ for e=scantokens Gemarks:
+ @# = e;
+ exitif floor(ypart graph_modified_higher/e)-floor(graph_modified_exponent_ypart(graph_modified_lower)/e) >= Gmarks;
+ numeric @#;
+ endfor
+ known @#
+enddef;
+
+vardef graph_modified_exponent_ypart(expr p) = ypart p if xpart p=1000: -1 fi enddef;
+
+
+% Compute the mark spacing d between xpart graph_modified_lower and xpart graph_modified_higher.
+vardef graph_tick_mark_spacing =
+ interim warningcheck:=0;
+ save m, n, d;
+ m = Gmarks;
+ n = 1 for i=1 upto mlog(xpart graph_modified_higher-xpart graph_modified_lower)/Mten - mlog m/(Mten-epsilon):
+ *10 endfor;
+ if n<=1000:
+ for x=scantokens Gumarks:
+ d = n*x;
+ exitif 0 graph_generate_numbers(d,+1)>=m;
+ numeric d;
+ endfor
+ fi
+ if known d: d else: n fi
+enddef;
+
+
+def graph_generate_numbers(expr d)(text t) =
+ for m = d*ceiling(xpart graph_modified_lower/d) step d until xpart graph_modified_higher:
+ t
+ endfor
+enddef;
+
+
+% Evaluate tokens t for exponents e in multiples of d in the range determined
+% by graph_modified_lower and graph_modified_higher.
+def graph_generate_exponents(expr d)(text t) =
+ for e = d*floor(graph_modified_exponent_ypart(graph_modified_lower)/d+1)
+ step d until d*floor(ypart graph_modified_higher/d): t
+ endfor
+enddef;
+
+
+% Adjust graph_modified_lower and graph_modified_higher so their exponent parts match and they are in true
+% exponent form ((x,y) means x*10^y). Return the new exponent.
+vardef graph_match_exponents =
+ interim warningcheck := 0;
+ save e;
+ e+3 = if graph_modified_lower=graph_modified_bias: ypart graph_modified_higher
+ elseif graph_modified_higher=graph_modified_bias: ypart graph_modified_lower
+ else: max(ypart graph_modified_lower, ypart graph_modified_higher) fi;
+ forsuffixes $=graph_modified_lower, graph_modified_higher:
+ $ := (xpart $ for i=ypart $ upto e+2: /(10) endfor, e);
+ endfor
+ e
+enddef;
+
+
+% Assume e is an integer and either m=0 or 1<=abs(m)<10000. Find m*(10^e)
+% and represent the result as a string if its absolute value would be at least
+% 4096 or less than .1. It is OK to return 0 as a string or a numeric.
+vardef graph_factor_and_exponent_to_string(expr m, e) =
+ if (e>3)or(e<-4):
+ decimal m & "e" & decimal e
+ elseif e>=0:
+ if abs m<infinity/Ten_to[e]:
+ m*Ten_to[e]
+ else: decimal m & "e" & decimal e
+ fi
+ else:
+ save x; x=m/Ten_to[-e];
+ if abs x>=.1: x else: decimal m & "e" & decimal e fi
+ fi
+enddef;
+
+
+def auto suffix $ =
+ hide(def graph_comma= hide(def graph_comma=,enddef) enddef)
+ if graph_bounds.graph_suffix($)=log:
+ if graph_select_exponent_mark.graph_exponent: graph_generate_exponents(graph_exponent, graph_comma graph_factor_and_exponent_to_string(1,e))
+ else:
+ graph_scan_mark(graph_select_mark, graph_modified_lower, graph_modified_higher, graph_comma graph_factor_and_exponent_to_string(m,e))
+ fi
+ else:
+ hide(graph_exponent:=graph_match_exponents)
+ graph_generate_numbers(graph_tick_mark_spacing, graph_comma graph_factor_and_exponent_to_string(m,graph_exponent))
+ fi
+enddef;
+
+
+string Autoform; Autoform = "%g";
+
+vardef autogrid(suffix tx, ty) text w =
+ graph_autogrid_needed:=false;
+ if str tx<>"": for x=auto.x: tx(Autoform,x) w; endfor fi
+ if str ty<>"": for y=auto.y: ty(Autoform,y) w; endfor fi
+enddef;
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% endgraph %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+def endgraph =
+ if graph_autogrid_needed: autogrid(otick.bot, otick.lft); fi
+ if graph_frame_needed: frame; fi
+ setcoords(linear,linear);
+ interim truecorners:=1;
+ for b=bbox graph_finished_graph:
+ setbounds graph_finished_graph to b;
+ for i=0 step .5 until 3.5:
+ if known graph_label[i]: addto graph_finished_graph also graph_label[i] shifted point i of b; fi
+ endfor
+ endfor
+ graph_finished_graph
+ endgroup
+enddef;
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
-input graph.mp ;
vardef roundd(expr x, d) =
if abs d > 4 :
@@ -79,7 +966,7 @@ enddef ;
% graph.mp: string Autoform; Autoform = "%g";
% graph.mp:
% graph.mp: vardef autogrid(suffix tx, ty) text w =
-% graph.mp: Gneedgr_:=false;
+% graph.mp: graph_autogrid_needed:=false;
% graph.mp: if str tx<>"": for x=auto.x: tx(Autoform,x) w; endfor fi
% graph.mp: if str ty<>"": for y=auto.y: ty(Autoform,y) w; endfor fi
% graph.mp: enddef;
@@ -88,7 +975,7 @@ enddef ;
% string Autoform_Y ; Autoform_Y := "@.0e" ;
vardef autogrid(suffix tx, ty) text w =
- Gneedgr_ := false ;
+ graph_autogrid_needed := false ;
if str tx <> "" :
for x=auto.x :
tx (
@@ -225,6 +1112,31 @@ enddef ;
% The following extensions are not specific to graph and could be moved to metafun...
+% sort a path
+
+def sortpath (suffix $) (text t) = % t can be "xpart", "ypart", "length", "angle", ...
+ if path $ :
+ if length $ > 0 :
+ save n, k ; n := length $ ;
+ for i=0 upto n :
+ k := i ;
+ for j=i+1 upto n :
+ if t (point j of $) < t (point k of $) :
+ k := j ;
+ fi
+ endfor
+ if k>i :
+ $ := if i>0 : subpath (0,i-1) of $ -- fi
+ point k of $ --
+ subpath (i,k-1) of $
+ if k<n : -- subpath (k+1,n) of $ fi
+ ;
+ fi
+ endfor
+ fi
+ fi
+enddef ;
+
% convert a polygon path to a smooth path (useful, e.g. as a guide to the eye)
def smoothpath (suffix $) =
@@ -440,6 +1352,8 @@ vardef exponential_fit (suffix p, $) (text t) =
fi
endfor
linear_fit(q,a,t) ;
+ save e ; e := exp(sqrt(fit_chi_squared)) ;
+ fit_chi_squared := e * e ;
$0 := a1 ;
$1 := exp(a0) ;
enddef ;
@@ -459,6 +1373,8 @@ vardef power_law_fit (suffix p, $) (text t) =
fi
endfor
linear_fit(q,a,t) ;
+ save e ; e := exp(sqrt(fit_chi_squared)) ;
+ fit_chi_squared := e * e ;
$0 := a1 ;
$1 := exp(a0) ;
enddef ;
@@ -491,6 +1407,8 @@ vardef gaussian_fit (suffix p, $) (text t) =
fi
endfor
polynomial_fit(q,a,2,if t > 0 : ln(t) else : 0 fi) ;
+ save e ; e := exp(sqrt(fit_chi_squared)) ;
+ fit_chi_squared := e * e ;
$1 := sqrt(-lntwo/a2) ;
$0 := -.5a1/a2 ;
$2 := exp(a0-.25*a1*a1/a2) ;
@@ -519,6 +1437,7 @@ vardef lorentzian_fit (suffix p, $) (text t) =
fi
endfor
polynomial_fit(q,a,2,if t <> 0 : 1/(t) else : 0 fi) ;
+ fit_chi_squared := 1/fit_chi_squared ;
$0 := -.5a1/a2 ;
$2 := 1/(a0-.25a1*a1/a2) ;
$1 := sqrt((a0-.25a1*a1/a2)/a2) ;