graph 22 KB

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