/*
 * plotting.c
 *
 * plotting routines.
 *
 */
#include <stdio.h>
#include <errno.h>
extern int errno;
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include "datatypes.h"

void	ErrorExit();

#ifndef	min
#	define	min(A,B)	A<B? A : B
#endif
#ifndef	max
#	define	max(A,B)	A>B? A : B
#endif

extern bool	GraphicsInProgress;

/* GoldenRatio = (1+Sqrt(5))/2	*/
#define	GOLDENRATIO	1.6180339887499
#define ASPECTRATIO	1.0/GOLDENRATIO
extern VALUE	V_AngularUnit;
extern VALUE	V_AnnotationScale;
extern VALUE	V_AspectRatio;
extern VALUE	V_Boxed;
extern VALUE	V_Domain;
extern VALUE	V_Gridding;
extern VALUE	V_LabelScale;
extern VALUE	V_LineColor;
extern VALUE	V_LineStyle;
extern VALUE	V_Orientation;
extern VALUE	V_Origin;
extern VALUE	V_PlotColor;
extern VALUE	V_PlotDevice;
extern VALUE	V_PlotJoined;
extern VALUE	V_PlotPoints;
extern VALUE	V_PlotTitle;
extern VALUE	V_PlotType;
extern VALUE	V_PointScale;
extern VALUE	V_PointSymbol;
extern VALUE	V_PolarVariable;
extern VALUE	V_Range;
extern VALUE	V_SubPages;
extern VALUE	V_SupplyAbscissa;
extern VALUE	V_TitleScale;
extern VALUE	V_UseInputFile;
extern VALUE	V_Verbose;
extern VALUE	V_ViewPort;
extern VALUE	V_XLabel;
extern VALUE	V_XTick;
extern VALUE	V_YLabel;
extern VALUE	V_YTick;

char	*DeviceTypes[] = {
	"amiga",
	"printer",
	"iff",
	"hp",
	"aegis",
	"postscript",
	(char *)NULL
};

typedef struct line_style {
		int	ls_type;
		int	ls_nelms;
		int	*ls_space;
		int	*ls_mark;
	} LINESTYLE;
LIST	LineStyleList;
LINESTYLE	*LineStyles; int NLineStyles;
int		*PointCodes, NPointCodes;
int	space0 = 0, mark0 = 0, space1 = 1500, mark1 = 1500;

RECT	UserRect;
RECT	ViewPort = { 0.1,0.1, 0.9,0.9 };
RECT	WorldRect = {0.0,0.0, 1.0,1.0};


void
ListPlot(Data, NTuples, TupleSize)
FLOAT	**Data;
int	NTuples, TupleSize;
{
	void InitializeDevice();
	void InitializePlottingEnv();
#ifdef	ANSI_C
	void Plot2D(FLOAT **, int , int );
	void PolarPlot(FLOAT **, int , int );
#else
	void Plot2D();
	void PolarPlot();
#endif

	InitializeDevice();
	GraphicsInProgress = TRUE;

	InitializePlottingEnv(Data, NTuples, TupleSize);

	if (Vstrcmp(V_PlotType,"polar")) {
		PolarPlot(Data, NTuples, TupleSize);
	} else {
		Plot2D(Data, NTuples, TupleSize);
	}
	plend();
}


void
InitializeDevice()
{
	register int	i;
	register char	*DeviceStr;


	plorient(Vstrcmp(V_Orientation, "portrait")? 1 : 0);
	plselect(stdout);	/* write to stdout */
		

	DeviceStr = V_PlotDevice.val_u.udt_string;
	for (i=0; DeviceTypes[i]; i++) 
		if (strcmp(DeviceStr, DeviceTypes[i]) == 0) {
			plbeg(
				i+1,	/* device code	*/
				(int)V_SubPages.val_u.udt_interval.int_lo, /* nx */
				(int)V_SubPages.val_u.udt_interval.int_hi /* ny */
			);
			return;
		}
	fprintf(stderr,"(InitializeDevice) Unknown device type: %s\n", DeviceStr);
	ErrorExit();
}

void
InitializePlottingEnv(Data, NTuples, TupleSize)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
{
	int	i, j;
	FLOAT	*Ptr;
	void	InitLineStyles();
	void	InitPointCodes();

	/* viewport	*/
	ViewPort = VtoRect(V_ViewPort);

	/* user window		*/
	UserRect.XMin = MAX_FLOAT;
	UserRect.YMin = MAX_FLOAT;
	UserRect.XMax = -MAX_FLOAT;
	UserRect.YMax = -MAX_FLOAT;
	
	if (VtoBoolean(V_SupplyAbscissa)) {
		UserRect.XMin = 0.0;
		UserRect.XMax = (FLOAT)(NTuples-1);
	} else {
		for (i=0; i<NTuples; i++) {
			if (Data[i][0] < UserRect.XMin)
				UserRect.XMin = Data[i][0];
			if (Data[i][0] > UserRect.XMax)
				UserRect.XMax = Data[i][0];
		}
	}
	for (i=0; i<NTuples; i++) 
		for (j=TupleSize-1,Ptr= &Data[i][1]; j>0; --j,Ptr++) {
			if (*Ptr < UserRect.YMin)
				UserRect.YMin = *Ptr;
			if (*Ptr > UserRect.YMax)
				UserRect.YMax = *Ptr;
		}
		
	
	/* font scaling		*/

	/* plot type		*/

	/* grid			*/

	/* line styles		*/
	InitLineStyles();
	

	/* line colors		*/

	/* point symbols	*/
	InitPointCodes();

	/* window type		*/

	/* X label		*/

	/* Y label		*/

	/* Plot title		*/

	/* aspect ratio		*/
}

#define	isMark(C)	(((C) == 'M' || (C) == 'm')? TRUE : FALSE)
#define	isSpace(C)	(((C) == 'S' || (C) == 's')? TRUE : FALSE)
#define	MarkLength(C)	(C) == 'M'? 1000 : 250
#define	SpaceLength(C)	(C) == 'S'? 1000 : 250
#define	MAX_MARKS	32

void
DecodeLineStyle(StyleStr, Mark, Space, N)
char	*StyleStr;
int	**Mark;
int	**Space;
int	*N;
{
	register char	*S;
	int	macc, sacc;
	int	i, j, k;
	int	mark[MAX_MARKS];
	int	space[MAX_MARKS];
	bool	ProcessingMark;

	/*
	 * 'S' = 1000   micron space
	 * 's' =  250   micron space
	 * 'M' = 1000 	micron line
	 * 'm' =  250	micron line
	 */
	ProcessingMark = TRUE; /* to satisfy some compilers */
	if (isMark(*StyleStr))
		ProcessingMark = TRUE;
	else if (isSpace(*StyleStr))
		ProcessingMark = FALSE;
	else {
		fprintf(stderr,"(DecodeLineStyle) Invalid Line style: \"%s\"\n", StyleStr);
		ErrorExit();
	}
	memset((char *)mark, 0, MAX_MARKS * sizeof(int));
	memset((char *)space, 0, MAX_MARKS * sizeof(int));
	for (macc=0,sacc=0,i=0,j=0,S=StyleStr ; *S && i<MAX_MARKS && j<MAX_MARKS; ++S) {
		if (isMark(*S) && ProcessingMark) {
			macc += MarkLength(*S);
		} else if (isMark(*S) && !ProcessingMark) {
			space[j++] = sacc;
			macc = MarkLength(*S);
			ProcessingMark = TRUE;
		} else if (isSpace(*S) && ProcessingMark) {
			mark[i++] = macc;
			sacc = SpaceLength(*S);
			ProcessingMark = FALSE;
		} else if (isSpace(*S) && !ProcessingMark) {
			sacc += SpaceLength(*S);
		} else if (isspace(*S)) {
			continue;
		} else {
			fprintf(stderr,"(DecodeLineStyle) Invalid line style code '%c' in \"%s\"\n", *S, StyleStr);
			ErrorExit();
		}
	}
	if (ProcessingMark)
		mark[i++] = macc;
	else
		space[j++] = sacc;

	k = max(i,j);

	if (((*Mark) = (int *)calloc(k, sizeof(int))) == (int *)NULL) {
		perror("(DecodeLineStyle) ");
		ErrorExit();
	}
	if (((*Space) = (int *)calloc(k, sizeof(int))) == (int *)NULL) {
		perror("(DecodeLineStyle) ");
		ErrorExit();
	}
	for (i=0; i<k; i++) {
		(*Mark)[i] = mark[i];
		(*Space)[i] = space[i];
	}
	*N = k;
}

void
InitLineStyles()
{
	int		i;
	LATOM		*A;
	LINESTYLE	*LS;
	int		*Mark, *Space;
	int		N;
#ifdef	ANSI_C
	LINESTYLE	*LSAlloc(int , int *, int *);
#else
	LINESTYLE	*LSAlloc();
#endif

	LineStyleList.list_n = 0;
	LineStyleList.list_head = (LATOM *)NULL;
	LineStyleList.list_tail = (LATOM *)NULL;

	LS = LSAlloc(0, (int *)NULL, (int *)NULL);
	Append(&LineStyleList, (generic *)LS);

	for (i=1,A=V_LineStyle.val_u.udt_set.list_head; A; A=A->la_next,i++) {
		DecodeLineStyle(
			(char *)A->la_p,
			&Mark,
			&Space,
			&N
		);
		LS = LSAlloc(N, Mark, Space);
		Append(&LineStyleList, (generic *)LS);
	}

	NLineStyles = i;
	if ((LineStyles = (LINESTYLE *)calloc(NLineStyles, sizeof(LINESTYLE))) == (LINESTYLE *)NULL) {
		perror("(InitLineStyles) ");
		ErrorExit();
	}
	for (i=0,A=LineStyleList.list_head; A; A=A->la_next,i++) {
		LineStyles[i].ls_mark = ((LINESTYLE *)(A->la_p))->ls_mark;
		LineStyles[i].ls_space = ((LINESTYLE *)(A->la_p))->ls_space;
		LineStyles[i].ls_nelms = ((LINESTYLE *)(A->la_p))->ls_nelms;
	}
}


LINESTYLE	*
LSAlloc(n, Mark, Space)
int	n;
int	*Mark, *Space;
{
	register LINESTYLE	*LS;

	if ((LS = (LINESTYLE *)calloc(1, sizeof(LINESTYLE))) == (LINESTYLE *)NULL) {
		perror("(LSAlloc) ");
		assert(LS != (LINESTYLE *)NULL);
		exit(errno);
	}
	LS->ls_mark = Mark;
	LS->ls_space = Space;
	LS->ls_nelms = n;
	return(LS);
}

void
InitPointCodes()
{
	register LATOM	*A;
	register int	i;
	extern int	*PointCodes, NPointCodes;
	extern VALUE	V_PointSymbol;

	if (!isSet(V_PointSymbol)) {
		NPointCodes = 0;
		return;
	}

	/* count # codes	*/
	for (i=0,A=V_PointSymbol.val_u.udt_set.list_head; A; A=A->la_next, i++)
		;

	NPointCodes = i;
	if (NPointCodes == 0)
		return;

	if ((PointCodes = (int *)calloc(NPointCodes, sizeof(int))) == (int *)NULL) {
		perror("(InitPointCodes) ");
		assert(PointCodes != (int *)NULL);
		ErrorExit();
	}
	for (i=0,A=V_PointSymbol.val_u.udt_set.list_head; A; A=A->la_next, i++) {
		PointCodes[i] = atoi((char *)A->la_p);
	}
}


void
Plot2D(Data, NTuples, TupleSize)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
{
	int	i, j;
	FLOAT	*X, *Y;
	FLOAT	x, y;
	FLOAT	x0, y0;
	FLOAT	xtick, ytick;
	FLOAT	Xmin,Xmax, Ymin,Ymax;
	FLOAT	MedianX, StdDevX;
	FLOAT	MedianY, StdDevY;
	int	nxsub, nysub;
	bool	AutoRange, AutoDomain;
	bool	LogX, LogY;
	char	Xopt[16], Yopt[16];
#ifdef	ANSI_C
	void	StatisticsOfX(
			FLOAT **,
			int,
			int ,
			FLOAT *,
			FLOAT *
		);
	void	StatisticsOfY(
			FLOAT **,
			int,
			int ,
			FLOAT *,
			FLOAT *
		);
	double	log10(double);
#else
	void	StatisticsOfX();
	void	StatisticsOfY();
	double	log10();
#endif

	pladv(0);
	ViewPort = VtoRect(V_ViewPort);

	if (isString(V_AspectRatio)) {
		/* automatic --> STRETCH */
		plvpor(ViewPort.XMin, ViewPort.XMax, ViewPort.YMin, ViewPort.YMax);
/*debug
		plvsta();
debug*/
	} else {
		assert(isDbl(V_AspectRatio));
		plvasp(VtoDbl(V_AspectRatio));
	}
	
	LogX = (Vstrcmp(V_PlotType, "loglin") || Vstrcmp(V_PlotType,"loglog"))?
			TRUE : FALSE;
	LogY = (Vstrcmp(V_PlotType, "linlog") || Vstrcmp(V_PlotType,"loglog"))?
			TRUE : FALSE;

	/* window bounds */
	AutoDomain = FALSE;
	if (isString(V_Domain) && Vstrcmp(V_Domain, "All")) {
		/* all points	*/
		Xmin = UserRect.XMin;
		Xmax = UserRect.XMax;
	} else if (isString(V_Domain) && Vstrcmp(V_Domain,"Automatic")) {
		/* automatic 	*/
		AutoDomain = TRUE;
		StatisticsOfX(Data, NTuples, TupleSize, &MedianX, &StdDevX);
		x = MedianX - 3.0*StdDevX;
		Xmin = UserRect.XMin < x? x : UserRect.XMin;
		x = MedianX + 3.0*StdDevX;
		Xmax = UserRect.XMax > x? x : UserRect.XMax;
	} else {
		assert(isInterval(V_Domain));
		Xmin = V_Domain.val_u.udt_interval.int_lo;
		Xmax = V_Domain.val_u.udt_interval.int_hi;
	}
	if (LogX) {
		if (Xmin == 0.0 || Xmax == 0.0) {
			/* error */
			fprintf(stderr,"ListPlot: Trying to find log10(0) in data! Stop.\n");
			ErrorExit();
		}
		Xmin = log10((double)Xmin);
		Xmax = log10((double)Xmax);
	}

	AutoRange = FALSE;
	if (isString(V_Range) && Vstrcmp(V_Range, "All")) {
		/* all */
		Ymin = UserRect.YMin;
		Ymax = UserRect.YMax;
	} else if (isString(V_Range) && Vstrcmp(V_Range,"Automatic")) {
		/* automatic 	*/
		AutoRange = TRUE;
		StatisticsOfY(Data, NTuples, TupleSize, &MedianY, &StdDevY);
		y = MedianY - 3.0*StdDevY;
		Ymin = UserRect.YMin < y? y : UserRect.YMin;
		y = MedianY + 3.0*StdDevY;
		Ymax = UserRect.YMax > y? y : UserRect.YMax;
	} else {
		assert(isInterval(V_Range));
		Ymin = V_Range.val_u.udt_interval.int_lo;
		Ymax = V_Range.val_u.udt_interval.int_hi;
	}
	if (LogY) {
		if (Ymin == 0.0 || Ymax == 0.0) {
			/* error */
			fprintf(stderr,"ListPlot: Trying to find log10(0) in data! Stop.\n");
			ErrorExit();
		}
		Ymin = log10((double)Ymin);
		Ymax = log10((double)Ymax);
	}
	plwind(
		Xmin,
		Xmax,
		Ymin,
		Ymax
	);
	plschr(0., VtoDbl(V_AnnotationScale));


	/* X and Y ticks */
	if (isString(V_XTick)) {
		/* automatic */
		xtick = 0.;
		nxsub = 0;
	} else {
		xtick = V_XTick.val_u.udt_interval.int_lo;
		if (LogX) xtick = log10((double)xtick);
		nxsub = V_XTick.val_u.udt_interval.int_hi;
	}
	if (isString(V_YTick)) {
		/* automatic */
		ytick = 0.;
		nysub = 0;
	} else {
		ytick = V_YTick.val_u.udt_interval.int_lo;
		if (LogY) ytick = log10((double)ytick);
		nysub = V_YTick.val_u.udt_interval.int_hi;
	}

	/* Axis options	*/
	Xopt[0] = (char)NULL;
	Yopt[0] = (char)NULL;
	if (isBoolean(V_Boxed) && VtoBoolean(V_Boxed)) {
		strcat(Xopt, "bc");
		strcat(Yopt, "bc");
	} else if (isString(V_Boxed)) {
		if (Vstrchr(V_Boxed, 'b'))
			strcat(Xopt, "b");
		if (Vstrchr(V_Boxed, 't'))
			strcat(Xopt, "c");
		if (Vstrchr(V_Boxed, 'r'))
			strcat(Yopt, "c");
		if (Vstrchr(V_Boxed, 'l'))
			strcat(Yopt, "b");
	} else {
		;
	}
	if (LogX)
		strcat(Xopt, "l");
	if (LogY)
		strcat(Yopt, "l");
	strcat(Xopt, "nst");
	strcat(Yopt, "nstv");

	if (isString(V_Origin) && Vstrcmp(V_Origin, "Median")) {
		if (AutoDomain == FALSE) {
			StatisticsOfX(Data,NTuples,TupleSize,&MedianX,&StdDevX);
		}
		if (AutoRange == FALSE) {
			StatisticsOfY(Data,NTuples,TupleSize,&MedianY,&StdDevY);
		}
		plaxes(
			MedianX,MedianY,
			Xopt, xtick, nxsub,
			Yopt, ytick,nysub
		);
	} else if (isInterval(V_Origin)) {
		x0 = V_Origin.val_u.udt_interval.int_lo;
		y0 = V_Origin.val_u.udt_interval.int_hi;
		plaxes(
			x0,y0,
			Xopt, xtick, nxsub,
			Yopt, ytick,nysub
		);
	} else if (isString(V_Origin) && Vstrcmp(V_Origin, "Automatic")) {
		plbox(Xopt, xtick, nxsub, Yopt, ytick,nysub);
	} else {
		plbox(Xopt, xtick, nxsub, Yopt, ytick,nysub);
	}

	/* gridding	*/
	if (VtoBoolean(V_Gridding)) {
		/* gridding on */
		plstyl(1, &mark1, &space1);
		plbox("g", xtick, nxsub, "g", ytick,nysub);
		plstyl(0, &mark0, &space0);
	}

	/* plot curves	*/
	if ((X = (FLOAT *)calloc(NTuples, sizeof(FLOAT))) == (FLOAT *)NULL) {
		perror("(Plot2D) ");
		ErrorExit();
	}
	if ((Y = (FLOAT *)calloc(NTuples, sizeof(FLOAT))) == (FLOAT *)NULL) {
		perror("(Plot2D) ");
		ErrorExit();
	}
	if (VtoBoolean(V_SupplyAbscissa)) {
		for (i=0; i<NTuples; i++)
			X[i] = LogX? log10((double)(i+1)) : (FLOAT)i;
	} else {
		for (i=0; i<NTuples; i++)
			X[i] = LogX? log10((double)Data[i][0]) : Data[i][0];
	}
	for (i=1; i<TupleSize; i++) {
		if (NLineStyles > 0) {
			/* use user-supplied patterns */
			plstyl(
				LineStyles[(i-1)%NLineStyles].ls_nelms,
				LineStyles[(i-1)%NLineStyles].ls_mark,
				LineStyles[(i-1)%NLineStyles].ls_space
			);
		} else {
			pllsty((i-1)%8 + 1);
		}
		plcol(i);
		for (j=0; j<NTuples; j++)
			Y[j] = LogY? log10((double)Data[j][i]) : Data[j][i];

		if (VtoBoolean(V_PlotJoined))
			plline(NTuples, X, Y);

		if (VtoBoolean(V_PlotPoints)) {
			plschr(0., VtoDbl(V_PointScale));
			pllsty(1);
			if (NPointCodes > 0) {
				plpoin(NTuples, X, Y, PointCodes[(i-1)%NPointCodes]);
			} else {
				plpoin(NTuples, X, Y, i);
			}
		}
	}


	/* restore line styles etc... */
	pllsty(1);
	plcol(1);
	plschr(0., VtoDbl(V_LabelScale));
	pllab(VtoString(V_XLabel), VtoString(V_YLabel), "");
	plschr(0., VtoDbl(V_TitleScale));
	pllab("","", VtoString(V_PlotTitle));
}

#define	DTR(D)	(double)((D)*3.1415927/180.0)

void
PolarPlot(Data, NTuples, TupleSize)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
{
	int	i, j;
	FLOAT	*A, *R;
	FLOAT	x, y;
	FLOAT	xtick, ytick;
	FLOAT	atick, rtick;
	FLOAT	Xmin,Xmax, Ymin,Ymax;
	FLOAT	r, rmax, ang, amax;
	FLOAT	MedianX, StdDevX;
	FLOAT	MedianY, StdDevY;
	FLOAT	da;
	FLOAT	x0,y0, xn,yn;
	double	pi;
	char	s[32];
	int	nxsub, nysub, nrsub, nasub;
	bool	InRadians, XisAngular;
	bool	AutoDomain, AutoRange;
#ifdef	ANSI_C
	void	StatisticsOfX(
			FLOAT **,
			int ,
			int ,
			FLOAT *,
			FLOAT *
		);
	void	StatisticsOfY(
			FLOAT **,
			int ,
			int ,
			FLOAT *,
			FLOAT *
		);
	double	log10(double); double fabs();
	double	sqrt(), sin(), cos(), atan();
#else
	void	StatisticsOfX();
	void	StatisticsOfY();
	double	log10(); double fabs();
	double	sqrt(), sin(), cos(), atan();
#endif

	pi = 4.0 * atan((double)1.0);
	InRadians = Vstrcmp(V_AngularUnit, "degrees")? FALSE : TRUE;
	XisAngular = Vstrcmp(V_PolarVariable,"angle")? TRUE : FALSE;

	pladv(0);

	if (isString(V_AspectRatio)) {
		/* automatic --> STRETCH */
		plvasp(1.0);
	} else {
		assert(isDbl(V_AspectRatio));
		plvasp(VtoDbl(V_AspectRatio));
	}

	/* window bounds */
	AutoDomain = FALSE;
	if (isString(V_Domain) && Vstrcmp(V_Domain, "All")) {
		/* all points	*/
		Xmin = UserRect.XMin;
		Xmax = UserRect.XMax;
	} else if (isString(V_Domain) && Vstrcmp(V_Domain,"Automatic")) {
		/* automatic 	*/
		AutoDomain = TRUE;
		StatisticsOfX(Data, NTuples, TupleSize, &MedianX, &StdDevX);
		x = MedianX - 3.0*StdDevX;
		Xmin = UserRect.XMin < x? x : UserRect.XMin;
		x = MedianX + 3.0*StdDevX;
		Xmax = UserRect.XMax > x? x : UserRect.XMax;
	} else {
		assert(isInterval(V_Domain));
		Xmin = V_Domain.val_u.udt_interval.int_lo;
		Xmax = V_Domain.val_u.udt_interval.int_hi;
	}

	AutoRange = FALSE;
	if (isString(V_Range) && Vstrcmp(V_Range, "All")) {
		/* all */
		Ymin = UserRect.YMin;
		Ymax = UserRect.YMax;
	} else if (isString(V_Range) && Vstrcmp(V_Range,"Automatic")) {
		/* automatic 	*/
		AutoRange = TRUE;
		StatisticsOfY(Data, NTuples, TupleSize, &MedianY, &StdDevY);
		y = MedianY - 3.0*StdDevY;
		Ymin = UserRect.YMin < y? y : UserRect.YMin;
		y = MedianY + 3.0*StdDevY;
		Ymax = UserRect.YMax > y? y : UserRect.YMax;
	} else {
		assert(isInterval(V_Range));
		Ymin = V_Range.val_u.udt_interval.int_lo;
		Ymax = V_Range.val_u.udt_interval.int_hi;
	}
	rmax = XisAngular? Ymax : Xmax;
	plwind(
		-1.3*rmax,
		1.3*rmax,
		-1.3*rmax,
		1.3*rmax
	);


	/* X and Y ticks */
	if (isString(V_XTick)) {
		/* automatic */
		xtick = XisAngular? (InRadians? pi/6.0 : 30.0) : 0.25 * rmax;
		nxsub = 4;
	} else {
		xtick = V_XTick.val_u.udt_interval.int_lo;
		nxsub = V_XTick.val_u.udt_interval.int_hi;
	}
	if (isString(V_YTick)) {
		/* automatic */
		ytick = XisAngular? 0.25 * rmax : (InRadians? pi/6.0 : 30.0);
		nysub = 4;
	} else {
		ytick = V_YTick.val_u.udt_interval.int_lo;
		nysub = V_YTick.val_u.udt_interval.int_hi;
	}
	rtick = XisAngular? ytick : xtick;
	atick = XisAngular? xtick : ytick;
	nasub = XisAngular? nxsub : nysub;
	nrsub = XisAngular? nysub : nxsub;
	amax = InRadians? 2.0 * pi : 360.0;

	/* Axis options	*/
	if (VtoBoolean(V_Gridding)) {
		/* draw circles	*/
		plstyl(0, &mark1, space1);
		/*  circular ticks	*/
		for (i=0,r=(rtick/nrsub); r<rmax; r += (rtick/nrsub),i++) {
			da = i%nrsub? 0.05 * atick : 0.1 * atick;
			for (ang=0; ang<amax; ang += atick) {
				x0 = r * cos(InRadians? ang-da : DTR(ang-da)); 
				y0 = r * sin(InRadians? ang-da : DTR(ang-da)); 
				xn = r * cos(InRadians? ang+da : DTR(ang+da)); 
				yn = r * sin(InRadians? ang+da : DTR(ang+da)); 
				pljoin(x0,y0,xn,yn);
			}
		}
#ifdef	CIRCULAR_TICKS
		/* major circles	*/
		for (r=rtick; r<rmax; r += rtick) {
			x0 = r; y0 = 0.0;
			for (ang=1.0; ang<=360.0; ang++) {
				xn = r * cos((double)(DTR(ang))); 
				yn = r * sin((double)(DTR(ang))); 
				pljoin(x0,y0,xn,yn);
				x0 = xn; y0 = yn;
			}
		}
#endif
		/* draw spokes	*/
		for (i=0,ang=0.0; ang<amax; ang += (atick/nasub),i++) {
			xn = rmax * cos(InRadians? ang : DTR(ang));
			yn = rmax * sin(InRadians? ang : DTR(ang));
			if (i%nasub == 0) {
				pljoin((FLOAT)0.0,(FLOAT)0.0,xn,yn);
			} else {
				pljoin(xn,yn,1.05*xn,1.05*yn);
			}
		}
	}
	/* write angle labels	*/
	plschr(0., VtoDbl(V_AnnotationScale));
	j = amax / atick;
	for (ang=0.0,i=0; i<j; ang += atick,i++) {
		if (InRadians) {
			xn = rmax * cos((double)ang);
			yn = rmax * sin((double)ang);
			if (fabs(ang-(2.0*pi)) > 1.0e-2)
				sprintf(s, "%.2f #gp", (float)ang/pi);
		} else {
			xn = rmax * cos((double)DTR(ang));
			yn = rmax * sin((double)DTR(ang));
			sprintf(s, "%d", (int)(ang+0.5));
		}
		if (xn >= 0)
			plptex(xn,yn,xn,yn,-0.15, s);
		else
			plptex(xn,yn,-xn,-yn,1.15, s);
	}
	plstyl(0, &mark0, &space0);

	/* plot curves	*/
	if ((A = (FLOAT *)calloc(NTuples, sizeof(FLOAT))) == (FLOAT *)NULL) {
		perror("(Plot2D) ");
		ErrorExit();
	}
	if ((R = (FLOAT *)calloc(NTuples, sizeof(FLOAT))) == (FLOAT *)NULL) {
		perror("(Plot2D) ");
		ErrorExit();
	}
	if (VtoBoolean(V_SupplyAbscissa)) {
		for (i=0; i<NTuples; i++)
			if (XisAngular)
				A[i] = i*2.0*pi/(NTuples-1);
			else
				R[i] = i*rmax/(NTuples-1);
	} else {
		for (i=0; i<NTuples; i++)
			if (XisAngular)
				A[i] = InRadians? Data[i][0] :  DTR(Data[i][0]);
			else
				R[i] = Data[i][0];
	}
	for (i=1; i<TupleSize; i++) {
		if (NLineStyles > 0) {
			/* use user-supplied patterns */
			plstyl(
				LineStyles[(i-1)%NLineStyles].ls_nelms,
				LineStyles[(i-1)%NLineStyles].ls_mark,
				LineStyles[(i-1)%NLineStyles].ls_space
			);
		} else {
			pllsty((i-1)%8);
		}
		plcol(i);

		/* get and convert data if necessary	*/
		if (InRadians && !XisAngular)
			for (j=0; j<NTuples; j++)
				A[j] = Data[j][i];
		else if ((!InRadians) && !XisAngular)
			for (j=0; j<NTuples; j++)
				A[j] = DTR(Data[j][i]);
		else
			for (j=0; j<NTuples; j++)
				R[j] = Data[j][i];

		/* convert to cartesian	A<->X  R<->Y*/
		for (j=0; j<NTuples; j++) {
			x0 = R[j] * cos(A[j]);
			y0 = R[j] * sin(A[j]);
			A[j] = x0;
			R[j] = y0;
		}
		if (VtoBoolean(V_PlotJoined)) {
			x0 = A[0];
			y0 = R[0];
			for (j=1; j<NTuples; j++) {
				pljoin(A[j-1], R[j-1], A[j],R[j]);
			}
		}

		if (VtoBoolean(V_PlotPoints)) {
			plschr(0., VtoDbl(V_PointScale));
			pllsty(1);
			if (NPointCodes > 0) {
				plpoin(NTuples, A, R, PointCodes[(i-1)%NPointCodes]);
			} else {
				plpoin(NTuples, A, R, i);
			}
		}
	}

	/* restore line styles etc... */
	pllsty(1);
	plcol(1);
	plschr(0., VtoDbl(V_LabelScale));
	pllab(VtoString(V_XLabel), VtoString(V_YLabel), "");
	plschr(0., VtoDbl(V_TitleScale));
	pllab("","", VtoString(V_PlotTitle));
}


void
StatisticsOfX(Data, NTuples,TupleSize, Median, StdDev)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
FLOAT	*Median;
FLOAT	*StdDev;
{
#ifdef	ANSI_C
	FLOAT	MedianOfX();
	FLOAT	StdDevOfX();
/*debug
	FLOAT	MedianOfX(FLOAT **Data,int NTuples,int TupleSize);
	FLOAT	StdDevOfX(FLOAT **Data,int NTuples,int TupleSize);
debug*/
#else
	FLOAT	MedianOfX();
	FLOAT	StdDevOfX();
#endif
	*StdDev = StdDevOfX(Data,NTuples,TupleSize);	
	*Median = MedianOfX(Data,NTuples,TupleSize);
}


void
StatisticsOfY(Data, NTuples,TupleSize, Median, StdDev)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
FLOAT	*Median;
FLOAT	*StdDev;
{
#ifdef	ANSI_C
	FLOAT	MedianOfY();
	FLOAT	StdDevOfY();
/*debug
	FLOAT	MedianOfY(FLOAT **Data,int NTuples,int TupleSize);
	FLOAT	StdDevOfY(FLOAT **Data,int NTuples,int TupleSize);
debug*/
#else
	FLOAT	MedianOfY();
	FLOAT	StdDevOfY();
#endif
	*StdDev = StdDevOfY(Data,NTuples,TupleSize);	
	*Median = MedianOfY(Data,NTuples,TupleSize);
}


FLOAT
StdDevOfX(Data,NTuples,TupleSize)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
{
	int	i;
	FLOAT	StdDev, x;
	FLOAT	var, sum, mean;
	double sqrt(), fabs();

	if (VtoBoolean(V_SupplyAbscissa)) {
		return((FLOAT)(NTuples*NTuples)/12.0);
	}

	sum = 0.0;
	for (i=0; i<NTuples; i++)
		sum += Data[i][0];
	mean = sum / NTuples;

	var = 0.0;
	for (i=0; i<NTuples; i++) {
		x = fabs((double)(Data[i][0] -  mean));
		var += x * x;
	}
	var /= (NTuples-1);
	StdDev = sqrt((double)var);
	return(StdDev);	
}


FLOAT
StdDevOfY(Data,NTuples,TupleSize)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
{
	int	i,j,D;
	FLOAT	StdDev, x;
	FLOAT	var, sum, mean;
	FLOAT	n;
	double sqrt(), fabs();

	D = VtoBoolean(V_SupplyAbscissa) ? 0 : 1;
	n = NTuples * (TupleSize-D);
	sum = 0.0;
	for (i=0; i<NTuples; i++)
		for (j=D; j<TupleSize; j++) {
			sum += Data[i][j];
		}
	mean = sum / n;

	var = 0.0;
	for (i=0; i<NTuples; i++)
		for (j=D; j<TupleSize; j++) {
			x = fabs((double)(Data[i][j] -  mean));
			var += x * x;
		}
	var /= (n-1);
	StdDev = sqrt((double)var);
	return(StdDev);	
}

#define	BIG	1.0e30
#define	AFAC	1.5
#define	AMP	1.5

FLOAT
MedianOfX(Data,NTuples,TupleSize)
FLOAT	**Data;
int	NTuples;
int	TupleSize;
/* an iterative computation of the median...
 * Code taken from Numerical Recipes..
 */
{
	int	np, nm, i;
	FLOAT	xx,xp,xm,sumx,sum,eps,stemp,dum,ap,am,aa,a;
	FLOAT	Median;
	double	fabs();

	if (VtoBoolean(V_SupplyAbscissa))
		return((FLOAT)(0.5*NTuples));

	/* first estimate	*/
	a = 0.5 * (Data[0][0] + Data[NTuples-1][0]);
	eps = fabs((double)(Data[NTuples-1][0] - Data[0][0]));

	am = -(ap=BIG);
	for (;;) {
		sum = sumx = 0.0;
		np = nm = 0;		/* # point above and below median */
		xm = -(xp = BIG);

		for (i=0; i<NTuples; i++) {
			xx = Data[i][0];
			if (xx != a) {
				if (xx > a) {
					++np;
					if (xx < xp) xp = xx;
				} else if (xx < a) {
					++nm;
					if (xx > xm) xm = xx;
				}
				sum += dum=1.0/(eps+fabs((double)(xx-a)));
				sumx += xx * dum;
			}
		}

		stemp = (sumx / sum) - a;
		if ((np-nm) >= 2) {
			/* guess is too low make another pass */
			am = a;
			aa = stemp < 0.0? xp : xp + stemp*AMP;
			if (aa > ap) aa = 0.5*(a+ap);
			eps = AFAC * fabs((double)(aa-a));
			a = aa;
		} else if ((nm-np) >= 2) {
			/* guess is too high make another pass */
			ap = a;
			aa = stemp > 0.0? xm : xm+stemp*AMP;
			if (aa < am) aa = 0.5*(a+am);
			eps = AFAC*fabs((double)(aa-a));
			a = aa;
		} else {	/* got it	*/
			if (NTuples%2 == 0) {
				Median = 0.5*(np==nm? xp+xm : np>nm? a+xp : xm+a);
			} else {
				Median = np == nm? a : np>nm? xp : xm;
			}
			return(Median);
		}
	}
}

FLOAT
MedianOfY(Data,N,M)
FLOAT	**Data;
int	N;
int	M;
/* an iterative computation of the median...
 * Code taken from Numerical Recipes..
 */
{
	int	n, np, nm, i,j,D;
	FLOAT	xx,xp,xm,sumx,sum,eps,stemp,dum,ap,am,aa,a;
	FLOAT	Median;
	double	fabs();

	D = VtoBoolean(V_SupplyAbscissa) ? 0 : 1;
	n = N * (M-D);

	/* first estimate	*/
	a = 0.5 * (Data[0][D] + Data[N-1][M-1]);
	eps = fabs((double)(Data[N-1][M-1] - Data[0][D]));

	am = -(ap=BIG);
	for (;;) {
		sum = sumx = 0.0;
		np = nm = 0;		/* # point above and below median */
		xm = -(xp = BIG);

		for (i=0; i<N; i++) {
			for (j=D; j<M; j++) {
				xx = Data[i][j];
				if (xx != a) {
					if (xx > a) {
						++np;
						if (xx < xp) xp = xx;
					} else if (xx < a) {
						++nm;
						if (xx > xm) xm = xx;
					}
					sum += dum=1.0/(eps+fabs((double)(xx-a)));
					sumx += xx * dum;
				}
			}
		}

		stemp = (sumx / sum) - a;
		if ((np-nm) >= 2) {
			/* guess is too low make another pass */
			am = a;
			aa = stemp < 0.0? xp : xp + stemp*AMP;
			if (aa > ap) aa = 0.5*(a+ap);
			eps = AFAC * fabs((double)(aa-a));
			a = aa;
		} else if ((nm-np) >= 2) {
			/* guess is too high make another pass */
			ap = a;
			aa = stemp > 0.0? xm : xm+stemp*AMP;
			if (aa < am) aa = 0.5*(a+am);
			eps = AFAC*fabs((double)(aa-a));
			a = aa;
		} else {	/* got it	*/
			if (n%2 == 0) {
				Median = 0.5*(np==nm? xp+xm : np>nm? a+xp : xm+a);
			} else {
				Median = np == nm? a : np>nm? xp : xm;
			}
			return(Median);
		}
	}
}
