[alt.sources] line graph program in troff

flak@mcgp1.UUCP (Dan Flak) (03/01/91)

line is a poor man's graphics program to produce line graphs
using troff. It has a rather limited scope in that it produces
only one line per plot and its corresponding trend line, and
takes as its x-axis only months at fixed intervals. However, this
should be a common enough application to be of some use to
someone else besides me.

No special make instructions are needed, Just "cc" it.

#----- cut here and sh the remainder -----
#!/bin/sh
# This is a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 02/28/1991 20:19 UTC by flak@mcgp1
# Source directory /usr/users/flak/News
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#   2377 -rw-r--r-- line.doc
#   7244 -rw-rw-rw- line.c
#
# ============= line.doc ==============
if test -f 'line.doc' -a X"$1" != X"-c"; then
	echo 'x - skipping line.doc (File already exists)'
else
echo 'x - extracting line.doc (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'line.doc' &&
.de )k
..
.TH line l local
.SH NAME
line \- create a line graph
.SH SYNOPSIS
.B line [-iltu] <datafile>
.SH DESCRIPTION
.I line
crates a stream of troff 'pic' commands to the standard output to
produce a line graph and trend analysis (least squares fit of a 
straight line) for a 12 month period of data.
.SH OPTIONS
.TP
.B i
increment. This value determines how many horizontal partitions there will
be on the graph. (E.G. "5" means place a horizontal scale every 5 units).
The horizontal lines on the graph are dashed. The vertical lines
extending from each month are dotted. The program is arranged so that
whatever increment is selected, there will be 10 "dots" per increment.
Default is 10.
.TP
.B l
lower limit. The lower limit for the data to be plotted. Default is 0.
.TP
.B t
title. The title string for the graph.
.TP
.B u
upper limit. The upper limit for the data to be plotted. Default is
100.
.SH EXAMPLES
DATA FILE FORMAT:
The data file is an ascii file containing one "record" per line (maximum 
of 12 records). A
record consists of the month to be plotted (in YYMM format - e.g. 9103
is March 1991).
and its value separated by a vertical bar \f(CW(|)\fR.
.sp
.nf
datafile
\f(CW9001|95.4
9002|91.2
9003|93.8
9004|ND
9005|94.2
9006|99.3
9007|95.2
9008|97.3
9009|96.1
9010|95.3
9011|97.4
9012|96.6\fR
.fi
.sp
line -l 90 -u 100 -i 5 -t "TEST TITLE" datafile
.sp
The example above will produce the commands to draw a line graph 
containing; an x-axis labeled with the months from "Jan" through "Dec", a
y-axis labeled with "90", "95", and "100", a solid line showing the
actual plot of data, a dotted line showing the trend line, the
values associated with the data points on top of the graph, and the
title "TEST TITLE" centered on top of the page.
.SH DIAGNOSTICS
The program will issue a warning message if the upper or lower limits
are exceeded by any of the data values. The keyword "ND" will be
interpreted by the program to mean "NO DATA". Put in the "value"
position in a record, it will cause a skip in the plot (the actual data
line will extend from the previous data plot to the next) and the
letters "ND" displayed instead of a data value.
.SH AUTHOR
Dan Flak, McCaw Cellular Communications Inc., 201 Elliott Ave W, Suite 105, 
Seattle, Wa 98119, 206-286-4355 (usenet: nwnexus!mcgp1!flak)
.SH BUGS
The program is very picky about the format of the data.
SHAR_EOF
chmod 0644 line.doc ||
echo 'restore of line.doc failed'
Wc_c="`wc -c < 'line.doc'`"
test 2377 -eq "$Wc_c" ||
	echo 'line.doc: original size 2377, current size' "$Wc_c"
fi
# ============= line.c ==============
if test -f 'line.c' -a X"$1" != X"-c"; then
	echo 'x - skipping line.c (File already exists)'
else
echo 'x - extracting line.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'line.c' &&
/*+
X * File: line.c
X *
X * Description: Produce a line graph using pic and troff.
X *
X * This module is the property of McCaw Cellular Communications, Inc
X * Copyright 1990, all rights reserved
X *
X * Audit Trail:
X *   Original Author and Date: Dan Flak - 13 Jul 1990.
X *
-*/
#ifdef VERSION
static char *SCCS = "%Z% %M% %I% %G% %U%"
#endif
X
/* #includes */
#include <stdio.h>
X
/* #defines  */
#define SAY printf
#define BACKSLASH '\134'
X
/* external variables */
extern char *optarg;
extern int  optind;
extern int  opterr;
extern int  optopt;
X
/* referenced external functions */
float atof();
X
/* internal functions */
int basemonth();
X
X
/* global variables   */
X
struct record
X	{
X	char  mdate[5];
X	float f;
X	char  m[8];
X	} rec[12];
X
float data_x;
float data_x2;
float data_y;
float data_y2;
float data_xy;
X
float newx, newy, oldx, oldy;
float factor1, factor2, factor3;
float m, b, bmonth;
X
float lower = 0, upper = 100, lineat = 10, hbar, eachmo;
char title[80];
int nrecs = 0; int npts = 0;
X
char *month[12] =
X	{
X	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
X	};
X
FILE *fin, *fopen();
X
X
/* static variables   */
X
X
/*<
X * Function: main
X *
X * Description: Parse the data and make a pic file
X *
X * Data Type: int
X *
X * Arguments: 
X *   argv[] = input file name
X *
X *    Flags
X *    -l    lower limit
X *    -u    upper limit
X *    -i    increment
X *    -t    title string
X *
X * Returns:
X *
X * Side Effects:
X *
X * Calls:
X *
X * PDL
X *
>*/
X
int
main (argc, argv)
int  argc;
char *argv[];
{
int i, k, nfound;
int c;
float range, perinch;
char linebuf[1000];
*title = '\0';
X
while ((c = getopt (argc, argv, "i:l:u:t:")) != EOF)
X    {
X    switch (c)
X        {
X        case 'i':
X            lineat = atof (optarg);
X        break;
X        case 'l':
X            lower = atof (optarg);
X        break;
X        case 'u':
X            upper = atof (optarg);
X        break;
X        case 't':
X            sprintf (title, "%s", optarg);
X        break;
X        default:
X            ;
X        break;
X        }
X	}
X
if (argc < 2)
X	{
X	fprintf (stderr, 
"syntax: %s [-i increment] [-l lower limit] [-u upper limit] [-t title]\n",
X	argv[0]);
X	fprintf (stderr, "        <data file>\n");
X	exit (0);
X	}
X
/*  Read data file */
if ((fin = fopen (argv[optind], "r")) == NULL)
X	{
X	fprintf (stderr, "%s: can't open %s\n", argv[0], argv[1]);
X	exit (-1);
X	}
X
X
while (fgets (linebuf, 1000, fin) != NULL)
X	{
X	nfound = sscanf (linebuf, "%[^|]|%[^'\n']",
X		rec[nrecs].mdate, rec[nrecs].m);
X	/* SAY ("nfound = %d / %2d - %s | %s\n", 
X		nfound, nrecs, rec[nrecs].mdate, rec[nrecs].m); */
X	if (nfound == 2)
X		{
X		if (strcmp (rec[nrecs].m, "ND"))
X			{
X			rec[nrecs].f = atof (rec[nrecs].m);
X			}
X		else
X			{
X			rec[nrecs].f = -1;
X			}
X		nrecs++;
X		if (nrecs > 12 )
X			{
X			fprintf (stderr, 
X				"%s: Can only accomodate a maximum of 12 data points\n",
X				argv[0]);
X			exit (-1);	
X			}
X		}
X	}
X
fclose (fin);
X
/*  Check upper and lower bounds  */
X
for (i = 0; i < nrecs; i++)
X	{
X	if (rec[i].f < lower && rec[i].f != -1)
X		{
X		fprintf (stderr, "Warning: lower limit exceeded %s\n", 
X			rec[i].mdate);
X		}
X
X	if (rec[i].f > upper)
X		{
X		fprintf (stderr, "Warning: upper limit exceeded %s\n",
X			rec[i].mdate);
X		}
X	}
X
X
/*  Build the file that will plot the graph  */
X
printf (".de )k\n..\n");
printf (".PH\n");
printf (".ps 12\n");
printf (".vs 12\n");
X	data_x = 0; data_x2 = 0; data_y = 0; data_y2 = 0; data_xy = 0;
X	printf (".ce 3\n");
X	printf ("%cs+4%cfB%s%cfP%cs0\n", 
X		BACKSLASH, BACKSLASH, title, BACKSLASH, BACKSLASH);
X	printf (".br\n");
X	printf (".DS CB\n");
X	printf (".PS\n");
X	printf ("\n# set invisible refernece box\n");
X	printf ("boxht = 3.45; boxwid = 4.65\n");
X	printf ("move to (0, -2.5); OB: box invis\n");
X	printf ("\n# workaround for bug in 'old' pic\n");
X	printf ("%c   %c at OB.nw + (0, .2)\n", '"', '"');
X	printf ("%c   %c at OB.sw\n", '"', '"');
X	printf ("\n# Print horizontal axis\n");
X	printf ("HA: line from OB.sw to OB.se\n");
X	printf ("\n# Print vertical axis\n");
X	printf ("VA: line from OB.nw to OB.sw\n");
X	range = upper - lower;
X	if (range == 0)
X		{
X		fprintf (stderr, 
X			"Warning: range = 0. Sorry, Dave, I can't do that\n");
X		exit (-1);
X		}
X	perinch = 3.45 / range;
X	SAY ("# range = %f, perinch = %f\n", range, perinch);
X	printf ("\n# put in horizontal scale marks\n");
X	printf ("%c%4.1f%c rjust at OB.sw + (-.1, 0)\n", '"', lower, '"');
X	hbar = lower + lineat;
X	eachmo = 4.65 / 11;
X	while (hbar <= upper)
X		{
X		printf ("line right 4.65 at OB.sw + (0, %f) dashed; ", 
X			(hbar  - lower) * perinch);
X		printf ("%c%4.1f%c rjust at OB.sw + (-.1, %f)\n",
X			'"', hbar, '"', (hbar - lower) * perinch);
X		hbar += lineat;
X		}
X	printf ("\n# Put in vertical scale marks\n");
X	for (i = 0; i < nrecs; i++)
X		{
X		k = atoi (rec[i].mdate + 2) - 1;
X		printf ("%c%s%c at OB.sw + (%f, -.1); ",
X			'"', month[k], '"', i * eachmo);
X		printf ("%c%s%c at OB.sw + (%f, 3.60)\n", 
X			'"', rec[i].m, '"', i * eachmo);
X		}
X	putchar ('\n');
X	for (i = 1; i < nrecs; i++)
X		{
X		if (lineat == 0)
X			{
X			lineat = 1;
X			}
X		printf ("line from OB.sw + (%f, 0) to OB.sw + (%f, 3.45) dotted %f\n",
X			i * eachmo, i * eachmo, perinch * lineat / 10);
X		}
X	printf ("\n# Drawing Actual lines\n");
X	oldx = -1; oldy = -1;
X	for (i = 0; i < nrecs; i++)
X		{
X		if (rec[i].f != -1)
X			{
X			newx = i * eachmo;
X			newy = (rec[i].f - lower) * perinch;
X			if (oldx != -1)
X				{
X				printf ("line from OB.sw + (%f, %f) to OB.sw + (%f, %f)\n",
X					oldx, oldy, newx, newy);
X				}
X			oldx = newx; oldy = newy;
X			}
X		}
X	printf ("\n# Drawing trend lines\n");
X	npts = 0;
X	for (i = 0; i < nrecs; i++)
X		{
X		if (rec[i].f != -1)
X			{
X			bmonth = basemonth (rec[i].mdate);
X			/* SAY ("%s = %d\n", rec[i].mdate, bmonth); */
X			data_x  += bmonth;
X			data_x2 += bmonth * bmonth;
X			data_y  += rec[i].f;
X			data_y2 += rec[i].f * rec[i].f;
X			data_xy += bmonth * rec[i].f;
X			npts++;
X			}
X		}
X	factor1 = npts * data_x2 - data_x * data_x;
X	factor2 = npts * data_y2 - data_y * data_y;
X	factor3 = npts * data_xy - data_x * data_y;
X	if (factor1 != 0)
X		{
X		m = factor3 / factor1;
X		}
X	else
X		{
X		fprintf (stderr, "Warning: slope not calcualted for %d\n");
X		m = 0;
X		}
X
X	if (npts != 0)
X		{
X		b = (data_y - m * data_x) / npts;
X		}
X	else
X		{
X		fprintf (stderr, "Warning: intercept not calculated\n");
X		b = (upper - lower) / 2;
X		}
X	printf ("\n# Slope = %f, Intercept = %f\n", m, b);
X	printf ("line from OB.sw + (0, %f) to OB.sw + (4.65, %f) dotted\n",
X		(m * basemonth (rec[0].mdate) + b - lower) * perinch,
X		(m * basemonth (rec[nrecs - 1].mdate) + b -lower) * perinch);
X
X	printf ("\n# Put in ledgend\n");
X	printf ("line right 1.0 at HA.start + (0.5, -.333)\n %c Actual%c ljust\n", 
X		'"', '"');
X	printf ("move right 1.0\n");
X	printf ("line right 1.0  dotted\n %c  Trend%c ljust\n", '"', '"');
X	printf (".PE\n");
X	printf (".DE\n");
X	printf (".bp\n");
}
X
/*<
X * Function: basemonth (yymm)
X *
X * Description: Feed it a YYMM and it will give you number of months
X *   since 8901.
X *
X * Data Type: int
X *
X * Arguments:
X *    yymm = the date to convert
X *
X * Returns:
X *    The number of months since 8901
X *
X * Side Effects:
X *
X * Calls:
X *
X * PDL:
X *
>*/
X
int
basemonth (yymm)
char *yymm;
{
int zzzz;
zzzz = atoi (yymm);
return (12 * ((zzzz - 8901) / 100) + (zzzz - 8901) % 100) ;
}
SHAR_EOF
chmod 0666 line.c ||
echo 'restore of line.c failed'
Wc_c="`wc -c < 'line.c'`"
test 7244 -eq "$Wc_c" ||
	echo 'line.c: original size 7244, current size' "$Wc_c"
fi
exit 0
-- 
       Dan Flak - McCaw Cellular Communications Inc., 201 Elliot Ave W.,
    Suite 105, Seattle, Wa 98119, 206-286-4355, (usenet: nwnexus!mcgp1!flak)