1
2# $Id$
3eval "exec perl -Ss $0 $@"
4	if 0;
5
6# A graphing preprocessor for GNU pic / troff package.
7# Hacked into existence by Larry McVoy (lm@sun.com now lm@sgi.com).
8# Copyright (c) 1994 Larry McVoy.  GPLed software.
9#
10# Input format is like that of Xgraph, i.e., sets of X Y pairs,
11# divided up by blank lines and titled with a "title.  Like so
12#
13#	1 1
14#	2 2
15#	"straight slope
16#
17#	4 4
18#	1 4
19#	"straight down
20#
21# Optional "quartile" data input format.
22# The drawing is ----- o  ---, with the lines being from y1..y2, y4..y5,
23# and the mark at y3.
24#
25#	x y1 y2 y3 y4 y5
26#	x y1 y2 y3 y4 y5
27#	x y1 y2 y3 y4 y5
28#
29# Optional input (superset of Xgraph) is like so:
30#
31#	%T Graph title in +4 point font
32#	%X X axis title and/or units in +2 point font
33#	%Y Y axis title and/or units in +2 point font
34#	%P Page title in +4 point font
35#	%fakemax-X <value>	force graph to be that big
36#	%fakemax-Y <value>	force graph to be that big
37#	%fakemin-X <value>	force graph to be that big
38#	%fakemin-Y <value>	force graph to be that big
39#
40# Options:
41#  -lm		implies -big -below -grid -close
42#  -rev		reverse X/Y data sense (and titles)
43#  -below	put data set titles below the graph rather than to the right
44#  -close	no extra space around the data
45#  -qline	connect the quartile center points
46#  -grid	grid :-)
47#  -halfgrid	Grid lines where the major ticks are
48#  -nobox	no box around whole graph
49#  -big		make the graph take the whole page
50#  -slide	make the graph fit in my slides
51#  -small	make the graph be small so you can do a lot of them.
52#  -notitle	no Title label
53#  -nolabels	no X/Y/Title labels
54#  -nodatal	no dataset labels
55#  -nomarks	no marks on the graphs.
56#  -nolines	no lines connecting the marks (don't use w/ -nomarks :-)
57#  -k		print (absolute) values larger than 1000 as (value/1000)K
58#  -grapheach	graph each data set separately
59#  -br_title	start a new graph at each title.
60#  -nospace	no .sp at top of picture
61#  -ts		time series, X axis is implied.
62#  -hist	produce a histogram graph
63#
64# Hacks :-)
65# -xk		multiply X input by 1024.
66# -xm		multiply X input by 1024*1024.
67# -logx		take the log base 2 of X input
68# -logy		take the log base 2 of Y input
69# -cut		add cut marks so that image croppers dont crop too close
70#
71# Much thanks to James Clark for providing such a nice replacement for
72# the Unix troff package.  Thanks to the Xgraph folks for providing
73# inspiration.  Thanks to Declan Murphy for math :-)
74# Thanks to noone for floating point numbers, they suck dog doo.
75# There are lots of hacks in here to deal with rounding errors.
76#
77# TODO:
78#	All of the option parsing done manually.
79#	A filter option to print ranges of the data?
80#	A way to do each data set in it's own graph.
81#	All of the other xgraph options?
82#	For Adam, that butthead, an option to sort the labels such that they
83#	are in the same order as the right endpoints of the data sets.
84
85&init;
86&autosize;
87&pic;
88exit;
89
90# init - slurp in the data and apply any transformations.
91sub init
92{
93	# Lint for the options.
94	$qline = $ts = $close = $nolines = $thk1 = $thk2 = $k = $notitle
95	= $thk1_5 = $xm = $grid = $nospace = $lm = $hist = 0 if 0;
96
97	if ($grapheach) { $grapheach = 1; $cut = 0; } else { $grapheach = 0; }
98	if ($halfgrid) { $halfgrid = 1; } else { $halfgrid = 0; }
99	if ($hist) { $nobox = 1; $nolabels = 1; $close = 1; $nolines = 1; }
100	if ($lm) { $big = $below = $grid = $close = 1; }
101
102	# Accept %options=value on the command line.
103	while ($ARGV[0] =~ /^%/) {
104		$_ = $ARGV[0];
105		s/=/ /;
106		push(@lines, "$_\n");
107		shift(@ARGV);
108	}
109
110	# OK, sometimes we get
111	#	%T title
112	#	%X X axis, etc.
113	#
114	#	"data set 1
115	#
116	# And this messes up the numbering later on.  So we carefully dump the
117	# whitespace between the control and data.
118	while (<>) {
119		last if /^\s*$/;
120		push(@lines, $_);
121		last if /^"/;
122		last if /^\d/;
123	}
124	push(@lines, <>);
125	$fake = "";
126	$items = 0;
127	$stat_sum = 0;
128	$min = 1.7E+308;
129	$max = 2.2E-308;
130	foreach (@lines) {
131		if (/^"?%fake/) {
132			$fake = $_;
133			s/"?%fakemax-//;
134			s/"?%fakemin-//;
135			@_ = split;
136			$_ = "$_[1] $_[1]";
137		} elsif (/^%hist\s/) {
138			@_ = split;
139			shift(@_);
140			($hist_bsize, $hist_low, $hist_high) = @_;
141			next;
142		} else {
143			next if /^\s*["%#]/;
144			next if /^\s*$/;
145		}
146		if ($ts) {
147			$_ = "$items $_";
148		}
149		$items++;
150		@_ = split;
151		if ($xk) {
152			$_[0] = $_[0] * 1024;
153		} elsif ($xm) {
154			$_[0] = $_[0] * 1024 * 1024;
155		}
156		if ($logx) {
157			$_[0] = &logbase(2, $_[0]);
158		}
159		if ($yk) {
160			$_[1] = $_[1] * 1024;
161		} elsif ($ym) {
162			$_[1] = $_[1] * 1024 * 1024;
163		}
164		if ($logy) {
165			$_[1] = &logbase(2, $_[1]);
166		}
167		if ($rev) {
168			$_ = "$_[1] $_[0]";
169			$y = $_[0];
170		} else {
171			$_ = "$_[0] $_[1]";
172			$y = $_[1];
173		}
174		$stat_sum += $y;
175		$max = $y if ($y > $max);
176		$min = $y if ($y < $min);
177		push(@y, $y);
178		if ($fake =~ /[XY]/) {
179			# XXX - reverse?  What should it do?
180			if ($fake =~ /fakemax-X/) {
181				$fakemax_X = $_[0];
182			} elsif ($fake =~ /fakemax-Y/) {
183				$fakemax_Y = $_[1];
184			} elsif ($fake =~ /fakemin-X/) {
185				$fakemin_X = $_[0];
186			} elsif ($fake =~ /fakemin-Y/) {
187				$fakemin_Y = $_[1];
188			}
189			$_ = $fake;
190			$fake = "";
191		}
192	}
193
194	# Do some statistics.
195	@s = sort(@y);
196	if ($items & 1) {
197		$stat_median = $s[($items + 1)/2];
198	} else {
199		$i = $items / 2;
200		$stat_median = ($s[$i] + $s[$i+1]) / 2;
201	}
202	$stat_avg = $stat_sum/$items;
203	$stat_avgdev = $stat_var = 0;
204	# $stat_skew = $stat_curt = 0;
205	foreach $_ (@lines) {
206		next if /^\s*["#%]/;
207		next if /^\s*$/;
208		@_ = split;
209		$stat_var += ($_[1] - $stat_median) ** 2;
210		$tmp = $_[1] - $stat_median;
211		$stat_avgdev += $tmp > 0 ? $tmp : -$tmp;
212	}
213	$stat_var /= $items - 1;
214	$stat_stddev = sqrt($stat_var);
215	$stat_avgdev /= $items;
216	if ($ts) {
217		printf STDERR "N=$items min=$min max=$max med=%.2f avg=%.2f stddev=%.2f avgdev=%.2f\n",
218		    $stat_median, $stat_avg, $stat_stddev, $stat_avgdev;
219	}
220
221	# Diddle this to create different marks.
222	@marks = (
223	    '[ "\s+2\(bu\s0" ]',
224	    '[ "\(sq" ]',
225	    '[ "\(*D" ]',
226	    '[ "\s+2\(pl\s0" ]',
227	    '[ "\(*F" ]',
228	    '[ "\s+2\fB\(mu\fP\s0" ]',
229	    '[ circle rad .035 fill 0 ]',
230	    '[ box ht .07 wid .07 fill 1 ]',
231	    '[ "\(dd" ]',
232	    );
233	$nmarks = $#marks + 1;
234	$nomark = '[ box invis ht .05 wid .05 ]';
235
236	$first_title = 1;
237
238	if ($nospace) {
239		$graphspace = "0";
240	} elsif ($small) {
241		$graphspace = ".15i";
242	} elsif ($medium) {
243		$graphspace = ".20i";
244	} else {
245		$graphspace = ".25i";
246	}
247
248	if ($small) {
249		$marks[0] = '[ circle rad .007 fill 1 ]';
250		$PS = 10;
251		$ft = "B";
252		$tick = .1;
253	} elsif ($medium) {
254		$PS = 11;
255		$ft = "HB";
256		$tick = .1;
257	} elsif ($slide) {
258		$ft = "HB";
259		$PS = 11;
260		$tick = .15;
261	} else {
262		$ft = "CB";
263		$PS = 12;
264		$tick = .15;
265	}
266	$thk = .75;
267	$thk = 1 if $thk1;
268	$thk = 1.5 if $thk1_5;
269	$thk = 2 if $thk2;
270	$thk = .2 if $thk_2;
271	$gthk = .25;
272	$gthk = 1 if $gthk1;
273	$gthk = .75 if $gthk_75;
274	$gthk = .5 if $gthk_5;
275	$lineinvis = $nolines ? "invis" : "";
276}
277
278# Calculate min/max to autosize the graph.
279sub autosize
280{
281	foreach $_ (@lines) {
282		next if /^\s*["#%]/;
283		next if /^\s*$/;
284		@_ = split;
285		if ($#_ == 1) {
286			$Ymax = $Ymin = $_[1];
287		} elsif ($#_ == 5) {	# Quartile plot
288			$Ymax = $Ymin = $_[1];
289			for ($i = 2; $i <= 5; ++$i) {
290				$Ymax = $_[$i] if ($Ymax < $_[$i]);
291				$Ymin = $_[$i] if ($Ymin > $_[$i]);
292			}
293		} else {
294			die "Data format error: $_\n";
295		}
296		if (!defined $xmin) {
297			$xmin = $_[0];
298			$xmax = $_[0];
299			$ymin = $Ymin;
300			$ymax = $Ymax;
301		}
302		else {
303			$xmin = $_[0] if ($xmin > $_[0]);
304			$xmax = $_[0] if ($xmax < $_[0]);
305			$ymin = $Ymin if ($ymin > $Ymin);
306			$ymax = $Ymax if ($ymax < $Ymax);
307		}
308	}
309
310	# Handle fake max
311	if (defined($fakemax_X) && $fakemax_X > $xmax) {
312		$xmax = $fakemax_X;
313	}
314	if (defined($fakemax_Y) && $fakemax_Y > $ymax) {
315		$ymax = $fakemax_Y;
316	}
317	if (defined($fakemin_X) && $fakemin_X < $xmin) {
318		$xmin = $fakemin_X;
319	}
320	if (defined($fakemin_Y) && $fakemin_Y < $ymin) {
321		$ymin = $fakemin_Y;
322	}
323	if ($hist) {
324		$xmax += $hist_bsize;
325	}
326	warn "n=$items xmin=$xmin xmax=$xmax ymin=$ymin ymax=$ymax\n" if $debug;
327	($xlower, $xupper, $xtick) = &tick($xmin, $xmax, $logx ? 2 : 10);
328	($ylower, $yupper, $ytick) = &tick($ymin, $ymax, $logy ? 2 : 10);
329	if ($ymax + $ytick*.45 < $yupper) {
330		$yupper -= $ytick;
331		$ypartial = $ymax - $yupper;
332	} else {
333		$ypartial = 0;
334	}
335	$xn = int(.9 + ($xupper - $xlower) / $xtick);
336	$yn = int(.9 + ($yupper - $ylower) / $ytick);
337	$xlower = sprintf("%.6f", $xlower);	# really ugly cast
338	$xupper = sprintf("%.6f", $xupper);	# really ugly cast
339	$xtick = sprintf("%.6f", $xtick);	# really ugly cast
340	$xn = sprintf("%.0f", $xn);		# really ugly cast
341	$ylower = sprintf("%.6f", $ylower);	# really ugly cast
342	$yupper = sprintf("%.6f", $yupper);	# really ugly cast
343	$ytick = sprintf("%.6f", $ytick);	# really ugly cast
344	$yn = sprintf("%.0f", $yn);		# really ugly cast
345}
346
347# Since I had to go rethink it, here's the explanation:
348#
349# log base e 10 = X implies e**x = 10
350# e ** (v * x) = (e ** x) ** v
351# since e ** x == 10, that implies e ** (v * x) is 10 ** v
352# Capeesh?
353sub expbase
354{
355	local($base, $val) = @_;
356
357	exp($val * log($base));
358}
359
360sub logbase
361{
362	local($base, $val) = @_;
363
364	if ($val == 0) {
365		return 0;
366	}
367	if ($val < 0) {
368		die "Input: $_: can't take log of negative value: $val\n";
369	}
370	log($val) / log($base);
371}
372
373# Figure out the tick marks.
374# XXX - the log stuff is not quite right.
375sub tick
376{
377	local($min, $max, $base) = @_;
378	local($delta, $adj, $lower, $upper, $tick);
379
380	$delta = $max - $min;
381	$tick = int(&logbase(10, $delta));
382	$tick = &expbase(10, $tick - 1);
383	if ($delta / $tick > 10) {
384		if ($base == 10) {
385			if (($delta / (2 * $tick)) > 15) {
386				$adj = 10;
387			} elsif (($delta / (2 * $tick)) > 10) {
388				$adj = 5;
389			} else {
390				$adj = 2;
391			}
392		} else {
393			$adj = 2;
394		}
395	} else {
396		$adj = 1;
397	}
398	$tick *= $adj;
399
400	# Go figure out the endpoints.  This is O(log10(n)) where N is the
401	# number of ticks from 0 to the min.
402	$lower = 0;
403	for ($i = 10e99; $i > 0; $i = int($i/$base)) {
404		$fudge = $i * $tick;
405		$bound = $min + $fudge * .00001;
406
407		# Sometimes it's too big
408		while ($lower > $bound) {
409			$lower -= $fudge;
410		}
411
412		# Sometimes it's too small
413		while (($lower + $fudge) <= $bound) {
414			$lower += $fudge;
415		}
416	}
417
418	if ($base == 2) {
419		if ($tick < 1) {
420			$tick = 1;
421		} else {
422			$tick = sprintf("%.0f", $tick);
423		}
424		$lower = sprintf("%.0f", $lower);
425	}
426	for ($upper = $lower; $upper < $max - $tick * .00001; $upper += $tick) {
427	}
428	if ($base == 2) {
429		$upper = sprintf("%.0f", $upper);
430	}
431	# If you don't like your end points on the border then do this.
432	unless ($close) {
433		if ($min - $lower < .1 * $tick) {
434			$lower -= $tick;
435		}
436		if ($max - $upper < .1 * $tick) {
437			$upper += $tick;
438		}
439	}
440	($lower, $upper, $tick);
441}
442
443# Spit out the pic stuff.
444# The idea here is to spit the variables and let pic do most of the math.
445# This allows tweaking of the output by hand.
446sub pic
447{
448	if ($k) {
449		$print = 'sprintf("%.0fK", j/1000)';
450	} else {
451		$print = 'sprintf("%.0f", j)';
452	}
453	if ($grid || $halfgrid) {
454		$nogrid = "dotted";
455	} else {
456		$nogrid = "invis";
457	}
458	if ($nobox) {
459		$nobox = "invis";
460	}
461	$log_x = $logx ? "logx = 1" : "logx = 0";
462	$log_y = $logy ? "logy = 1" : "logy = 0";
463	if ($big) {
464		print ".sp .5i\n.po .5i\n";
465		if ($below) {
466			$ysize = 7;
467		} else {
468			$ysize = 9;
469		}
470		if ($nodatal) {
471			$xsize = 7;
472		} else {
473			$xsize = 6;
474		}
475	} elsif ($small) {
476		$ysize = 1.75;
477		$xsize = 1.75;
478	} elsif ($medium) {
479		print ".po .52i\n";
480		$ysize = 1.9;
481		$xsize = 2.05;
482	} elsif ($slide) {
483		print ".sp .35i\n";
484		$xsize = 4.5;
485		$ysize = 4.1;
486	} else {
487		print ".sp 1i\n";
488		$ysize = 5;
489		$xsize = 5;
490	}
491	&graph;
492
493	# Mark the data points
494	@datasets = ();
495	for ($sub = 0; $sub <= $#lines; $sub++) {
496		$_ = $lines[$sub];
497		if (/^\s*$/) {		# end of data set
498			&data($set++);
499			if ($grapheach) {
500				&titles;
501				if ($small) {
502					if ($set == 4) {
503						print ".sp -11i\n";
504						print ".po 3.5i\n";
505					} elsif ($set == 8) {
506						print ".sp -11i\n";
507						print ".po 6i\n";
508					}
509				} else {	# ???
510					if ($set == 4) {
511						print ".sp -11i\n";
512						print ".po 3.15i\n";
513					} elsif ($set == 8) {
514						print ".sp -11i\n";
515						print ".po 5.8i\n";
516					}
517				}
518
519				if ($sub < $#lines) {
520					&graph;
521				}
522			}
523			next;
524		}
525		if (/^"?%fake/) {	# Skip this
526			next;
527		}
528		if (/^"?%T\s+/) {	# Title specification
529			# Spit out the last graph at next title.
530			if ($br_title && $graphs++ > 0) {
531				&titles;
532				if ($graphs == 5) {
533					print ".sp -11i\n";
534					print ".po 3.5i\n";
535				} elsif ($graphs == 9) {
536					print ".sp -11i\n";
537					print ".po 6i\n";
538				}
539				&graph;
540			}
541			s/^"?%T\s+//;
542			chop;
543			$Gtitle = $_;
544			next;
545		}
546		if (/^"?%X\s+/) {	# X axis title specification
547			s/^"?%X\s+//;
548			chop;
549			$Xtitle = $_;
550			next;
551		}
552		if (/^"?%Y\s+/) {	# Y axis title specification
553			s/^"?%Y\s+//;
554			chop;
555			$Ytitle = $_;
556			next;
557		}
558		if (/^"?%P\s+/) {	# Page title specification
559			s/^"?%P\s+//;
560			chop;
561			$Ptitle = $_;
562			#VERBOSE warn "Pt: $Ptitle\n";
563			next;
564		}
565		if (/^"/) {		# Data set title
566			s/^"//;
567			chop;
568			$dataset = $_;
569			push(@datasets, "$dataset");
570			next;
571		}
572		push(@data, $_);
573	}
574	unless ($grapheach) {
575		&data($set++);
576		&titles;
577	}
578	if (defined($Ptitle)) {
579		print ".po 1i\n.sp -12i\n.ps 20\n.ce 1\n";
580		print "$Ptitle\n";
581		print ".po 1i\n.sp -12i\n.sp 10.4i\n.ps 20\n.ce 1\n";
582		print "$Ptitle\n";
583	}
584}
585
586# Draw the titles and finish this graph.
587sub titles
588{
589	# Do X/Y titles, if any.
590	unless ($nolabels) {
591		$Xtitle = defined($Xtitle) ? $Xtitle : "X";
592		$Ytitle = defined($Ytitle) ? $Ytitle : "Y";
593		if ($rev && $first_title) {
594			$tmp = $Xtitle;
595			$Xtitle = $Ytitle;
596			$Ytitle = $tmp;
597		}
598		print "\n# Xaxis title.\n";
599		print "\"\\s+4$Xtitle\\s0\" rjust at O.se - (0, .6)\n";
600
601		print "\n# Yaxis title ($Ytitle)\n.ps +2\n";
602		$tmp = $Ytitle;
603		while (length($tmp) > 0) {
604			$tmp =~ s/(.)//;
605	    		print "\"$1\" ";
606		}
607		print "\\\n    at O.w - (.75, 0)\n.ps\n";
608
609	}
610
611	# Do the graph title, if any.
612	$Gtitle = defined($Gtitle) ? $Gtitle : "Pic Graph";
613	if ($grapheach) {
614		$Gtitle = $datasets[$#datasets];
615		print "\n# Graph title.\n";
616		print "\"$Gtitle\" at O.n + (0, .1)\n";
617	}
618
619	if ($br_title) {
620		print "\n# Graph title.\n";
621		print "\"\\s+2$Gtitle\\s0\" at O.n + (0, .1)\n";
622	}
623
624	unless ($nolabels || $notitle) {
625		print "\n# Graph title.\n";
626		if ($big) {
627			print "\"\\s+8$Gtitle\\s0\" at O.n + (0, .3)\n";
628		} else {
629			print "\"\\s+4$Gtitle\\s0\" at O.n + (0, .3)\n";
630		}
631	}
632
633	if ($cut) {
634		$cutthick = .75;
635		print "\n# Cut marks\n";
636		print "move to O.n + 0,.65; line thick $cutthick right .1\n";
637		print "move to O.w - 1,0; line thick $cutthick down .1\n";
638		print "move to O.e + .35,0; line thick $cutthick down .1\n";
639	}
640
641	# Do the dataset titles.
642	$i = 0;
643	unless ($nodatal) {
644		print "\n# Title.\n";
645		if (!$grapheach) {
646			print ".ft R\n" if ($slide);
647			for ( ; $i <= $#datasets; $i++) {
648				print $marks[$i % $nmarks];
649				if ($below) {
650					print " at O.sw - (0, .75 + $i * vs)\n";
651				} else {
652					print " at O.ne + (.25, - $i * vs)\n";
653				}
654				print
655			    "\"$datasets[$i]\" ljust at last [].e + (.1, 0)\n";
656		    	}
657			if ($cut) {
658				print "\nmove to O.s - 0,.75 + $i * vs\n";
659				print "line thick $cutthick right .1\n";
660			}
661			print ".ft\n" if ($slide);
662		}
663	}
664
665	# Finish up.
666	print "]\n.ft\n.ps\n.PE\n";
667
668	# Do the statistics
669	if ($stats) {
670		$i++;
671		$min = sprintf "%.4f", $min;
672		$max = sprintf "%.4f", $max;
673		$stat_median = sprintf "%.4f", $stat_median;
674		$stat_avg = sprintf "%.4f", $stat_avg;
675		$stat_stddev = sprintf "%.4f", $stat_stddev;
676		$stat_avgdev = sprintf "%.4f", $stat_avgdev;
677		print <<EOF;
678.ps 12
679.vs 14
680.ft CB
681.po +.7i
682.TS
683c s
684l r.
685Statistics
686=
687min	$min
688max	$max
689median	$stat_median
690average	$stat_avg
691stddev	$stat_stddev
692avgdev	$stat_avgdev
693.TE
694.po -.7i
695.ft
696.ps
697.vs
698EOF
699	}
700
701	$first_title = 0;
702}
703
704sub graph
705{
706	if ($hist) { $hist = 1; } else { $hist = 0; }
707	print ".sp ${graphspace}\n";
708	print <<EOF;
709.PS
710.ps $PS
711.vs 11
712.ft $ft
713[
714# Variables, tweak these.
715	xtick = $xtick			# width of an X tick
716	xlower = $xlower			# where the xtick start
717	xupper = $xupper			# upper range of graph
718	xn = $xn					# number of ticks to do
719	ytick = $ytick			# width of an Y tick
720	ylower = $ylower			# where the ytick start
721	yupper = $yupper			# upper range of graph
722	yn = $yn					# number of ticks to do
723	xsize = $xsize				# width of the graph
724	ysize = $ysize				# height of the graph
725	yscale = ysize / (yupper - ylower)	# scale data to paper
726	xscale = xsize / (xupper - xlower)	# scale data to paper
727	tick = $tick				# distance towards numbers
728	gthk = $gthk				# thickness of grid lines
729	thk = $thk				# thickness of data lines
730	grapheach = $grapheach			# doing lotso little ones?
731	halfgrid = $halfgrid			# fewer grid lines
732	qthk = 2.0				# thickness of quartile lines
733	vs = .15				# works for 10 point fonts
734	hist = $hist				# histogram
735	ypartial = $ypartial				# Y spillerover
736	$log_x				# 1 if x data is log base 2
737	$log_y				# 1 if y data is log base 2
738
739# Draw the graph borders and tick marks
740	O:	box $nobox thick 2 ht ysize wid xsize
741	if (hist) then {
742		# The box was invisible, draw the three sides
743		# The partial part i sbecause we are just too big.
744		line thick 2 from O.sw to O.se
745		line thick 2 from O.sw to O.nw + 0,ypartial*yscale
746		line thick 2 from O.se to O.ne + 0,ypartial*yscale
747		xgridlen = xsize + tick/2
748	} else {
749		xgridlen = xsize
750	}
751	if (ysize < 2.5) then {
752		ysp = -.15
753		xsp = -.2
754		tick = tick * .75
755	} else {
756		ysp = -.2
757		xsp = -.25
758	}
759	j = ylower
760	t = tick * .5
761	for i = 0 to yn by 1 do {
762		ys = j - ylower
763		g = ys * yscale
764		# Draw the ticks to the numbers on the Y axis
765		line thick gthk from O.sw + (-tick, g) to O.sw + (0, g)
766		if (hist) then {
767			line thick gthk from O.se + (tick, g) to O.se + (0, g)
768		}
769		# Grid line across at same level as number ticks
770		line $nogrid thick gthk from O.sw + 0,g to O.sw + xsize,g
771		if (i < yn) then {
772			y2 = (ys + (ytick / 2)) * yscale
773			if (!halfgrid) then {
774				# Grid line across between number ticks
775				line $nogrid thick gthk from \\
776				    O.sw + (-t, y2) to O.sw + (xgridlen, y2)
777			}
778		}
779		if (logy == 1) then {
780			tmp = 2 ^ j;
781			if (tmp >= 1024*1024) then {
782				tmp = tmp / (1024*1024)
783				sprintf("%.0fM", tmp) at O.sw + ysp,g-.02
784			} else { if (tmp >= 1024) then {
785				tmp = tmp / 1024
786				sprintf("%.0fK", tmp) rjust at O.sw + ysp,g-.02
787			} else {
788				sprintf("%.0f", tmp) rjust at O.sw + ysp,g-.02
789			}}
790		} else { if (yupper - ylower > 999) then {
791			$print rjust at O.sw + ysp, g - .02
792			if (hist) then { $print ljust at O.se + -ysp,g-.02 }
793		} else { if (yupper - ylower > 10) then {
794			sprintf("%.0f", j) rjust at O.sw + ysp, g - .02
795			if (hist) then {
796				sprintf("%.0f", j) ljust at O.se + -ysp,g-.02
797			}
798		} else { if (yupper - ylower > 1) then {
799			sprintf("%.1f", j) rjust at O.sw + ysp, g - .02
800			sprintf("%.1f", j) rjust at O.sw + ysp, g - .02
801		} else { if (yupper - ylower > .1) then {
802			sprintf("%.2f", j) rjust at O.sw + ysp, g - .02
803			if (hist) then {
804				sprintf("%.2f", j) ljust at O.se + -ysp,g-.02
805			}
806		} else {
807			sprintf("%.3f", j) rjust at O.sw + ysp, g - .02
808			if (hist) then {
809				sprintf("%.3f", j) ljust at O.se + -ysp,g-.02
810			}
811		}}}}}
812		j = j + ytick
813	}
814	j = xlower
815	even = 0
816	for i = 0 to xn by 1 do {
817		even = !even
818		doit = !grapheach || xn > 9 || even
819		xs = j - xlower
820		g = xs * xscale
821		line thick gthk from O.sw + (g, -tick) to O.sw + (g, 0)
822		if (!hist) then {
823			line $nogrid thick gthk from O.sw + g,0 to O.sw + g,ysize
824		}
825		if (i < xn) then {
826			x2 = (xs + (xtick / 2)) * xscale
827			if (!halfgrid && !hist) then {
828				line $nogrid thick gthk from O.sw+x2,-t to O.sw+x2,ysize
829			}
830		}
831		if (logx == 1) then {
832			tmp = 2 ^ j;
833			if (tmp >= 1024*1024) then {
834				tmp = tmp / (1024*1024)
835				if (doit) then {
836					sprintf("%.0fM", tmp) at O.sw + g,xsp
837				}
838			} else { if (tmp >= 1024) then {
839				tmp = tmp / 1024
840				if (doit) then {
841					sprintf("%.0fK", tmp) at O.sw + g,xsp
842				}
843			} else {
844				if (doit) then {
845					sprintf("%.0f", tmp) at O.sw + g,xsp
846				}
847			}}
848		} else { if (xupper - xlower > 999) then {
849			$print at O.sw + g, xsp
850		} else { if (xupper - xlower > 10) then {
851			sprintf("%.0f", j) at O.sw + g, xsp
852		} else { if (xupper - xlower > 1) then {
853			sprintf("%.1f", j) at O.sw + g, xsp
854		} else { if (xupper - xlower > .1) then {
855			sprintf("%.2f", j) at O.sw + g, xsp
856		} else {
857			sprintf("%.3f", j) at O.sw + g, xsp
858		}}}}}
859		j = j + xtick
860	}
861EOF
862	# Add some statistics.
863	if ($stats) {
864		print "line from O.sw + 0,(yscale * ($stat_avg - $ylower)) " .
865		    "to O.se + 0,(yscale * ($stat_avg - $ylower))\n";
866		print "\"average\" at last line.e + .2,0 ljust\n";
867		print "line from O.sw + 0,(yscale * ($stat_median - $ylower)) " .
868		    "to O.se + 0,(yscale * ($stat_median - $ylower))\n";
869		print "\"median\" at last line.e + .2,0 ljust\n";
870		$tmp = $stat_median + $stat_avgdev;
871		print "line from O.sw + 0,(yscale * ($tmp - $ylower)) " .
872		    "to O.se + 0,(yscale * ($tmp - $ylower))\n";
873		print "\"+ avgdev\" at last line.e + .2,0 ljust\n";
874		$tmp = $stat_median - $stat_avgdev;
875		print "line from O.sw + 0,(yscale * ($tmp - $ylower)) " .
876		    "to O.se + 0,(yscale * ($tmp - $ylower))\n";
877		print "\"- avgdev\" at last line.e + .2,0 ljust\n";
878	}
879}
880
881sub data
882{
883	local($mark) = int(int($_[0]) % int($nmarks));
884
885	print "\n# DATASET: $dataset, MARK $mark\n";
886	$first = 1;
887	foreach $d (@data) {
888		next if $d =~ /^\s*"/;
889		next if $d =~ /^\s*#/;
890		next if $d =~ /^\s*$/;
891		@_ = split(/[ \t\n]+/, $d);
892		$x = sprintf("%.6g", $_[0]);
893		$y = sprintf("%.6g", $_[1]);
894		if ($#_ == 1) {
895			if ($hist) {
896				print "box fill .25 " .
897				    "ht yscale * ($y - ylower) " .
898				    "wid $hist_bsize * xscale " .
899				    "with .sw at O.sw + " .
900				    "xscale * ($x - xlower),0\n";
901			} elsif ($nomarks && ($grapheach || !$first)) {
902				print $nomark . " at O.sw + \\\n\t" .
903				    "(xscale * ($x - xlower), " .
904				    "yscale * ($y - ylower))\n";
905			} else {
906				print $marks[$mark] .
907				    " at O.sw + \\\n\t" .
908				    "(xscale * ($x - xlower), " .
909				    "yscale * ($y - ylower))\n";
910			}
911			if (!$hist && $first != 1) {
912				print "line $lineinvis thick thk from " .
913				    "2nd last [].c to last [].c\n";
914			}
915			$first = 0;
916		} elsif ($#_ == 5) {	# Quartile graph
917			# Draw the lower line
918			print "x = xscale * ($_[0] - xlower)\n";
919			print "    line thick qthk from \\\n\t" .
920			    "O.sw + x, yscale * ($_[1] - ylower) to\\\n\t" .
921			    "O.sw + x, yscale * ($_[2] - ylower)\n";
922			# Draw the mark
923			print "    $marks[$mark]" . " at O.sw + \\\n\t" .
924			    "x, yscale * ($_[3] - ylower)\n";
925			# Draw the upper line
926			print "    line thick qthk from \\\n\t" .
927			    "O.sw + x, yscale * ($_[4] - ylower) to\\\n\t" .
928			    "O.sw + x, yscale * ($_[5] - ylower)\n";
929			# Connect the lines?
930			if ($qline) {
931				if ($first != 1) {
932					print "line thick thk from " .
933					    "2nd last [].c to last [].c\n";
934				}
935			}
936			$first = 0;
937		}
938	}
939	# Put a mark on the end point
940	if ($nomarks && !$nodatal && !$first && !$grapheach) {
941		print $marks[$mark] .
942		    " at O.sw + \\\n\t" .
943		    "(xscale * ($x - xlower), " .
944		    "yscale * ($y - ylower))\n";
945	}
946	@data = ();
947}
948