// -------------------------------------------------------------------------------------
// BarChart.m
// Martin D. Flynn, NeXT Computer, Inc.
// -------------------------------------------------------------------------------------

#import <appkit/appkit.h>
#import <libc.h>
#import <c.h>
#import <stdlib.h>
#import <string.h>
#import <math.h>
#import <dpsclient/wraps.h>
#import <streams/streams.h>
#import <objc/Storage.h>
#import <objc/List.h>
#import "BarChartPS.h"
#import "BarChart.h"

// -------------------------------------------------------------------------------------
// archive version number
#define	VERSION			2

// -------------------------------------------------------------------------------------
// data source types
#define	srcTypeSAMPLE	-1
#define	srcTypeNONE		0
#define	srcTypeTEXT		1
#define	srcTypeMATRIX	2
#define	srcTypeSTORAGE	3
#define	srcTypeSTREAM	4

// -------------------------------------------------------------------------------------
#define	X				origin.x
#define	Y				origin.y
#define	W				size.width
#define	H				size.height
#define	fontWIDTH(L)	[currentFont getWidthOf:L]

// -------------------------------------------------------------------------------------
// x/y plot/scale conversions
#define	xPOINT(P)		(cFlags.xLogScale? [self log:(P)] : (P))
#define	yPOINT(P)		(cFlags.yLogScale? [self log:(P)] : (P))
#define	xSCALE(P)		(dataScale.x * xPOINT(P))
#define	ySCALE(P)		(dataScale.y * yPOINT(P))
#define	xPLOT(P)		(axisOrigin.x + xSCALE(P))
#define	yPLOT(P)		(axisOrigin.y + ySCALE(P))

// -------------------------------------------------------------------------------------
@implementation BarChart

// -------------------------------------------------------------------------------------
// set archive version number
static BOOL	_nibMode = NO;
+ initialize
{
  [self setVersion:VERSION];
  if (!strcmp([NXApp appName], "InterfaceBuilder")) _nibMode = YES;
  return self;
}

// -------------------------------------------------------------------------------------
// return inspector name
- (const char*)getInspectorClassName { return "BarChartInspector"; }

// -------------------------------------------------------------------------------------
// color support 

/* convert color to gray */
static float _colorGray(NXColor color)
{
  float	gray;
  NXConvertColorToGray(color, &gray);
  return gray;
}

/* set color/gray */
static void _setColor(NXColor color, BOOL isColor)
{
  if (isColor) NXSetColor(color); else PSsetgray(_colorGray(color));
}

// -------------------------------------------------------------------------------------
// new
- initFrame:(const NXRect*)r
{

  /* instantiate view */
  self = [super initFrame:r];
  isFirstResponder = NO;

  /* init font */
  currentFont = [Font newFont:"Helvetica" size:10.0 matrix:NX_IDENTITYMATRIX];  

  /* initialize flags */
  memset(&cFlags, 0, sizeof(cFlags));
  cFlags.xShowLabels = YES;
  cFlags.xLogScale	 = NO;
  cFlags.xGridDraw	 = NO;
  cFlags.yShowLabels = YES;
  cFlags.yLogScale	 = NO;
  cFlags.yGridDraw	 = YES;
  cFlags.drawInColor = NO;

  /* initialize colors */
  backgroundColor = NXConvertGrayToColor(NX_LTGRAY);
  axisColor		  = NXConvertGrayToColor(NX_BLACK);
  yGridColor	  = NXConvertGrayToColor(NX_WHITE);
  xGridColor	  = NXConvertGrayToColor(NX_WHITE);
  
  /* default chart (for IB use) */
  dftChartType  = chartBAR;
  dftChartColor = NXConvertGrayToColor(NX_DKGRAY);

  /* init outlets */
  delegate		 = self;
  xLabelMatrix	 = (id)nil;
  yLabelMatrix	 = (id)nil;
  actionMatrix	 = (id)nil;

  /* init defaut chart */
  dataList	  = (id)nil;
  dataRange.X = 0.0;
  dataRange.Y = 0.0;
  dataRange.W = 10.0;
  dataRange.H = 5.0;
  tickMark.X  = 0.0;
  tickMark.Y  = 0.0;
  tickMark.W  = 2.0;
  tickMark.H  = 1.0;

  /* flag for rescaling */
  reScale = YES;
  
  return self;
}

/* free */
- free
{
  if (dataList) [dataList free];
  return [super free];
}

// -------------------------------------------------------------------------------------
// access to internal charting data structure list
// -------------------------------------------------------------------------------------

/* return charting data list */
- (Storage*)chartList
{
  return dataList;
}

/* return number of chart types in list */
- (int)chartCount
{
  return [dataList count];
}

/* return pointer to specific chart item */
- (chartData_s*)chartDataAt:(int)index
{
  return (chartData_s*)[dataList elementAt:index];
}

// -------------------------------------------------------------------------------------
// log conversion method
// -------------------------------------------------------------------------------------

/* return log converted data point */
- (float)log:(float)num
{
  return num? (float)log10(fabs((double)num)) : 0.0;
}

/* return antiLog converted data point */
- (float)exp:(float)num
{
  return (float)pow((double)10.0, (double)num);
}

// -------------------------------------------------------------------------------------
// chart data point conversion
// -------------------------------------------------------------------------------------

/* convert data point to chart */
- convertDataPointToChart:(NXPoint*)point
{
  point->x = xPLOT(point->x);
  point->y = xPLOT(point->y);
  return self;
}

/* convert chart point to data */
- convertChartPointToData:(NXPoint*)point
{
  NXPoint	p;
  p.x = (point->x - axisOrigin.x) / dataScale.x;
  p.y = (point->y - axisOrigin.y) / dataScale.y;
  point->x = cFlags.xLogScale? [self exp:p.x] : p.x;
  point->y = cFlags.yLogScale? [self exp:p.y] : p.y;
  return self;
}

// -------------------------------------------------------------------------------------
// set plot options
// -------------------------------------------------------------------------------------

/* set delegate */
- setDelegate:anObject
{
  delegate = anObject;
  return self;
}

/* return current delegate */
- delegate
{
  return delegate;
}

/* set font (make a new unflipped font copy) */
// This is done this way on purpose
- setFont:fontId
{
  currentFont = [Font newFont:[fontId name] size:[fontId pointSize] matrix:NX_IDENTITYMATRIX];  
  reScale = YES;
  return self;
}

/* return current font (NX_FLIPPEDMATRIX) */
// This is done this way on purpose (IB can't handle it properly any other way)
- font
{
  return [Font newFont:[currentFont name] size:[currentFont pointSize]];
}

/* return isColor status */
- (BOOL)drawInColor
{
  return cFlags.drawInColor;
}

/* set background transparent */
- setBackgroundTransparent:(BOOL)flag
{
  cFlags.transparent = flag;
  return self;
}

/* return background transparent state */
- (BOOL)backgroundTransparent
{
  return cFlags.transparent;
}

/* set background gray */
- setBackgroundGray:(float)gray
{
  backgroundColor = NXConvertGrayToColor(gray);
  cFlags.drawInColor = NO;
  return self;
}

/* return background gray */
- (float)backgroundGray
{
  return _colorGray(backgroundColor);
}

/* set background color */
- setBackgroundColor:(NXColor)color
{
  backgroundColor = color;
  cFlags.drawInColor = YES;
  return self;
}

/* return background color */
- (NXColor)backgroundColor
{
  return backgroundColor;
}

/* set default chart gray */
- setDftChartGray:(float)gray
{
  dftChartColor = NXConvertGrayToColor(gray);
  cFlags.drawInColor = NO;
  return self;
}

/* return default chart gray */
- (float)dftChartGray
{
  return _colorGray(dftChartColor);
}

/* set default chart gray */
- setDftChartColor:(NXColor)color
{
  dftChartColor = color;
  cFlags.drawInColor = YES;
  return self;
}

/* return default chart color */
- (NXColor)dftChartColor
{
  return dftChartColor;
}

/* set axis gray */
- setAxisGray:(float)gray
{
  axisColor = NXConvertGrayToColor(gray);
  cFlags.drawInColor = NO;
  return self;
}

/* return axis gray */
- (float)axisGray
{
  return _colorGray(axisColor);
}

/* set axis color */
- setAxisColor:(NXColor)color
{
  axisColor = color;
  cFlags.drawInColor = YES;
  return self;
}

/* return axis color */
- (NXColor)axisColor
{
  return axisColor;
}

/* set to draw x grid */
- setXGridDraw:(BOOL)flag
{
  cFlags.xGridDraw = flag;
  return self;
}

/* return x grid drawing status */
- (BOOL)xGridDraw
{
  return cFlags.xGridDraw;
}

/* set x grid gray */
- setXGridGray:(float)gray
{
  xGridColor = NXConvertGrayToColor(gray);
  cFlags.drawInColor = NO;
  return self;
}

/* return x grid gray */
- (float)xGridGray
{
  return _colorGray(xGridColor);
}

/* set x grid color */
- setXGridColor:(NXColor)color
{
  xGridColor = color;
  cFlags.drawInColor = YES;
  return self;
}

/* return x grid color */
- (NXColor)xGridColor
{
  return xGridColor;
}

/* set to draw y grid */
- setYGridDraw:(BOOL)flag
{
  cFlags.yGridDraw = flag;
  return self;
}

/* return y grid drawing status */
- (BOOL)yGridDraw
{
  return cFlags.yGridDraw;
}

/* set y grid gray */
- setYGridGray:(float)gray
{
  yGridColor = NXConvertGrayToColor(gray);
  cFlags.drawInColor = NO;
  return self;
}

/* return y grid gray */
- (float)yGridGray
{
  return _colorGray(yGridColor);
}

/* set y grid color */
- setYGridColor:(NXColor)color
{
  yGridColor = color;
  cFlags.drawInColor = YES;
  return self;
}

/* return y grid color */
- (NXColor)yGridColor
{
  return yGridColor;
}

/* set default chart type */
- setDftChartType:(int)chartType
{
  dftChartType = chartType;
  return self;
}

/* return default chart type */
- (int)dftChartType
{
  return dftChartType;
}

/* data range */
- setDataRange:(NXRect*)range
{
  dataRange = *range;
  reScale = YES;
  return self;
}

/* return data range */
- (NXRect*)dataRange
{
  return &dataRange;
}

/* tick marks (origin currently not used) */
- setTickMark:(NXRect*)tick
{
  tickMark = *tick;
  tickMark.X = tickMark.Y = 0.0;
  reScale = YES;
  return self;
}

/* return tick mark settings */
- (NXRect*)tickMark
{
  return &tickMark;
}

/* set x log scaling */
- setXLogScaling:(BOOL)flag
{
  cFlags.xLogScale = flag;
  reScale = YES;
  return self;
}

/* return x log scaling */
- (BOOL)xLogScaling
{
  return cFlags.xLogScale;
}

/* set y log scaling */
- setYLogScaling:(BOOL)flag
{
  cFlags.yLogScale = flag;
  reScale = YES;
  return self;
}

/* return y log scaling */
- (BOOL)yLogScaling
{
  return cFlags.yLogScale;
}

/* set x show labels */
- setXShowLabels:(BOOL)flag
{
  cFlags.xShowLabels = flag;
  reScale = YES;
  return self;
}

/* return x show labels */
- (BOOL)xShowLabels
{
  return cFlags.xShowLabels;
}

/* set y show labels */
- setYShowLabels:(BOOL)flag
{
  cFlags.yShowLabels = flag;
  reScale = YES;
  return self;
}

/* return y show labels */
- (BOOL)yShowLabels
{
  return cFlags.yShowLabels;
}

/* rotate x axis labels */
- setXLabelRotate:(BOOL)flag
{
  cFlags.xLabelRotate = flag;
  reScale = YES;
  return self;
}

/* return X axis label rotate state */
- (BOOL)xLabelRotate
{
  return cFlags.xLabelRotate;
}

/* rotate y axis labels */
- setYLabelRotate:(BOOL)flag
{
  cFlags.yLabelRotate = flag;
  reScale = YES;
  return self;
}

/* return Y axis label rotate state */
- (BOOL)yLabelRotate
{
  return cFlags.yLabelRotate;
}

// -------------------------------------------------------------------------------------
// actions
// -------------------------------------------------------------------------------------

/* redisplay chart from source */
- update:sender
{
  return [self display];
}

/* override from super to rescale if frame changes */
- sizeTo:(NXCoord)w :(NXCoord)h
{
  reScale = YES;
  return [super sizeTo:w :h];
}

/* add data source */
- (int)_addData:srcId:(int)sType chart:(int)cType:(float)width:(NXColor)color:(BOOL)isColor
{
  int			count;
  chartData_s	src = { sType, srcId, cType, NO, width, isColor, color };
  if (sType == srcTypeNONE) return -1;
  if (!dataList) dataList = [[[Storage alloc] initCount:1 elementSize:sizeof(chartData_s)
  	description:(char*)nil] empty];
  count = [dataList count];
  [dataList addElement:&src];
  return count;
}

/* return source type */
- (int)_srcType:sourceId
{
  if ([sourceId isKindOf:[Text    class]]) return srcTypeTEXT;
  if ([sourceId isKindOf:[Matrix  class]]) return srcTypeMATRIX;
  if ([sourceId isKindOf:[Storage class]]) return srcTypeSTORAGE;
  return srcTypeNONE;
}

/* add data source (returns list index) (gray) */
- (int)addDataSource:sourceId type:(int)type lineWidth:(float)width gray:(float)gray
{
  NXColor	color = NXConvertGrayToColor(gray);
  id		src = ([sourceId isKindOf:[ScrollView class]])? [sourceId docView] : sourceId;
  return [self _addData:src:[self _srcType:src] chart:type:width:color:NO];
}

/* add data source (returns list index) (color) */
- (int)addDataSource:sourceId type:(int)type lineWidth:(float)width color:(NXColor)color
{
  id	src = ([sourceId isKindOf:[ScrollView class]])? [sourceId docView] : sourceId;
  return [self _addData:src:[self _srcType:src] chart:type:width:color:YES];
}

/* add data stream (gray) */
- (int)addDataStream:(NXStream*)s type:(int)type lineWidth:(float)width gray:(float)gray
{
  NXColor	color = NXConvertGrayToColor(gray);
  return [self _addData:(id)s:srcTypeSTREAM chart:type:width:color:NO];
}

/* add data stream (color) */
- (int)addDataStream:(NXStream*)s type:(int)type lineWidth:(float)width color:(NXColor)color
{
  return [self _addData:(id)s:srcTypeSTREAM chart:type:width:color:YES];
}

/* default data source outlet (if added, index will always be zero) */
- setDataSource:anObject
{
  int		n;
  NXColor	color = dftChartColor;
  if (dataList) { [dataList free]; dataList = (id)nil; }
  if (!anObject) return self;
  n = cFlags.drawInColor?
  	[self addDataSource:anObject type:dftChartType lineWidth:-1.0 color:color]:
	[self addDataSource:anObject type:dftChartType lineWidth:-1.0 gray:_colorGray(color)];
  if (n >= 0) [self chartDataAt:n]->isEditable = YES;
  return self;
}

/* set x label matrix */
- setXLabelMatrix:anObject
{
  if (![anObject isKindOf:[Matrix class]]) return (id)nil;
  xLabelMatrix = anObject;
  reScale = YES;
  return self;
}

/* return current x-axis label matrix */
- xLabelMatrix
{
  return xLabelMatrix;
}

/* set y label matrix */
- setYLabelMatrix:anObject
{
  if (![anObject isKindOf:[Matrix class]]) return (id)nil;
  yLabelMatrix = anObject;
  reScale = YES;
  return self;
}

/* return current y-axis label matrix */
- yLabelMatrix
{
  return yLabelMatrix;
}

// -------------------------------------------------------------------------------------
// data retrieval methods
// -------------------------------------------------------------------------------------

/* return number of points in data chart set */
- (int)_pointCount:(chartData_s*)dtaPtr
{
  if (!dtaPtr) return MIN((floor(dataRange.W) - 1.0), 30.0); // inadequate for xLogScale?
  switch (dtaPtr->_srcType) {
    case srcTypeMATRIX:		return [[dtaPtr->_srcId cellList] count];
	case srcTypeSTORAGE:	return [dtaPtr->_srcId count];
  }
  return 0;
}

/* return specific point */
#define RND(S)	(srandom(S*21),(float)(random()&0xFFFF)/(float)0xFFFF)
- _dataItemAt:(int)index :(chartData_s*)dtaPtr :(float*)x:(float*)y
{
  id		cellId;
  NXPoint	*pt;
  
  /* check for sample default chart type */
  if (!dtaPtr) {
	float gx, gy;
    if (!_nibMode) return (id)nil;
	gx = dataRange.X+1.0+(float)(index*(cFlags.xLogScale?rint(dataRange.X+10.0/10.0):1));
	gy = dataRange.Y+dataRange.H*(cFlags.yLogScale?0.15:0.3)*(1.0+RND(index));
    *x = xPLOT(gx);
	*y = yPLOT(gy);
	return self;
  }
  
  /* normal chart type */
  switch (dtaPtr->_srcType) {
    case srcTypeMATRIX:		// Matrix
	  if (!(cellId = [[dtaPtr->_srcId cellList] objectAt:index])) return (id)nil;
	  if (![cellId respondsTo:@selector(floatValue)]) return (id)nil;
	  *y = yPLOT([cellId floatValue]);
	  *x = xPLOT((float)[cellId tag]);
	  return self;
	case srcTypeSTORAGE:	// Storage
	  pt = (NXPoint*)[dtaPtr->_srcId elementAt:index];
	  *x = xPLOT(pt->x);
	  *y = yPLOT(pt->y);
	  return self;
  }
  
  return (id)nil;
}

/* build storage object containing points fount in stream (MUST BE EXPLICITLY FREED) */
- _parseStream:(NXStream*)s :(BOOL)all
{
  int			chartType = dftChartType;
  NXColor		chartColor;
  BOOL			chartIsColor = NO, newChart;
  chartData_s	dtaSrc;
  id			dList = (id)nil;

  /* reset to beginning */
  NXSeek(s, 0L, NX_FROMSTART);
  
  /* read stream */
  for (newChart = YES; !NXAtEOS(s); ) {
	int v;
	NXPoint pt;
	char *p, b[256];
  
    /* read line */
	for (p = b; !NXAtEOS(s); p++) {
	  *p = (char)NXGetc(s);
	  if (!*p || (*p == '\n')) break;
	} *p = 0;
	
	/* check for command */
	if ((*b == ':') && !strncasecmp(b, ":chart ", 7)) {
	  char typeStr[256];
	  float red = -1.0, green = -1.0, blue = -1.0;
	  int n, rtn;
	  
	  /* default chart */
	  newChart = YES;
	  chartType = dftChartType;
	  chartColor = dftChartColor;
	  chartIsColor = cFlags.drawInColor;
	  if (sscanf(b, "%*s %s %n", typeStr, &n) != 1) continue;
	  
	  /* change chart type */
	  chartType = 0;
	  if (index(typeStr, '*')) chartType  = dftChartType;
	  if (index(typeStr, 'b')) chartType |= chartBAR;
	  if (index(typeStr, 'l')) chartType |= chartLINE;
	  if (index(typeStr, 'a')) chartType |= chartAREA;
	  if (index(typeStr, 'p')) chartType |= chartPOINT;
	  if (n > strlen(b)) continue;
	  
	  /* change color by name */
	  if (b[n] == '*') {
		char	c[128];
		sscanf(&b[n+1], "%s", c);
		if (!strcasecmp(c,"black"  )) {chartColor=NX_COLORBLACK  ; chartIsColor=NO ;} else
		if (!strcasecmp(c,"dkgray" )) {chartColor=NX_COLORDKGRAY ; chartIsColor=NO ;} else
		if (!strcasecmp(c,"ltgray" )) {chartColor=NX_COLORLTGRAY ; chartIsColor=NO ;} else
		if (!strcasecmp(c,"white"  )) {chartColor=NX_COLORWHITE  ; chartIsColor=NO ;} else
		if (!strcasecmp(c,"gray"   )) {chartColor=NX_COLORGRAY   ; chartIsColor=YES;} else
		if (!strcasecmp(c,"red"    )) {chartColor=NX_COLORRED    ; chartIsColor=YES;} else
		if (!strcasecmp(c,"orange" )) {chartColor=NX_COLORORANGE ; chartIsColor=YES;} else
		if (!strcasecmp(c,"yellow" )) {chartColor=NX_COLORYELLOW ; chartIsColor=YES;} else
		if (!strcasecmp(c,"green"  )) {chartColor=NX_COLORGREEN  ; chartIsColor=YES;} else
		if (!strcasecmp(c,"blue"   )) {chartColor=NX_COLORBLUE   ; chartIsColor=YES;} else
		if (!strcasecmp(c,"purple" )) {chartColor=NX_COLORPURPLE ; chartIsColor=YES;} else
		if (!strcasecmp(c,"cyan"   )) {chartColor=NX_COLORCYAN   ; chartIsColor=YES;} else
		if (!strcasecmp(c,"brown"  )) {chartColor=NX_COLORBROWN  ; chartIsColor=YES;} else
		if (!strcasecmp(c,"magenta")) {chartColor=NX_COLORMAGENTA; chartIsColor=YES;} else
		if (!strcasecmp(c,"clear"  )) {chartColor=NX_COLORCLEAR  ; chartIsColor=YES;}
		continue;
	  }
	  
	  /* change color by number */
	  rtn = sscanf(&b[n], "%f %f %f", &red, &green, &blue);
	  chartIsColor = (rtn == 3)? YES : NO;
	  if (chartIsColor) chartColor = NXConvertRGBToColor(red, green, blue);
	  else chartColor = NXConvertGrayToColor(red);
	  continue;
	}

	/* new chart */
	if (newChart) {
	  if (dList && !all) break;	// already have the first set
	  memset(&dtaSrc, 0, sizeof(chartData_s));
	  dtaSrc._srcType = srcTypeSTORAGE;
	  dtaSrc._srcId   = [[[Storage alloc] initCount:1 elementSize:sizeof(NXPoint)
	  	description:(char*)nil] empty];
	  dtaSrc.chartType  = chartType;
	  dtaSrc.isEditable = NO;
	  dtaSrc.lineWidth  = -1.0;
	  dtaSrc.isColor    = chartIsColor;
	  dtaSrc.chartColor = chartColor;
	  if (!dList) dList = [[[Storage alloc] initCount:1 elementSize:sizeof(chartData_s)
			description:(char*)nil] empty];
	  [dList addElement:&dtaSrc];
	  newChart = NO;
	}
	
	/* parse data point */
	v = sscanf(b, "%f %f ", &pt.x, &pt.y);
	if (v <= 0) continue;
	if (v == 1) { pt.y = pt.x; pt.x = (float)[dtaSrc._srcId count]; }
	[dtaSrc._srcId addElement:&pt];
	
  }
  
  /* return parse data list */
  if (dList && ![dList count]) { [dList free]; dList = (id)nil; }
  return dList;
  
}

// -------------------------------------------------------------------------------------
// plot chart
// -------------------------------------------------------------------------------------

/* chart data */
- _chartData:(chartData_s*)dtaPtr
{
  BOOL	first;
  float	x, y, lineWidth;
  int	i, cnt = [self _pointCount:dtaPtr], chartType;

  /* check for sample chart type */
  if (dtaPtr) {
	if (dtaPtr->isColor) NXSetColor(dtaPtr->chartColor);
	else PSsetgray(_colorGray(dtaPtr->chartColor));
	chartType = dtaPtr->chartType;
	lineWidth = dtaPtr->lineWidth;
  } else {
	_setColor(dftChartColor, cFlags.drawInColor);
	chartType = dftChartType;
	lineWidth = -1.0;
  }

  /* area chart */
  if (chartType & chartAREA) {
	PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
	for (first = YES, i = 0; i < cnt ; i++) {
	  if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
	  if (first) { PSmoveto(x, axisOrigin.y); first = NO; }
	  PSlineto(x, y);
	}
	if (!first) { PSlineto(x, axisOrigin.y); PSclosepath(); PSfill(); }
  }

  /* bar chart */
  if (chartType & chartBAR) {
	PSsetlinewidth((lineWidth>=0.0)?xSCALE(lineWidth):xSCALE(0.8));
	for (i = 0; i < cnt ; i++) {
	  if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
	  _barALine(x, axisOrigin.y, x, y);
	}
  }

  /* line chart */
  if (chartType & chartLINE) {
	PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
	PSnewpath();
	for (first = YES, i = 0; i < cnt ; i++) {
	  if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
	  if (first) { PSmoveto(x, y); first = NO; }
	  else PSlineto(x, y);
	}
	if (!first) PSstroke();
  }

  /* point chart */
  if (chartType & chartPOINT) {
	PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
	for (i = 0; i < cnt ; i++) {
	  if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
	  _barDrawPoint(x, y, 3.0);
	}
  }
  
  return self;
}

// -------------------------------------------------------------------------------------
// draw chart view
// -------------------------------------------------------------------------------------

/* calculate printed decimal precision */
static int _dPrecision(float num)
{
  int		p;
  char		floatStr[32], *fPtr;
  if (num > 9999.0) return 0;
  sprintf(floatStr, "%f", num);
  for (fPtr = floatStr+strlen(floatStr)-1; (*fPtr=='0') || (*fPtr==' ');) *(fPtr--) = 0;
  for (fPtr = floatStr; *fPtr && (*fPtr != '.'); fPtr++);
  p = (*fPtr)? strlen(fPtr + 1) : 0;
  return p;
}

/* rescale axis */
- _rescaleAxis
{
  char		label[64];
  NXSize	xLS, yLS;
  float		fontHeight = [currentFont pointSize] * 0.76;

  /* save upper range limit */
  maxRange.x = dataRange.X + dataRange.W;
  maxRange.y = dataRange.Y + dataRange.H;

  /* check number of tick marks */
  if (!cFlags.xLogScale && (dataRange.W / tickMark.W >= 250.0))
	  tickMark.W = dataRange.W / 250.0;
  if (!cFlags.yLogScale && (dataRange.H / tickMark.H >= 250.0))
	  tickMark.H = dataRange.H / 250.0;

  /* calc tick mark length and line width */
  tickLength = MAX(MIN(fontHeight, 12.0), 5.0);
  tickWidth  = ([currentFont pointSize] >= 20.0)? 2.0 : 1.0;

  /* label precision */
  precisionX = _dPrecision(tickMark.W);
  precisionY = _dPrecision(tickMark.H);
	
  /* label size */
  if (xLabelMatrix) [xLabelMatrix getCellSize:&xLblSize];
  else {
	sprintf(label, "%.*f", precisionX, maxRange.x);
	xLblSize.width  = fontWIDTH(label);
	xLblSize.height = fontHeight;
  }
  if (!cFlags.xLabelRotate) xLS = xLblSize;
  else { xLS.width = xLblSize.height; xLS.height = xLblSize.width; }
  if (yLabelMatrix) [yLabelMatrix getCellSize:&yLblSize];
  else {
	sprintf(label, "%.*f", precisionY, maxRange.y);
	yLblSize.width  = fontWIDTH(label);
	yLblSize.height = fontHeight;
  }
  if (!cFlags.yLabelRotate) yLS = yLblSize;
  else { yLS.width = yLblSize.height; yLS.height = yLblSize.width; }

  /* calculate chart frame */
  chartFrame.X = (cFlags.yShowLabels?yLS.width :0.0) + tickLength / 2.0 + 2.0;
  if (chartFrame.X < xLS.width / 2.0) chartFrame.X = xLS.width / 2.0;
  chartFrame.Y = (cFlags.xShowLabels?xLS.height:0.0) + tickLength / 2.0 + 2.0;
  chartFrame.W = bounds.W - chartFrame.X - (cFlags.xShowLabels?xLS.width *0.50:2.0);
  chartFrame.H = bounds.H - chartFrame.Y - (cFlags.yShowLabels?yLS.height     :2.0);

  /* scale factor */
  dataScale.x = dataRange.W? chartFrame.W/(xPOINT(maxRange.x)-xPOINT(dataRange.X)) : 0.0;
  dataScale.y = dataRange.H? chartFrame.H/(yPOINT(maxRange.y)-yPOINT(dataRange.Y)) : 0.0;

  /* axis origin */
  axisOrigin.x = chartFrame.X - xSCALE(dataRange.X);  
  axisOrigin.y = chartFrame.Y - ySCALE(dataRange.Y);
  
  /* actual displayed axis position */
  axisPosition.x = xPLOT((maxRange.x > 0.0)? MAX(dataRange.X, 0.0): MIN(maxRange.x, 0.0));
  axisPosition.y = yPLOT((maxRange.y > 0.0)? MAX(dataRange.Y, 0.0): MIN(maxRange.y, 0.0));

  return self;
}

/* draw Y grid lines (parallel X-axis) */
- _drawYGridLines
{

  if (cFlags.yGridDraw && (tickMark.H > 0.0)) {
	float t = tickMark.H, y = t * (long)(dataRange.Y / t), p;
	if (y < dataRange.Y) y += t;
	_setColor(yGridColor, cFlags.drawInColor);
	PSsetlinewidth(tickWidth);
    for (p = y; p <= maxRange.y; p += t) {
	  _barRLine(chartFrame.X, yPLOT(p), chartFrame.W, 0.0);
	  if (cFlags.yLogScale) while (p / t >= 10.0) t *= 10.0;
	}
  }
  
  return self;
}

/* draw X grid lines (parallel Y-axis) */
- _drawXGridLines
{

  if (cFlags.xGridDraw && (tickMark.W > 0.0)) {
	float t = tickMark.W, x = t * (long)(dataRange.X / t), p;
	if (x < dataRange.X) x += t;
	_setColor(xGridColor, cFlags.drawInColor);
	PSsetlinewidth(tickWidth);
    for (p = x; p <= maxRange.x; p += t) {
	  _barRLine(xPLOT(p), chartFrame.Y, 0.0, chartFrame.H);
	  if (cFlags.xLogScale) while (p / t >= 10.0) t *= 10.0;
	}
  }
  
  return self;
}

/* draw Y axis */
- _drawYAxis
{

  PSsetlinewidth(tickWidth);
  _setColor(axisColor, cFlags.drawInColor);
  _barRLine(axisPosition.x, chartFrame.Y, 0.0, chartFrame.H);
  
  return self;
}

/* draw X axis */
- _drawXAxis
{

  PSsetlinewidth(tickWidth);
  _setColor(axisColor, cFlags.drawInColor);
  _barRLine(chartFrame.X, axisPosition.y, chartFrame.W, 0.0);
  
  return self;
}

/* draw Y-axis label */
- _drawYLabel:(int)i:(float)p:(NXPoint*)point
{
  char		label[64];
  NXRect	r;
  BOOL		stateSaved = NO;
	  
  /* label size */
  if (yLabelMatrix) {
	*label = 0;
	r.W = yLblSize.width;
	r.H = yLblSize.height;
  } else {
	sprintf(label, "%.*f", precisionY, p);
	r.W = fontWIDTH(label);
	r.H = yLblSize.height / 4.0;
  }

  /* label orientation */
  if (cFlags.yLabelRotate) {
    PSgsave(); stateSaved = YES;
	PStranslate(point->x - 1.0, MAX(point->y - r.H / 2.0, 0.0));
	PSrotate(90.0);
	r.X  = 0.0;
	r.Y  = 0.0;
  } else {
	r.X  = point->x - r.W - 1.0;
	r.Y  = MAX(point->y - r.H / 2.0, 0.0);
  }

  /* draw label */
  if (yLabelMatrix) {
	id cellId = [yLabelMatrix cellAt:i:0];
	if (!cellId) cellId = [yLabelMatrix cellAt:0:i];
	if (cellId) [cellId drawSelf:&r inView:self];
  } else _barDrawText(r.X, r.Y, label);
	  
  /* restore state */
  if (stateSaved) PSgrestore();
	  
  return self;
}

/* draw Y-axis labels */
- _drawYAxisLabels
{

  if (tickMark.H > 0.0) {
	NXPoint	point;
    int		i;
	float	t = tickMark.H, y = t * (long)(dataRange.Y / t), p;
	point.x = axisPosition.x - floor(tickLength / 2.0);
	if (y < dataRange.Y) y += t;
	if (!yLabelMatrix && cFlags.yShowLabels) [currentFont set];
    for (i = 0, p = y; p <= maxRange.y; p += t) {

	  /* draw tickMark */
	  point.y = yPLOT(p);
	  _setColor(axisColor, cFlags.drawInColor);
	  PSsetlinewidth(tickWidth);
	  _barRLine(point.x + 1.0, point.y, tickLength, 0.0);
	  if (!cFlags.yShowLabels) {
		if (cFlags.yLogScale) while (p / t >= 10.0) t *= 10.0;
	    continue;
	  }
	  
	  /* draw label */
	  if (!cFlags.yLogScale) {
		[self _drawYLabel:i++:p:&point];
	  } else
	  if (p / t >= 10.0) {
	    [self _drawYLabel:i++:p:&point];
		while (p / t >= 10.0) t *= 10.0;
	  }
	 
	}
  }
  
  return self;
}

/* draw X-axis label */
- _drawXLabel:(int)i:(float)p:(NXPoint*)point
{
  char		label[64];
  NXRect	r;
  BOOL		stateSaved = NO;
	  
  /* label size */
  if (xLabelMatrix) {
	*label = 0;
	r.W = xLblSize.width;
	r.H = xLblSize.height;
  } else {
	sprintf(label, "%.*f", precisionX, p);
	r.W = fontWIDTH(label);
	r.H = xLblSize.height;
  }

  /* label orientation */
  if (cFlags.xLabelRotate) {
    PSgsave(); stateSaved = YES;
	PStranslate(point->x + r.H / 2.0, MAX(point->y - r.W - 1.0, 0.0));
	PSrotate(90.0);
	r.X  = 0.0;
	r.Y  = 0.0;
  } else {
	r.X  = point->x - r.W / 2.0;
	r.Y  = MAX(point->y - r.H - 1.0, 0.0);
  }

  /* draw label */
  if (xLabelMatrix) {
	id cellId = [xLabelMatrix cellAt:0:i];
	if (!cellId) cellId = [xLabelMatrix cellAt:i:0];
	if (cellId) [cellId drawSelf:&r inView:self];
  } else _barDrawText(r.X, r.Y, label);
	  
  /* restore state */
  if (stateSaved) PSgrestore();
  
  return self;
}

/* draw X-axis labels */
- _drawXAxisLabels
{

  if (tickMark.W > 0.0) {
	NXPoint	point;
    int		i;
	float	t = tickMark.W, x = t * (long)(dataRange.X / t), p;
	point.y = axisPosition.y - floor(tickLength / 2.0);
	if (x < dataRange.X) x += t;
	if (!xLabelMatrix && cFlags.xShowLabels) [currentFont set];
    for (i = 0, p = x; p <= maxRange.x; p += t) {
	  
	  /* draw tickMark */
	  point.x = xPLOT(p);
	  _setColor(axisColor, cFlags.drawInColor);
	  PSsetlinewidth(tickWidth);
	  _barRLine(point.x, point.y + 1.0, 0.0, tickLength);
	  if (!cFlags.xShowLabels) {
		if (cFlags.xLogScale) while (p / t >= 10.0) t *= 10.0;
	    continue;
	  }
	  
	  /* draw label */
	  if (!cFlags.xLogScale) {
	    [self _drawXLabel:i++:p:&point];
	  } else
	  if (p / t >= 10.0) {
	    [self _drawXLabel:i++:p:&point];
		while (p / t >= 10.0) t *= 10.0;
	  }

	}
  }
  
  return self;
}

/* draw data */
- _drawChartData
{

  PSgsave();
  _barClipBox(chartFrame.X, chartFrame.Y, chartFrame.W, chartFrame.H);
  if (dataList) {
    int s;
	for (s = 0; s < [dataList count]; s++) {
	  chartData_s *dtaPtr = (chartData_s*)[dataList elementAt:s];
	  if ((dtaPtr->_srcType == srcTypeTEXT) || (dtaPtr->_srcType == srcTypeSTREAM)) {
	    id list, src = dtaPtr->_srcId;
	    NXStream *s = (dtaPtr->_srcType==srcTypeTEXT)?[src stream]:(NXStream*)src;
	    if (list = [self _parseStream:s:YES]) {
	      int i, cnt = [list count];
		  for (i = 0; i < cnt; i++) {
	        chartData_s *dp = (chartData_s*)[list elementAt:i];
			dp->lineWidth = dtaPtr->lineWidth;
	        [self _chartData:dp];
		    [dp->_srcId free];
		  }
		  [list free];
	    }
	  } else [self _chartData:dtaPtr];
	}
  } else [self _chartData:(chartData_s*)nil];
  PSgrestore();	// remove clip path
  
  return self;
}

/* draw all chart types */
// this method has been arranged to provide for easy subclassing
- _drawChart
{

  [self _drawYGridLines];		// Y axis grid lines
  [self _drawXGridLines];		// X axis grid lines
  [self _drawChartData];		// chart data
  [self _drawYAxis];			// Y axis
  [self _drawXAxis];			// X axis
  [self _drawYAxisLabels];		// Y axis labels
  [self _drawXAxisLabels];		// X axis labels
  
  return self;
}

/* draw self */
- drawSelf:(const NXRect *)r :(int)n
{
  
  /* fill background */
  if (!cFlags.transparent) {
	_setColor(backgroundColor, cFlags.drawInColor);
	NXRectFill(&bounds);
  }

  /* rescale if necessary */
  if (reScale) { [self _rescaleAxis]; reScale = NO; }
  
  /* draw */
  [self _drawChart];
  
  /* highlight if first responder */
//if (isFirstResponder) NXHighlightRect(&bounds);

  return self;
}

// -------------------------------------------------------------------------------------
// object archiving stuff

- read:(NXTypedStream*)s
{
  int	ver;
  [super read:s];
  NXReadTypes(s, "i",&ver);
  NXReadRect (s, &dataRange);
  NXReadRect (s, &tickMark);
  NXReadTypes(s, "@is",&currentFont,&dftChartType,&cFlags);
  if (ver <= 1) { float dmy; NXReadTypes(s, "fffff",&dmy,&dmy,&dmy,&dmy,&dmy); }
  backgroundColor = NXReadColor(s);
  dftChartColor	  = NXReadColor(s);
  axisColor		  = NXReadColor(s);
  xGridColor	  = NXReadColor(s);
  yGridColor	  = NXReadColor(s);
  delegate		  = self;
  xLabelMatrix	  = (id)nil;
  yLabelMatrix	  = (id)nil;
  actionMatrix	  = (id)nil;
  dataList        = (id)nil;
  reScale         = YES;
  return self;
}

- write:(NXTypedStream*)s
{
  int	ver = [[self class] version];
  [super write:s];
  NXWriteTypes(s, "i",&ver);
  NXWriteRect (s, &dataRange);
  NXWriteRect (s, &tickMark);
  NXWriteTypes(s, "@is",&currentFont,&dftChartType,&cFlags);
  NXWriteColor(s, backgroundColor);
  NXWriteColor(s, dftChartColor);
  NXWriteColor(s, axisColor);
  NXWriteColor(s, xGridColor);
  NXWriteColor(s, yGridColor);
  return self;
}

// -------------------------------------------------------------------------------------
// firstResponder for getting copy/paste commands

/* allow becoming first responder */
- (BOOL)acceptsFirstResponder
{
  return YES;
}

/* become first responder */
- becomeFirstResponder
{
  isFirstResponder = YES;
  [self display];
  return self;
}

/* resign first responder */
- resignFirstResponder
{
  isFirstResponder = NO;
  [self display];
  return self;
}

// -------------------------------------------------------------------------------------
// mouseDown hit detection

/* handle point selected */
- _selectedPoint:(chartData_s*)dtaPtr:(NXPoint*)point:(int)index clicks:(int)clicks
{
  if (clicks == 2) {
	if (delegate && [delegate respondsTo:@selector(chartDataSelected:index:)]) {
	  [delegate chartDataSelected:dtaPtr index:index];
	}
  }
  return self;
}

/* chart data */
- (BOOL)_hit:(NXPoint*)pt data:(chartData_s*)dtaPtr hit:(NXPoint*)hit:(int*)index
{
  NXPoint	w, h;
  int		i, cnt = [self _pointCount:dtaPtr];

  /* valid range */
  if (dtaPtr->chartType & chartBAR) { w.x = xSCALE(0.8) / 2.0; w.y = 0; }
  else { w.x = 3.0; w.y = w.x; }

  /* check hit */
  for (i = 0; i < cnt; i++) {
	if (![self _dataItemAt:i :dtaPtr :&h.x:&h.y]) break;
	if ((pt->x >= h.x - w.x) && (pt->x <= h.x + w.x)                 &&
	    (((dtaPtr->chartType & chartBAR) && (pt->y <= h.y + 8.0)) ||
		 ((pt->y >= h.y - w.y) && (pt->y <= h.y + w.y))             )   ) {
	  *index = i;
	  *hit = h;
	  return YES;
	} 
  }

  /* no hit */
  return NO;

}

/* data point hit detection */
- _dataHitDetection:(NXPoint*)pt clicks:(int)clicks
{
  NXPoint		hPt;
  int			s, index = -1;
  
  /* ignore if no data points */
  if (!dataList) return self;
  
  /*loop through datasets to find hit */
  for (s = [dataList count]; s;) {
	BOOL hit = NO;
	chartData_s *dtaPtr = (chartData_s*)[dataList elementAt:--s];
	if ((dtaPtr->_srcType == srcTypeTEXT) || (dtaPtr->_srcType == srcTypeSTREAM)) {
	  id list, src = dtaPtr->_srcId;
	  NXStream *s = (dtaPtr->_srcType == srcTypeTEXT)? [src stream] : (NXStream*)src;
	  if (list = [self _parseStream:s:NO]) {
	    int i;
	    hit = [self _hit:pt data:(chartData_s*)[list elementAt:0] hit:&hPt:&index];
		for (i=0;i<[list count];i++) [((chartData_s*)[list elementAt:i])->_srcId free];
		[list free];
	  }
	} else hit = [self _hit:pt data:dtaPtr hit:&hPt:&index];
	if (hit) { [self _selectedPoint:dtaPtr:&hPt:index clicks:clicks]; break; }
  }

  return self;
}

/* intercept mouse down */
- mouseDown:(NXEvent*)e
{
  NXPoint	pt = e->location;
  [self convertPoint:&pt fromView:(id)nil];
  if (![self mouse:&pt inRect:&chartFrame]) return [super mouseDown:e];
  if (e->data.mouse.click == 2) [self _dataHitDetection:&pt clicks:2];
  return [super mouseDown:e];
}

// -------------------------------------------------------------------------------------
// chart data point change/selection delegate methods

/* data point selected */
- chartDataSelected:(chartData_s*)dtaPtr index:(int)index
{
  id c, m = actionMatrix, srcId = dtaPtr->_srcId;
  if (!m && [srcId isKindOf:[Matrix class]] && ([srcId target] != self)) m = srcId;
  if (m && (c = [[m cellList] objectAt:index])) { [m selectCell:c]; [m sendAction]; }
  return self;
}

@end
