ftp.nice.ch/pub/next/science/mathematics/nxyplot.NIHS.bs.tar.gz#/nxyplot/Source/PlotView.m

This is PlotView.m in view mode; [Download] [Up]

/* Generated by Interface Builder */

#import "defs.h"
#import "PlotView.h"
#import "Plot.h"
#import <appkit/SavePanel.h>
#import <appkit/color.h>
#import <appkit/FormCell.h>
#import <appkit/PrintPanel.h>
#import <appkit/Pasteboard.h>

/* The following routines are in auxil.m: */
extern void count_labels(int *, double *, double, double, double);
extern void autoformat(double, double, double, int *);
extern void handformat(float, char *, int *);

@implementation PlotView

- (BOOL) acceptsFirstMouse { return YES;}      /* grab that mouse down event! */

- provideMainTitleFont   { return newMainTitleFont;}
- provideXTitleFont      { return newXTitleFont;}
- provideYTitleFont      { return newYTitleFont;}
- provideLegendTitleFont { return newLegendTitleFont;}
- provideLegendFont      { return newLegendFont;}
- provideTicLabelFont    { return newTicLabelFont;}
- (NXPoint) provideLegendBoxOrigin { return legendbox.origin;}
- (NXPoint) provideXTitleBoxOrigin { return xtitlebox.origin;}
- (NXPoint) provideYTitleBoxOrigin { return ytitlebox.origin;}
- (NXPoint) provideMainTitleBoxOrigin { return maintitlebox.origin;}

- provideWindowFrame:(NXRect *)windowframe
{
  return [[self window] getFrame:windowframe];
}

/* All these method names that start with "force" would more naturally start
 * with "set", but everything malfunctions then; apparently it's a bad idea
 * to have methods whose names begin with "set".  Is it also a bad idea to
 * have methods whose names begin with "init"?
 */
- forceWindowFrame:(NXRect *)windowframe
{
  [[self window] placeWindow:windowframe];
  oldbounds = bounds;		/* have to do this right here so that the */
  [self display];		/* title boxes are not moved around again */
  return self;
}

- forceLegendBoxOrigin: (NXPoint)point
{
  legendbox.origin = point;
  return self;
}

- forceXTitleBoxOrigin: (NXPoint)point
{
  xtitlebox.origin = point;
  return self;
}

- forceYTitleBoxOrigin: (NXPoint)point
{
  ytitlebox.origin = point;
  return self;
}

- forceMainTitleBoxOrigin: (NXPoint)point
{
  maintitlebox.origin = point;
  return self;
}

- forceMainTitleFont:(char *)fontname :(float)fontsize
{
  newMainTitleFont =
    [Font newFont:fontname size:fontsize style:0 matrix:NX_IDENTITYMATRIX];
  return self;
}

- forceXTitleFont:(char *)fontname :(float)fontsize;
{
  newXTitleFont =
    [Font newFont:fontname size:fontsize style:0 matrix:NX_IDENTITYMATRIX];
  return self;
}

- forceYTitleFont:(char *)fontname :(float)fontsize;
{
  newYTitleFont =
    [Font newFont:fontname size:fontsize style:0 matrix:NX_IDENTITYMATRIX];
  return self;
}

- forceLegendFont:(char *)fontname :(float)fontsize;
{
  newLegendFont =
    [Font newFont:fontname size:fontsize style:0 matrix:NX_IDENTITYMATRIX];
  return self;
}

- forceLegendTitleFont:(char *)fontname :(float)fontsize;
{
  newLegendTitleFont =
    [Font newFont:fontname size:fontsize style:0 matrix:NX_IDENTITYMATRIX];
  return self;
}

- forceTicLabelFont:(char *)fontname :(float)fontsize;
{
  newTicLabelFont =
    [Font newFont:fontname size:fontsize style:0 matrix:NX_IDENTITYMATRIX];
  return self;
}

- initFrame:(const NXRect *)frameRect
{
  [super initFrame:frameRect];
  legendbox.origin.x = XOFFSET + 50.0; /* get the legendbox initialized here */
  legendbox.origin.y = YOFFSET + 50.0;
  xtitlebox.origin.x = XOFFSET/4.0
                   + (bounds.size.width - DEFAULTAXISTITLEWIDTH)/2.0;
  xtitlebox.origin.y = bounds.origin.y + 10.0;
  xtitlebox.size.width  = DEFAULTAXISTITLEWIDTH;
  xtitlebox.size.height = DEFAULTFONTSIZE;
  ytitlebox.origin.x = bounds.origin.x + 6.0;
  ytitlebox.origin.y = YOFFSET/4.0
                   + (bounds.size.height - DEFAULTAXISTITLEWIDTH)/2.0;
  ytitlebox.size.width  = DEFAULTFONTSIZE;
  ytitlebox.size.height = DEFAULTAXISTITLEWIDTH;
  maintitlebox.origin.x = XOFFSET/4.0
                       + (bounds.size.width - DEFAULTMAINTITLEWIDTH)/2.0;
  maintitlebox.origin.y = bounds.size.height - 2.0 - 14.0;
  maintitlebox.size.width  = DEFAULTMAINTITLEWIDTH;
  maintitlebox.size.height = 14.0;

/*
 * With NS 3.0, we start handling the dragging code here (not in PlotDelegate).
 * registerForDraggedTypes is a new method in the View class.
 */
  [self registerForDraggedTypes:&NXFilenamePboardType count:1];

  oldbounds = bounds;

  return self;
}

- (NXCoord *)xdata:(int)n	/* we use this in drawing the legend curves */
{
  xlegend[0] = (legendbox.origin.x + 5.33333)/ppxunit;
/* We used to have 5.0 instead of 5.33333, but that can give erroneous-looking
 * (albeit correct) results when writing to the screen: the pixels that are
 * turned on when drawing a filled circle, for example, are much different
 * when the circle's center is precisely at a pixel than when the circle's center
 * is not precisely at a pixel.  The results are much better when the center
 * of the circle is at a half-pixel point, and so that's what we do.
 */
  xlegend[1] = xlegend[0] + 40.0/ppxunit;
  /* curve fragment in legend is 40 pixels in length */
  if (!drawingLegendLines) xlegend[0] = 0.5*(xlegend[0] + xlegend[1]);
  return xlegend;
}

- (NXCoord **)ydata:(int)n	/* we use this in drawing the legend curves */
{
  int ncurves = [plotParam nCurvesTotal];
  int j;
  float yloc, yhgt;
  const char * curvetitle;

  if (newLegendFont) {
    yhgt = [newLegendFont pointSize];
  }
  else {
    yhgt = DEFAULTFONTSIZE;
  }

  yloc = (legendbox.origin.y - 2.0)/ppyunit;

  ylegend = (NXCoord **)realloc((void *)ylegend, ncurves*sizeof(NXCoord *));
  for (j = 0; j < ncurves; j++) {
    *(ylegend+j) = (NXCoord *)NULL; /* this is necessary */
    *(ylegend+j) = (NXCoord *)realloc((void *)*(ylegend+j), 2*sizeof(NXCoord));
    /* check for no lines and no symbols */
    if ( ([plotParam providelinestyle:j] == NOLINE)  &&
	([plotParam providesymbolstyle:j] == NOSYMBOL) ) continue;
    /* check for empty curvetitle */
    curvetitle = [legendForm stringValueAt:j];
    if ((drawingLegendLines || drawingLegendSymbols) && curvetitle[0]=='\0')
      continue;
    yloc = yloc + (yhgt+0.33333)/ppyunit;
/* We used to have just yhgt (instead of yhgt+0.33333); see comments in preceding
 * routine for the reason to change it.
 */
  }

  for (j=0; j<ncurves; j++) {
    *(*(ylegend+j)+0) = yloc;
    *(*(ylegend+j)+1) = yloc;
    /* check for no lines and no symbols */
    if( ([plotParam providelinestyle:j] == NOLINE)  &&
       ( [plotParam providesymbolstyle:j] == NOSYMBOL) )continue;
    /* check for empty curvetitle */
    curvetitle = [legendForm stringValueAt:j];
    if ((drawingLegendLines || drawingLegendSymbols) && curvetitle[0]=='\0')
      continue;
    yloc -= yhgt/ppyunit;
  }
  return ylegend;
}

- (BOOL)has_ebars:(int)n
{
  return NO;
}

- (int)nPoints:(int)n
{
  if (drawingLegendLines)  return 2;
  else return 1;
}

- (int)nCurves:(int)n
{
  return [plotParam nCurvesTotal];
}

- (int)nFiles
{
  return 1;
}

- startPlot
{
  const char * xtitle = [xTitle stringValueAt:0];
  const char * ytitle = [yTitle stringValueAt:0];
  const char * maintitle = [mainTitle stringValueAt:0];
  float yhgt;
  id mainTitleFont,axisTitleFont;

  NXSetColor([plotParam provideBackgroundColor]);
  [self clear:self];
  PSsetlinewidth(NXDrawingStatus==NX_DRAWING? 0.0 :
		 [accPrintColorButton state]==0? LINE_WIDTH_IF_PRINTING_BW :
		                                 LINE_WIDTH_IF_PRINTING_COLOR);
  NXSetColor([plotParam provideTextColor]);

  /*  get new font from fontmanager */
  if ([plotParam shouldChangeXTitleFont]) {
    newXTitleFont = [theFontManager convertFont:[theFontManager selFont]];
  }

  if (newXTitleFont) {
    axisTitleFont = [Font newFont:[newXTitleFont name] 
		   size:[newXTitleFont pointSize]
		   style:[newXTitleFont style]
		   matrix:NX_IDENTITYMATRIX];
    yhgt = [newXTitleFont pointSize];
  }
  else {
    axisTitleFont =
      [Font newFont:"Helvetica" size:DEFAULTFONTSIZE
            style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = DEFAULTFONTSIZE;
  }

  [axisTitleFont set];

  xtitlebox.size.width  = [axisTitleFont getWidthOf:xtitle];
  xtitlebox.size.height = yhgt;
  if (oldbounds.size.width != bounds.size.width
      || oldbounds.size.height != bounds.size.height) {
/* Couldn't figure out how to do this with windowDidResize or superviewSizeChanged,
 * so this grubby idea (with the global oldbounds variable) is used instead.
 */
    xtitlebox.origin.x = (xtitlebox.origin.x + 0.5*xtitlebox.size.width)
      *(bounds.size.width/oldbounds.size.width) - 0.5*xtitlebox.size.width;
    xtitlebox.origin.y = (xtitlebox.origin.y + 0.5*xtitlebox.size.height)
      *(bounds.size.height/oldbounds.size.height) - 0.5*xtitlebox.size.height;
    legendbox.origin.x = (legendbox.origin.x + 0.5*legendbox.size.width)
      *(bounds.size.width/oldbounds.size.width) - 0.5*legendbox.size.width;
    legendbox.origin.y = (legendbox.origin.y + 0.5*legendbox.size.height)
      *(bounds.size.height/oldbounds.size.height) - 0.5*legendbox.size.height;
  }
  PSmoveto(xtitlebox.origin.x, xtitlebox.origin.y);
  PSshow((char *)xtitle);

  /*  get new font from fontmanager */
  if ([plotParam shouldChangeYTitleFont]) {
    newYTitleFont =
      [theFontManager convertFont:[theFontManager selFont]];
  }

  if (newYTitleFont) {
    axisTitleFont = [Font newFont:[newYTitleFont name] 
		   size:[newYTitleFont pointSize]
		   style:[newYTitleFont style]
		   matrix:NX_IDENTITYMATRIX];
    yhgt = [newYTitleFont pointSize];
  }
  else {
    axisTitleFont =
      [Font newFont:"Helvetica" size:DEFAULTFONTSIZE
            style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = DEFAULTFONTSIZE;
  }

  [axisTitleFont set];

  ytitlebox.size.width  = yhgt;
  ytitlebox.size.height = [axisTitleFont getWidthOf:ytitle];
  if (oldbounds.size.width != bounds.size.width
      || oldbounds.size.height != bounds.size.height) {
    ytitlebox.origin.x = (ytitlebox.origin.x + 0.5*ytitlebox.size.height)
      *(bounds.size.width/oldbounds.size.width) - 0.5*ytitlebox.size.height;
    ytitlebox.origin.y = (ytitlebox.origin.y + 0.5*ytitlebox.size.width)
      *(bounds.size.height/oldbounds.size.height) - 0.5*ytitlebox.size.width;
  }
  PSmoveto(ytitlebox.origin.x + yhgt, ytitlebox.origin.y);
  PSgsave();
  PSrotate(90.0);
  PSshow((char *)ytitle);
  PSgrestore();
  
  if ([plotParam shouldChangeMainTitleFont]) {
    newMainTitleFont =
      [theFontManager convertFont:[theFontManager selFont]];
  }

  if (newMainTitleFont) {
    mainTitleFont = [Font newFont:[newMainTitleFont name] 
		   size:[newMainTitleFont pointSize]
		   style:[newMainTitleFont style]
		   matrix:NX_IDENTITYMATRIX];
    yhgt = [newMainTitleFont pointSize];
  }
  else {
    mainTitleFont =
      [Font newFont:"Helvetica" size:14.0 style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = 14.0;
  }

  [mainTitleFont set];

  maintitlebox.size.width  = [mainTitleFont getWidthOf:maintitle];
  maintitlebox.size.height = yhgt;
  if (oldbounds.size.width != bounds.size.width
      || oldbounds.size.height != bounds.size.height) {
    maintitlebox.origin.x = (maintitlebox.origin.x + 0.5*maintitlebox.size.width)
      *(bounds.size.width/oldbounds.size.width) - 0.5*maintitlebox.size.width;
    maintitlebox.origin.y = (maintitlebox.origin.y + 0.5*maintitlebox.size.height)
      *(bounds.size.height/oldbounds.size.height) - 0.5*maintitlebox.size.height;
    oldbounds = bounds;
  }
  PSmoveto(maintitlebox.origin.x, maintitlebox.origin.y);
  PSshow((char *)maintitle);

  // box around the plot:
  if ([borderBoxOnOff state]) {
    PSsetlinewidth([borderBoxThicknessText floatValue]);
    PSmoveto(0.0, 0.0);
    PSlineto(bounds.size.width, 0.0);
    PSlineto(bounds.size.width, bounds.size.height);
    PSlineto(0.0, bounds.size.height);
    PSlineto(0.0, 0.0);
    PSstroke();
    PSsetlinewidth(NXDrawingStatus==NX_DRAWING? 0.0 :
		 [accPrintColorButton state]==0? LINE_WIDTH_IF_PRINTING_BW :
		                                 LINE_WIDTH_IF_PRINTING_COLOR);
  }
  // The preceding doesn't work quite right: for some reason the vertical line
  // on the right hand side of the plot doesn't show up on the display.  The
  // line is there if you print or preview the file, however.
  return self;
}

- setDrawColor:(float) color
{
  PSsetgray(color);
  return self;
}

- drawLines:sender :(BOOL)xaxislog :(BOOL)yaxislog
/*
 * This is coded so that when drawLines is called with plotParam as argument,
 * it draws the data curves; when drawLines is called with self (plotView) as
 * argument, it draws the short line segments in the legend box.
 */
{
  int i, j, jrun, n;
  NXCoord *x;
  NXCoord **y;
  int npoints, ncurves;
  int curveindex = 0;		/* cumulative index */
  int linestyle;
  float thick = [lineThicknessText floatValue];
  float pattern0[] = {};	/* solid      */
  float pattern1[] = {4.0, 4.0}; /* dash       */
  float pattern2[] = {1.0, 3.0}; /* dot        */
  float pattern3[] = {7.0, 3.0, 3.0, 3.0}; /* chain dash */
  float pattern4[] = {7.0, 4.0, 1.0, 4.0}; /* chain dot  */
  const char * curvetitle;

  for (n=0; n<[sender nFiles]; n++) { /* loop over all active files */
    x  = [sender xdata:n];
    y  = [sender ydata:n];
    ncurves = [sender nCurves:n];
    npoints = [sender nPoints:n];
    PSnewpath();
    PSsetlinewidth(NXDrawingStatus==NX_DRAWING? thick :
	 [accPrintColorButton state]==0? MAX(thick, LINE_WIDTH_IF_PRINTING_BW) :
		                         MAX(thick, LINE_WIDTH_IF_PRINTING_COLOR));
    for (jrun=curveindex; jrun<curveindex+ncurves; jrun++) {
      linestyle = [plotParam providelinestyle:jrun];
      NXSetColor([plotParam provideCurveColor:jrun]);
      switch(linestyle) {
      case SOLID:
	PSsetdash(pattern0, 0, 0.0);
	break;
      case DASH:
	PSsetdash(pattern1, 2, 0.0);
	break;
      case DOT:
	PSsetdash(pattern2, 2, 0.0);
	break;
      case CHAINDASH:
	PSsetdash(pattern3, 4, 0.0);
	break;
      case CHAINDOT:
	PSsetdash(pattern4, 4, 0.0);
	break;
      case NOLINE:			/* no lines */
	continue;
      }
      if (linestyle == NOLINE) continue; /* no lines, go to next curve */
      /* If we're drawing the legend and there is no title, don't
       * bother to draw the line:
       */
      curvetitle = [legendForm stringValueAt:jrun];
      if (drawingLegendLines && curvetitle[0]=='\0') continue;
      j = jrun - curveindex;	/* for indexing into the y array */
      if (!xaxislog && !yaxislog) {
	PSmoveto(x[0]*ppxunit, *(*(y + j)) * ppyunit );
	for (i=1; i<npoints; i++) {
	  if ( (i % 512) == 0 ) {
	    PSstroke();
	    PSnewpath();
	    PSmoveto(x[i-1]*ppxunit, *(*(y+j)+i-1) * ppyunit);
	  }
	  PSlineto(x[i]*ppxunit, *(*(y+j)+i) * ppyunit);
	}
	PSstroke();
      }
      else if (!xaxislog && yaxislog) {
	PSmoveto(x[0]*ppxunit, (float)log10((double)*(*(y + j))) * ppyunit );
	for (i=1; i<npoints; i++) {
	  if ( (i % 512) == 0 ) {
	    PSstroke();
	    PSnewpath();
	    PSmoveto(x[i-1]*ppxunit, 
		     (float)log10((double)*(*(y+j)+i-1)) * ppyunit);
	  }
	  PSlineto(x[i]*ppxunit, (float)log10((double)*(*(y+j)+i)) * ppyunit);
	}
	PSstroke();
      }
      else if (xaxislog && !yaxislog) {
	PSmoveto((float)log10((double)x[0])*ppxunit, *(*(y + j)) * ppyunit );
	for (i=1; i<npoints; i++) {
	  if ( (i % 512) == 0 ) {
	    PSstroke();
	    PSnewpath();
	    PSmoveto((float)log10((double)x[i-1])*ppxunit,
		     *(*(y+j)+i-1) * ppyunit);
	  }
	  PSlineto((float)log10((double)x[i])*ppxunit, *(*(y+j)+i) * ppyunit);
	}
	PSstroke();
      }
      else if (xaxislog && yaxislog) {
	PSmoveto((float)log10((double)x[0])*ppxunit,
		 (float)log10((double)*(*(y + j))) * ppyunit );
	for (i=1; i<npoints; i++) {
	  if ( (i % 512) == 0 ) {
	    PSstroke();
	    PSnewpath();
	    PSmoveto((float)log10((double)x[i-1])*ppxunit,
		     (float)log10((double)*(*(y+j)+i-1)) * ppyunit);
	  }
	  PSlineto((float)log10((double)x[i])*ppxunit,
		   (float)log10((double)*(*(y+j)+i)) * ppyunit);
	}
	PSstroke();
      }
    }
    curveindex += ncurves;
  }
  PSsetdash(pattern0, 0, 0.0);	/* back to solid lines  */
  return self;
}

/*
 * Our first idea for drawing the symbols was: draw each symbol just once
 * in an off-screen window, then composite them in where needed.  This worked
 * fine for screen drawing (modulo a little difficulty in compositing to just
 * the right spot), but was a miserable failure when it came to printing: the
 * print machinery scaled the composite bitmap.  We finally decided to abandon
 * compositing, in the interest of keeping our screen drawing code the same as
 * our printing code.  Should check this again under version 2.0.
 */
- drawSymbols:sender :(BOOL)xaxislog :(BOOL)yaxislog
/*
 * This is coded so that when drawSymbols is called with plotParam as argument,
 * it draws the data curves; when drawSymbols is called with self (plotView) as
 * argument, it draws the symbols in the legend box.
 */
{
/* Our current code in drawSymbols could perhaps be sped up by using wraps
 * or DPSuserpath(). See if it's too slow as it stands before trying to
 * speed it up.
 */
  int i, j, jrun, n;
  float size = 4.0;
  NXCoord *x;
  NXCoord **y;
  int npoints, ncurves;
  int curveindex = 0;
  int symbolstyle;
  register float xtmp, ytmp;
  const char * curvetitle;

  size = [symbolSizeText floatValue];

  PSgsave();
  for (n=0; n<[sender nFiles]; n++) { /* loop over all active files */
    x = [sender xdata:n];
    y = [sender ydata:n];
    ncurves = [sender nCurves:n];
    npoints = [sender nPoints:n];
    PSnewpath();
    PSsetlinewidth(NXDrawingStatus==NX_DRAWING? 0.0 :
                 [accPrintColorButton state]==0? LINE_WIDTH_IF_PRINTING_BW :
		                                 LINE_WIDTH_IF_PRINTING_COLOR);
    for (jrun=curveindex; jrun<curveindex+ncurves; jrun++) {
      symbolstyle = [plotParam providesymbolstyle:jrun];
      NXSetColor([plotParam provideCurveColor:jrun]);
      /* If we're drawing the legend and there is no title, don't
       * bother to draw the symbol:
       */
      curvetitle = [legendForm stringValueAt:jrun];
      if (drawingLegendSymbols && curvetitle[0]=='\0') continue;
      j = jrun - curveindex;	/* for indexing into the y array */
      switch(symbolstyle) {
      case NOSYMBOL:
	continue;
	break;
      case CIRCLE:
	for (i=0; i<npoints; i++) {
	  /*	  circle_at(x[i], *(*(y+j)+i));      */
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  PSarc(xtmp * ppxunit, ytmp * ppyunit, size, 0.0, 360.0);
	  PSfill();
	}
	break;
      case XMARK:
	for (i=0; i<npoints; i++) {
	  /*	  x_at(x[i], *(*(y+j)+i));           */
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  PSmoveto(xtmp * ppxunit  - size, ytmp * ppyunit  - size);
	  PSrlineto(2.0*size, 2.0*size);
	  PSmoveto(xtmp * ppxunit  - size, ytmp * ppyunit  + size);
	  PSrlineto(2.0*size, -2.0*size);
	  PSstroke();
	}
	break;
      case UPTRIANGLE:
	for (i=0; i<npoints; i++) {
	  /*	  uptriangle_at(x[i], *(*(y+j)+i));    */
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  PSmoveto(xtmp * ppxunit  - size, ytmp * ppyunit  - size/SQRT3);
	  PSrlineto(2.0*size, 0.0);
	  PSrlineto(-size, size*SQRT3);
	  PSclosepath();
	  PSfill();
	}
	break;
      case DOWNTRIANGLE:
	for (i=0; i<npoints; i++) {
	  /*	  downtriangle_at(x[i], *(*(y+j)+i));     */
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  PSmoveto(xtmp * ppxunit  - size, ytmp * ppyunit  + size/SQRT3);
	  PSrlineto(2.0*size, 0.0);
	  PSrlineto(-size, -size*SQRT3);
	  PSclosepath();
	  PSfill();
	}
	break;
      case DIAMOND:
	for (i=0; i<npoints; i++) {
	  /*	  diamond_at(x[i], *(*(y+j)+i));          */
	  /* 3 pixels horizontal, 4 pixels vertical */
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  PSmoveto(xtmp * ppxunit, ytmp * ppyunit  - size);
	  PSrlineto(3.0/4.0*size, size);	
	  PSrlineto(-3.0/4.0*size, size); /* gives a pleasing diamond shape */
	  PSrlineto(-3.0/4.0*size, -size);
	  PSclosepath();
	  PSfill();
	}
	break;
      case SQUARE:
	for (i=0; i<npoints; i++) {
	  /*	  square_at(x[i], *(*(y+j)+i));            */
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  PSrectfill(xtmp * ppxunit  - size, ytmp * ppyunit  - size,
		     2.0*size, 2.0*size);
	}
	break;
      case PLUS:
	for (i=0; i<npoints; i++) {
	  /*	  plus_at(x[i], *(*(y+j)+i));              */
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  PSmoveto(xtmp * ppxunit, ytmp * ppyunit  - size);
	  PSrlineto(0.0, 2.0*size);
	  PSmoveto(xtmp * ppxunit  - size, ytmp * ppyunit);
	  PSrlineto(2.0*size, 0.0);
	  PSstroke();
	}
	break;
      }
    }
    curveindex += ncurves;
  }
  PSgrestore();

  return self;
}

- drawErrorBars :(BOOL)xaxislog :(BOOL)yaxislog
{
  int     n, j, npoints, ncurves, jrun, i;
  NXCoord *x, *ex = NULL;	/* ex and ey initialized to avoid */
  NXCoord **y, **ey = NULL;	/* compiler warning               */
  int     curveindex = 0;
  register float xtmp, ytmp, tmp1, tmp2;
  float   width = [errorBarBaseWidth floatValue];
  BOOL    exbars, eybars;

  PSgsave();
  for (n=0; n<[plotParam nFiles]; n++) { /* loop over all active files */
    exbars = [plotParam has_exbars:n];
    eybars = [plotParam has_eybars:n];
    ncurves = [plotParam nCurves:n];
    if (!exbars  && !eybars ) {       /* skip if no error bars */
      curveindex += ncurves;	      /* but have to bump this index, */
      continue;			      /* to get the colors correct    */
    }
    x = [plotParam xdata:n];	/* we have to draw error bars */
    if (exbars) {
      ex = [plotParam exdata:n];
    }
    y = [plotParam ydata:n];
    if (eybars) {
      ey = [plotParam eydata:n];
    }
    npoints = [plotParam nPoints:n];
    PSnewpath();
    PSsetlinewidth(NXDrawingStatus==NX_DRAWING? 0.0 :
		 [accPrintColorButton state]==0? LINE_WIDTH_IF_PRINTING_BW :
		                                 LINE_WIDTH_IF_PRINTING_COLOR);
    for (jrun=curveindex; jrun<curveindex+ncurves; jrun++) {
      NXSetColor([plotParam provideCurveColor:jrun]);
      j = jrun - curveindex;	/* for indexing into the y and ey arrays */
      /* check if error bars are desired */
      if (exbars &&  [[errorBarMatrix cellAt :n :0] state] == 1) {
	for (i=0; i<npoints; i++) {
	  ytmp = (yaxislog? (float)log10((double)*(*(y+j)+i)) : *(*(y+j)+i));
	  if (xaxislog) {
	    if (x[i] - ex[i] <= 0.0) { /* avoid log of negative */
	      tmp1 = (float)log10( (double) x[i] ); /* what to do? */
	    }
	    else {
	      tmp1 = (float)log10( (double) (x[i] - ex[i]) );
	    }
	    if (x[i] + ex[i] <= 0.0) { /* again avoid log of negative */
	      tmp2 = (float)log10( (double) x[i] );
	    }
	    else {
	      tmp2 = (float)log10( (double) (x[i] + ex[i]) );
	    }
	  }
	  else {
	    tmp1 = x[i] - ex[i];
	    tmp2 = x[i] + ex[i];
	  }
	  PSmoveto(tmp1 * ppxunit, ytmp * ppyunit);
	  PSrlineto((tmp2-tmp1)*ppxunit, 0.0);
	  if (width > 0.0) {
	    PSmoveto(tmp1 * ppxunit, ytmp*ppyunit - width/2.0);
	    PSrlineto(0.0, width);
	    PSmoveto(tmp2 * ppxunit, ytmp*ppyunit - width/2.0);
	    PSrlineto(0.0, width);
	  }
	  PSstroke();
	}
      }
      if (eybars && [[errorBarMatrix cellAt :n :j+1] state] == 1) {
	for (i=0; i<npoints; i++) {
	  xtmp = (xaxislog? (float)log10((double)x[i]) : x[i]);
	  if (yaxislog) {
	    if ((*(*(y+j)+i) - *(*(ey+j)+i)) <= 0.0) { /* avoid log of negative */
	      tmp1 = (float)log10( (double) (*(*(y+j)+i)) );
	    }
	    else {
	      tmp1 = (float)log10( (double) (*(*(y+j)+i) - *(*(ey+j)+i)) );
	    }
	    if ((*(*(y+j)+i) + *(*(ey+j)+i)) <= 0.0) { /* avoid log of negative */
	      tmp2 = (float)log10( (double) (*(*(y+j)+i)) );
	    }
	    else {
	      tmp2 = (float)log10( (double) (*(*(y+j)+i) + *(*(ey+j)+i)) );
	    }
	  }
	  else {
	    tmp1 = *(*(y+j)+i) - *(*(ey+j)+i);
	    tmp2 = *(*(y+j)+i) + *(*(ey+j)+i);
	  }
	  PSmoveto(xtmp * ppxunit, tmp1 * ppyunit);
	  PSrlineto(0.0, (tmp2-tmp1)*ppyunit);
	  if (width > 0.0) {
	    PSmoveto(xtmp*ppxunit - width/2.0, tmp1 * ppyunit);
	    PSrlineto(width, 0.0);
	    PSmoveto(xtmp*ppxunit - width/2.0, tmp2 * ppyunit);
	    PSrlineto(width, 0.0);
	  }
	  PSstroke();
	}
      }
    }
    curveindex += ncurves;
  }
  PSgrestore();

  return self;
}


- startLegend
{
  id legendtextfont,legendTitleFont;
  const char * curvetitle;
  const char * legendtitle = [legendTitle stringValueAt:0];
  int j;
  int ncurves = [plotParam nCurvesTotal];
  float maxcurvetitlewid = 0;
  float yhgt;

  if ([plotParam shouldChangeLegendFont]) {
    newLegendFont = [theFontManager convertFont:[theFontManager selFont]];
  }

  if (newLegendFont) {
    legendtextfont = [Font newFont:[newLegendFont name] 
		           size:[newLegendFont pointSize]
		           style:[newLegendFont style]
		           matrix:NX_IDENTITYMATRIX];
    yhgt = [newLegendFont pointSize];	/* height of text */
  }
  else {
    legendtextfont =
      [Font newFont:"Helvetica" size:DEFAULTFONTSIZE
            style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = DEFAULTFONTSIZE;
  }

  [legendtextfont set];

  legendbox.size.height = 10.0;

  for (j=0; j<ncurves; j++) {
    /* check for no lines and no symbols */
    if( ([plotParam providelinestyle:j] == NOLINE)  &&
       ( [plotParam providesymbolstyle:j] == NOSYMBOL) )continue;
    curvetitle = [legendForm stringValueAt:j];
    /* skip this curve if there is no title: */
    if ([legendtextfont getWidthOf:curvetitle] == 0.0) continue;
    maxcurvetitlewid = MAX(maxcurvetitlewid,
			   [legendtextfont getWidthOf:curvetitle]);
    legendbox.size.height += yhgt; /* increment by yhgt for every curve */
  }

  /* check on legend title width also */

  if ([plotParam shouldChangeLegendTitleFont]) {
    newLegendTitleFont = [theFontManager convertFont:[theFontManager selFont]];
  }

  if (newLegendTitleFont) {
    legendTitleFont = [Font newFont:[newLegendTitleFont name] 
		            size:[newLegendTitleFont pointSize]
		            style:[newLegendTitleFont style]
		            matrix:NX_IDENTITYMATRIX];
    yhgt = [newLegendTitleFont pointSize];	/* height of text */
  }
  else {
    legendTitleFont =
      [Font newFont:"Helvetica" size:DEFAULTFONTSIZE
            style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = DEFAULTFONTSIZE;
  }

  [legendTitleFont set];
  maxcurvetitlewid = MAX(maxcurvetitlewid,
			   [legendTitleFont getWidthOf:legendtitle]);

  legendbox.size.width = 5.0 + 40.0 + 5.0 + maxcurvetitlewid + 5.0;
  /* legendboxwidth = L. margin + 40 + space + curve title + R. margin */
  if([legendTitleFont getWidthOf:legendtitle] != 0)
    legendbox.size.height = legendbox.size.height + 2.0*yhgt - 4.0;
  return self;
}

- drawLegend:sender
{
  id legendtextfont,legendTitleFont;
  const char * curvetitle;
  const char * legendtitle = [legendTitle stringValueAt:0];
  int j;
  int ncurves = [plotParam nCurvesTotal];
  float yhgt;

  if ([plotParam shouldChangeLegendFont]) {
    newLegendFont = [theFontManager convertFont:[theFontManager selFont]];
  }

  /* get legend text font initialized */
  if (newLegendFont) {
    legendtextfont = [Font newFont:[newLegendFont name] 
		           size:[newLegendFont pointSize]
		           style:[newLegendFont style]
		           matrix:NX_IDENTITYMATRIX];
    yhgt = [newLegendFont pointSize];
  }
  else {
    legendtextfont =
      [Font newFont:"Helvetica" size:DEFAULTFONTSIZE
            style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = DEFAULTFONTSIZE;
  }

  [legendtextfont set];

  drawingLegendLines = YES;
  [self xdata:0];
  [self ydata:0];

  NXSetColor([plotParam provideBackgroundColor]);

  if ([legendOpaque state] == 0) {
    NXRectFill(&legendbox);
  }

  [sender drawLines:sender :NO :NO];	/* legend lines, linear axes always */

  NXSetColor([plotParam provideTextColor]);

  for (j=0; j<ncurves; j++) {
    /* check for no lines and no symbols */
    if ( ([plotParam providelinestyle:j] == NOLINE)  &&
	([plotParam providesymbolstyle:j] == NOSYMBOL) ) continue;
    curvetitle = [legendForm stringValueAt:j];
    /* skip this curve if there is no title: */
    if ([legendtextfont getWidthOf:curvetitle] == 0.0) continue;
    PSmoveto(xlegend[1]*ppxunit + 5.0, 
	     *(*(ylegend+j)+0) * ppyunit - 0.33*yhgt);
    PSshow((char *)curvetitle);
  }

  if ([plotParam shouldChangeLegendTitleFont]) {
    newLegendTitleFont = [theFontManager convertFont:[theFontManager selFont]];
  }

  if (newLegendTitleFont) {
    legendTitleFont = [Font newFont:[newLegendTitleFont name] 
		            size:[newLegendTitleFont pointSize]
		            style:[newLegendTitleFont style]
		            matrix:NX_IDENTITYMATRIX];
    yhgt = [newLegendTitleFont pointSize];	/* height of text */
  }
  else {
    legendTitleFont =
      [Font newFont:"Helvetica" size:DEFAULTFONTSIZE
            style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = DEFAULTFONTSIZE;
  }

  [legendTitleFont set];
  PSmoveto(xlegend[0]*ppxunit - 5.0 + legendbox.size.width/2.0
	   - 0.5*[legendTitleFont getWidthOf:legendtitle],
	   *(*(ylegend+0)+0) * ppyunit + yhgt);
  PSshow((char *)legendtitle);
  if([legendBoxOnOff state]) {
    PSsetlinewidth(NXDrawingStatus==NX_DRAWING? 0.0 :
		 [accPrintColorButton state]==0? LINE_WIDTH_IF_PRINTING_BW :
		                                 LINE_WIDTH_IF_PRINTING_COLOR);
    PSrectstroke(legendbox.origin.x, legendbox.origin.y,
		 legendbox.size.width, legendbox.size.height);
  }
  drawingLegendLines = NO;
  drawingLegendSymbols = YES;
  [sender drawSymbols:sender :NO :NO];	/* legend symbols, linear axes always */
  drawingLegendSymbols = NO;

  return self;
}

- clear:sender
{
  /*
   * Derek Lisoski (dlisoski@cco.caltech.edu) suggests not drawing the
   * opaque background rectangle when you print or save a file.  Then
   * when you read the saved plots into a separate drawing program you
   * can overlay multiple plots or get the plots arbitrarily close to
   * each other.  There may be some problems with background colors
   * when importing into various applications (Create or Draw, e.g.)
   */
  if (NXDrawingStatus == NX_DRAWING || [opaqueBackgroundButton state] == 0) {
    NXRectFill(&bounds);
  /* for color; had NXEraseRect, but that always fills with white    */
  }
  return self;
}

/*
 * This routine assumes it is called with xmin != xmax and ymin != ymax.
 * Bad things may happen if this is not the case.
 * The input parameters are assumed to be in pixel coordinates.
 */
- drawTicMarks:(float)xmin :(float)xmax :(float)ymin :(float)ymax
{
  float pattern0[] = {};	/* solid      */
  float pattern2[] = {1.0, 3.0}; /* dot        */
  double xinc_unscaled = [plotParam provideXinc];
  double yinc_unscaled = [plotParam provideYinc];
  double xmin_unscaled = [plotParam provideXmin];
  double ymin_unscaled = [plotParam provideYmin];
  double xmax_unscaled = [plotParam provideXmax];
  double ymax_unscaled = [plotParam provideYmax];
  /* It is useful in some fairly extreme cases to have the increments
   * not in pixel coordinates (otherwise can get "bad" labels).
   */
  char ticlabel[32];
  id ticfont, ticfont1;
  float x, y, ticloc_rat, xticloc, yticloc;
  BOOL drawGrid = [gridOnOff state];
  BOOL xaxislog = [plotParam xaxisLog];
  BOOL yaxislog = [plotParam yaxisLog];
  BOOL drawMinorTics = [minorTicMarksOnOff state];
  int  ticLocation;		/* 0=axes, 1=2 sides, 2=4 sides */
  float  ticmarklen = [ticMarkLengthText floatValue];
  int    nmin, ninc, nmax, j, i;
  float  ticloc, xwid, yhgt, yhgt1;
  double first;
  int    nlabels;
  int    axformat[3];


  if (strncmp([ticMarkLocation title], "Axes", 4) == 0)
    ticLocation = 0;
  else if (strncmp([ticMarkLocation title], "Frame (2 sides)", 15) == 0)
    ticLocation = 1;
  else
    ticLocation = 2;

  if ([plotParam shouldChangeTicLabelFont]) {
    newTicLabelFont = [theFontManager convertFont:[theFontManager selFont]];
  }

  if (newTicLabelFont) {
    ticfont = [Font newFont:[newTicLabelFont name] 
	            size:[newTicLabelFont pointSize]
	            style:[newTicLabelFont style]
	            matrix:NX_IDENTITYMATRIX];
    yhgt = [newTicLabelFont pointSize];

    yhgt1 = (yhgt >= 10.0? yhgt - 2.0 : 8.0);
    ticfont1 = [Font newFont:[newTicLabelFont name] 
	             size:yhgt1
	             style:[newTicLabelFont style]
	             matrix:NX_IDENTITYMATRIX];
  }
  else {
    ticfont = [Font newFont:"Helvetica" size:DEFAULTFONTSIZE
	            style:0 matrix:NX_IDENTITYMATRIX];
    ticfont1 =
      [Font newFont:"Helvetica" size:10.0 style:0 matrix:NX_IDENTITYMATRIX];
    yhgt = DEFAULTFONTSIZE;
  }

  /* get tic font initialized */
  [ticfont set];

  PSnewpath();
  PSsetlinewidth([ticMarkThicknessText floatValue]);
  if (xaxislog) {		/* x-axis is logarithmic */
    nmin = (int)floor((double)(xmin/ppxunit));
    nmax = (int)ceil((double)(xmax/ppxunit));
    ninc = (int)rint(log10(xinc_unscaled));
    if (ninc == 0) ninc = 1;	/* avoid infinite loop */
    if ([handFormatXaxis state] == 1) {
      axformat[0] = [xFormatLeft intValue];
      axformat[1] = [xFormatRight intValue];
      axformat[2] = [xFormatExponent intValue];
    }
    for (j=nmin; j<=nmax; j+=ninc) {
      if ( (float)j * ppxunit >= xmin &&  (float)j * ppxunit <= xmax ) {
	if (drawGrid) {
	  PSsetlinewidth([gridThicknessText floatValue]);
	  PSmoveto((float)j * ppxunit, ymin);
	  if ([gridDotted state])
	    PSsetdash(pattern2, 2, 0.0);
	  else
	    PSsetdash(pattern0, 0, 0.0);
	  PSrlineto(0.0, ABS(ymax-ymin));
	  PSstroke();
	  PSsetlinewidth([ticMarkThicknessText floatValue]);
	}
	PSmoveto((float)j * ppxunit, ymin - ticmarklen*6.0); /* big tic mark */
	PSsetdash(pattern0, 0, 0.0);
	PSrlineto(0.0, ticmarklen*6.0);
	PSstroke();
	if (ticLocation == 2) {	/* tics on right and top */
	  PSmoveto((float)j * ppxunit, ymax); /* big tic mark */
	  PSsetdash(pattern0, 0, 0.0);
	  PSrlineto(0.0, ticmarklen*6.0);
	  PSstroke();
	}
	if ([handFormatXaxis state] == 0) {
	  ticloc_rat = yhgt/DEFAULTFONTSIZE;
	  PSmoveto((float)j * ppxunit - ticloc_rat*8.0,
		   ymin - MAX(24.0*ticloc_rat,
			      24.0*ticloc_rat*(ticmarklen+10.0)/10.0));
	  PSshow("10");
	  [ticfont1 set];
	  PSmoveto((float)j * ppxunit + ticloc_rat*4.0,
		   ymin - MAX(16.0*ticloc_rat,
			       8.0*ticloc_rat*(2.0 + 0.3*ticmarklen)));
	  sprintf(ticlabel, "%-5d", j);
	  PSshow(ticlabel);
	  [ticfont set];
	}
	else {
	  x = (float) pow((double)10.0, (double)j);
	  handformat(x, ticlabel, axformat);
	  xwid = [ticfont getWidthOf:ticlabel];
	  PSmoveto((float)j * ppxunit - xwid/2.0,
		    ymin - yhgt - MAX(5.0, 5.0*ticmarklen));
	  PSshow(ticlabel);
	}
      }
      if (drawMinorTics) {
	for (i=2; i<=9; i++) {
	  ticloc = (float)j * ppxunit + ppxunit*(float)log10((double)i);
	  if ( ticloc>xmin && ticloc<xmax ) {
	    PSmoveto(ticloc, ymin - ticmarklen*3.0); /* small tic mark */
	    PSrlineto(0.0, ticmarklen*3.0);
	    PSstroke();
	    if (ticLocation == 2) { /* tics on right and top */
	      PSmoveto(ticloc, ymax); /* small tic mark */
	      PSrlineto(0.0, ticmarklen*3.0);
	      PSstroke();
	    }
	  }
	}
      }
    }
  }
  else {			/* x-axis is linear */
    yticloc = (ticLocation > 0 ? ymin : 2.0*ticmarklen) ;
    /* If inc is big, skip tic marks entirely */
    if (fabs(xinc_unscaled) < fabs(xmax_unscaled - xmin_unscaled)) {
      count_labels(&nlabels, &first, xmin_unscaled, xinc_unscaled, xmax_unscaled);
      if ([handFormatXaxis state] == 1) {
	axformat[0] = [xFormatLeft intValue];
	axformat[1] = [xFormatRight intValue];
	axformat[2] = [xFormatExponent intValue];
      }
      else {
	autoformat(xmin_unscaled, xinc_unscaled, xmax_unscaled, axformat);
	[xFormatLeft setIntValue:axformat[0]];
	[xFormatRight setIntValue:axformat[1]];
	[xFormatExponent setIntValue:axformat[2]];
      }
      /*
       * next loop starts at -1 because there may be room for minor tic
       * marks to the left of the first major tic mark (after a zoom, e.g.)
       */
      for (i = -1; i < nlabels; i++) {
	/* Special test here for what should be exact 0 (but isn't sometimes
	 * due to floating-point arithmetic.
	 */
	if (fabs(first/xinc_unscaled + (float)i) < 4.0e-7) { /* ugly */
	  x = 0.0;
	}
	else {
	  x = (first + (xinc_unscaled)*(float)i) * ppxunit;
	}
	if (x >= xmin) {	/* ensure major tic mark won't be off edge */
	  if (drawGrid) {
	    PSsetlinewidth([gridThicknessText floatValue]);
	    PSmoveto(x, ymin);
	    if ([gridDotted state])
	      PSsetdash(pattern2, 2, 0.0);
	    else
	      PSsetdash(pattern0, 0, 0.0);
	    PSrlineto(0.0, ABS(ymax-ymin));
	    PSstroke();
	    PSsetlinewidth([ticMarkThicknessText floatValue]);
	  }
	  /* Nothing at 0 if we're putting tic marks on axes:  */
	  if (ticLocation > 0 || x != 0.0) {
	    PSmoveto(x, yticloc - ticmarklen*4.0);
	    PSsetdash(pattern0, 0, 0.0);
	    PSrlineto(0.0, ticmarklen*4.0);
	    PSstroke();
            if (ticLocation == 2) { /* tics on right and top */
	      PSmoveto(x, ymax);
	      PSsetdash(pattern0, 0, 0.0);
	      PSrlineto(0.0, ticmarklen*4.0);
	      PSstroke();
            }
	    handformat(x/ppxunit, ticlabel, axformat);
	    xwid = [ticfont getWidthOf:ticlabel];
	    PSmoveto(x - xwid/2.0, yticloc - yhgt - MAX(5.0, 5.0*ticmarklen));
	    PSshow(ticlabel);
	  }
	}
	if (drawMinorTics) {
          if (ticLocation > 0) { /* tic marks on frame */
	    for (j=1; j<=9; j++) {
	      ticloc = x + ((xinc_unscaled/10.0)*(float)j)*ppxunit;
	      if (ticloc>xmin && ticloc<xmax) {
	        PSmoveto(ticloc, yticloc - ticmarklen*2.0);
	        PSrlineto(0.0, ticmarklen*2.0);
	        PSstroke();
                if (ticLocation == 2) {	/* tics on right and top */
  	          PSmoveto(ticloc, ymax);
	          PSrlineto(0.0, ticmarklen*2.0);
	          PSstroke();
                }
              }
            }
          }
          else {
	    for (j=1; j<=9; j++) {
	      ticloc = x + ((xinc_unscaled/10.0)*(float)j)*ppxunit;
	      if (ticloc>xmin && ticloc<xmax) {
	        PSmoveto(ticloc, -ticmarklen);
	        PSrlineto(0.0, 2.0*ticmarklen);
	        PSstroke();
              }
            }
          }
	}
      }
    }
  }
  if (yaxislog) {		/* y-axis is logarithmic */
    nmin = (int)floor((double)(ymin/ppyunit));
    nmax = (int)ceil((double)(ymax/ppyunit));
    ninc = (int)rint(log10(yinc_unscaled));
    if (ninc == 0) ninc = 1;	/* avoid infinite loop */
    if ([handFormatYaxis state] == 1) {
      axformat[0] = [yFormatLeft intValue];
      axformat[1] = [yFormatRight intValue];
      axformat[2] = [yFormatExponent intValue];
    }
    for (j=nmin; j<=nmax; j+=ninc) {
      if ( (float)j * ppyunit >= ymin &&  (float)j * ppyunit <= ymax ) {
	if (drawGrid) {
	  PSsetlinewidth([gridThicknessText floatValue]);
	  PSmoveto(xmin, (float)j * ppyunit);
	  if ([gridDotted state])
	    PSsetdash(pattern2, 2, 0.0);
	  else
	    PSsetdash(pattern0, 0, 0.0);
	  PSrlineto(ABS(xmax-xmin), 0.0);
	  PSstroke();
	  PSsetlinewidth([ticMarkThicknessText floatValue]);
	}
	PSmoveto(xmin - ticmarklen*6.0, (float)j * ppyunit); /* big tic mark */
	PSsetdash(pattern0, 0, 0.0);
	PSrlineto(ticmarklen*6.0, 0.0);
	PSstroke();
        if (ticLocation == 2) {	/* tics on right and top */
	  PSmoveto(xmax, (float)j * ppyunit); /* big tic mark */
	  PSsetdash(pattern0, 0, 0.0);
	  PSrlineto(ticmarklen*6.0, 0.0);
	  PSstroke();
        }
	if ([handFormatYaxis state] == 0) {
	  ticloc_rat = yhgt/DEFAULTFONTSIZE;
	  PSmoveto(xmin - MAX(40.0*ticloc_rat,
			      40.0*ticloc_rat*(ticmarklen+10.0)/10.0),
		   (float)j * ppyunit - ticloc_rat*7.0);
	  PSshow("10");
	  [ticfont1 set];
	  PSmoveto(xmin - MAX(24.0*ticloc_rat,
			       4.0*ticloc_rat*(ticmarklen + 6.0)),
		   (float)j * ppyunit - ticloc_rat*1.0);
	  sprintf(ticlabel, "%-5d", j);
	  PSshow(ticlabel);
	  [ticfont set];
	}
	else {
	  y = (float) pow((double)10.0, (double)j);
	  handformat(y, ticlabel, axformat);
	  xwid = [ticfont getWidthOf:ticlabel];
	  PSmoveto(xmin - xwid - MAX(10.0, 5.0*ticmarklen),
		       (float)j * ppyunit + 2.0 - yhgt/2.0);
	  PSshow(ticlabel);
	}
      }
      if (drawMinorTics) {
	for (i=2; i<=9; i++) {
	  ticloc = (float)j * ppyunit + ppyunit*(float)log10((double)i);
	  if (ticloc>ymin && ticloc<ymax) {
	    PSmoveto(xmin - ticmarklen*3.0, ticloc); /* small tic mark */
	    PSrlineto(ticmarklen*3.0, 0.0);
	    PSstroke();
            if (ticLocation == 2) { /* tics on right and top */
	      PSmoveto(xmax, ticloc); /* small tic mark */
	      PSrlineto(ticmarklen*3.0, 0.0);
	      PSstroke();
            }
	  }
	}
      }
    }
  }
  else {			/* y-axis is linear */
    xticloc = (ticLocation > 0 ? xmin : 2.0*ticmarklen) ;
    /* If inc is big, skip tic marks entirely */
    if (fabs(yinc_unscaled) < fabs(ymax_unscaled - ymin_unscaled)) {
      count_labels(&nlabels, &first, ymin_unscaled, yinc_unscaled, ymax_unscaled);
      if ([handFormatYaxis state] == 1) {
	axformat[0] = [yFormatLeft intValue];
	axformat[1] = [yFormatRight intValue];
	axformat[2] = [yFormatExponent intValue];
      }
      else {
	autoformat(ymin_unscaled, yinc_unscaled, ymax_unscaled, axformat);
	[yFormatLeft setIntValue:axformat[0]];
	[yFormatRight setIntValue:axformat[1]];
	[yFormatExponent setIntValue:axformat[2]];
      }
      /*
       * next loop starts at -1 because there may be room for minor tic
       * marks to the left of the first major tic mark (after a zoom, e.g.)
       */
      for (i = -1; i < nlabels; i++) {
	/* Special test here for what should be exact 0 (but isn't sometimes
	 * due to floating-point arithmetic.
	 */
	if (fabs(first/yinc_unscaled + (float)i) < 4.0e-7) { /* ugly */
	  y = 0.0;
	}
	else {
	  y = (first + (yinc_unscaled)*(float)i) * ppyunit;
	}
	if (y >= ymin) {	/* ensure major tic mark won't be off edge */
	  if (drawGrid) {
	    PSsetlinewidth([gridThicknessText floatValue]);
	    PSmoveto(xmin, y);
	    if ([gridDotted state])
	      PSsetdash(pattern2, 2, 0.0);
	    else
	      PSsetdash(pattern0, 0, 0.0);
	    PSrlineto(ABS(xmax-xmin), 0.0);
	    PSstroke();
	    PSsetlinewidth([ticMarkThicknessText floatValue]);
	  }
	  /* Nothing at 0 if we're putting tic marks on axes:  */
	  if (ticLocation > 0 || y != 0.0) {
	    PSmoveto(xticloc - ticmarklen*4.0, y);
	    PSsetdash(pattern0, 0, 0.0);
	    PSrlineto(ticmarklen*4.0, 0.0);
	    PSstroke();
            if (ticLocation == 2) { /* tics on right and top */
              PSmoveto(xmax, y);
	      PSsetdash(pattern0, 0, 0.0);
	      PSrlineto(ticmarklen*4.0, 0.0);
	      PSstroke();
            }
	    handformat(y/ppyunit, ticlabel, axformat);
	    xwid = [ticfont getWidthOf:ticlabel];
	    PSmoveto(xticloc - xwid - MAX(10.0, 5.0*ticmarklen),
		     y + 2.0 - yhgt/2.0);
	    PSshow(ticlabel);
	  }
	}
	if (drawMinorTics) {
          if (ticLocation > 0) { /* tics on frame */
	    for (j=1; j<=9; j++) {
	      ticloc = y + ((yinc_unscaled/10.0)*(float)j)*ppyunit;
	      if (ticloc>ymin && ticloc<ymax) {
	        PSmoveto(xticloc - ticmarklen*2.0, ticloc);
	        PSrlineto(ticmarklen*2.0, 0.0);
	        PSstroke();
                if (ticLocation == 2) {	/* tics on right and top */
	          PSmoveto(xmax, ticloc);
	          PSrlineto(ticmarklen*2.0, 0.0);
	          PSstroke();
                }
              }
	    }
	  }
          else {
            for (j=1; j<=9; j++) {
	      ticloc = y + ((yinc_unscaled/10.0)*(float)j)*ppyunit;
	      if (ticloc>ymin && ticloc<ymax) {
	        PSmoveto(-ticmarklen, ticloc);
	        PSrlineto(2.0*ticmarklen, 0.0);
	        PSstroke();
              }
	    }
	  }
	}
      }
    }
  }
  return self;
}

- drawSelf: (const NXRect *)rects :(int)rectCount
{
  float  xmin = (float)[plotParam provideXmin];
  float  xmax = (float)[plotParam provideXmax];
  float  ymin = (float)[plotParam provideYmin];
  float  ymax = (float)[plotParam provideYmax];

  [self startPlot];

  if ([plotParam nFiles] == 0) return self; /* no data */

  if (xmin==xmax || ymin==ymax) return self; /* avoid division by zero */

  PSgsave();

  PStranslate(XOFFSET, YOFFSET);

  if ([plotParam xaxisLog]) {	/* x-axis is logarithmic */
    ppxunit = 0.9*(bounds.size.width-XOFFSET)
      /(float)log10((double)(xmax/xmin));
    xmin = (float)log10((double)xmin) * ppxunit;
    xmax = (float)log10((double)xmax) * ppxunit;
  }
  else {			/* x-axis is linear */
    ppxunit = 0.9*(bounds.size.width-XOFFSET)/(xmax-xmin);
    xmin = xmin*ppxunit;	/* drawing is all in pixel coordinates */
    xmax = xmax*ppxunit;
  }
  if ([plotParam yaxisLog]) {	/* y-axis is logarithmic */
    ppyunit = 0.9*(bounds.size.height-YOFFSET)
      /(float)log10((double)(ymax/ymin));
    ymin = (float)log10((double)ymin) * ppyunit;
    ymax = (float)log10((double)ymax) * ppyunit;
  }
  else {			/* y-axis is linear */
    ppyunit = 0.9*(bounds.size.height-YOFFSET)/(ymax-ymin);
    ymin = ymin*ppyunit;
    ymax = ymax*ppyunit;
  }
  PStranslate(-xmin, -ymin);
  // inner "frame" box
  if ([frameBoxOnOff state]) {
    PSsetlinewidth([frameBoxThicknessText floatValue]);
    PSnewpath();
    PSmoveto(xmin, ymin);
    PSlineto(xmax, ymin);
    PSlineto(xmax, ymax);
    PSlineto(xmin, ymax);
    PSclosepath();
    PSstroke();
 /* reset line width */
    PSsetlinewidth(NXDrawingStatus==NX_DRAWING? 0.0 :
		 [accPrintColorButton state]==0? LINE_WIDTH_IF_PRINTING_BW :
		                                 LINE_WIDTH_IF_PRINTING_COLOR);
  }

  if ([axesOnOff state]) {
    PSnewpath();
    PSsetlinewidth([axisThicknessText floatValue]);
    PSmoveto(0.0, ymin);
    PSlineto(0.0, ymax);
    PSstroke();
    PSmoveto(xmin, 0.0);
    PSlineto(xmax, 0.0);
    PSstroke();
  }

  if ([majorTicMarksOnOff state])
    [self drawTicMarks:xmin :xmax :ymin :ymax]; /* do this before clipping */

  /* MIN and MAX below in case xmin > xmax  and/or  ymin > ymax. */
  PSrectclip(MIN(xmin,xmax), MIN(ymin,ymax), ABS(xmax-xmin), ABS(ymax-ymin));

  [self drawLines:plotParam :[plotParam xaxisLog] :[plotParam yaxisLog]];
  [self drawSymbols:plotParam :[plotParam xaxisLog] :[plotParam yaxisLog]];

  [self drawErrorBars :[plotParam xaxisLog] :[plotParam yaxisLog]];

  PSgrestore();
  if ([legendOnOff state]) {
    [self startLegend];
    [self drawLegend:self];
  }

  return self;
}

- doPrinting:sender
{

  id myPrintPanel = [PrintPanel new];

  [ [NXApp printInfo] setMarginLeft:72.0 right:72.0 top:72.0 bottom:72.0 ];

  if (bounds.size.height > bounds.size.width) { /* portrait mode */
    if (bounds.size.height/bounds.size.width > 9.0/6.5) {
      [ [NXApp printInfo] setScalingFactor: 9.0*72.0/bounds.size.height];
    }
    else {
      [ [NXApp printInfo] setScalingFactor: 6.5*72.0/bounds.size.width];
    }
    [ [NXApp printInfo] setOrientation:NX_PORTRAIT andAdjust:YES ];
  }
  else {			/* landscape mode */
    if (bounds.size.width/bounds.size.height > 9.0/6.5) {
      [ [NXApp printInfo] setScalingFactor: 9.0*72.0/bounds.size.width];
    }
    else {
      [ [NXApp printInfo] setScalingFactor: 6.5*72.0/bounds.size.height];
    }
    [ [NXApp printInfo] setOrientation:NX_LANDSCAPE andAdjust:YES ];
  }

  [myPrintPanel setAccessoryView:printPanelAccessory];

  [self printPSCode:sender];
  return self;
}

- mouseDown:(NXEvent *)e
/*
 * This code taken from the Mandelbrot example in /NextDeveloper (and modified).
 * We implement the mouseDown method so the user can sweep out a section of
 * the view and select that as the current window in which to view the curve(s).
 */
{
  int looping = YES;
  int oldMask;
  NXRect bbox;
  NXPoint startPoint, currPoint;
  float xmin, xmax, ymin, ymax;
  register float t1, t2, t3, t4;
  int  zooming;
  BOOL xaxislog = [plotParam xaxisLog];
  BOOL yaxislog = [plotParam yaxisLog];
  
  zooming = ZOOM;
  if (strncmp([zoomChoice title], "Zoom/Move", 9) == 0)
    zooming = NOZOOM;
  else if (strncmp([zoomChoice title], "Move legend", 11) == 0)
    zooming = MOVELEGEND;
  else if (strncmp([zoomChoice title], "Move x title", 12) == 0)
    zooming = MOVEXTITLE;
  else if (strncmp([zoomChoice title], "Move y title", 12) == 0)
    zooming = MOVEYTITLE;
  else if (strncmp([zoomChoice title], "Move main title", 15) == 0)
    zooming = MOVEMAINTITLE;

  if (zooming == ZOOM) {	/* really zooming */
    if (xaxislog) {
      xmin = (float)log10([plotParam provideXmin]) * ppxunit;
      xmax = (float)log10([plotParam provideXmax]) * ppxunit;
    }
    else {
      xmin = [plotParam provideXmin] * ppxunit;
      xmax = [plotParam provideXmax] * ppxunit;
    }
    if (yaxislog) {
      ymin = (float)log10([plotParam provideYmin]) * ppyunit;
      ymax = (float)log10([plotParam provideYmax]) * ppyunit;
    }
    else {
      ymin = [plotParam provideYmin] * ppyunit;
      ymax = [plotParam provideYmax] * ppyunit;
    }
  
    oldMask =  [window addToEventMask:NX_MOUSEDRAGGEDMASK];
    startPoint = e->location;
    [self convertPoint:&startPoint fromView:nil];
    NXSetRect(&bbox,startPoint.x,startPoint.y,0.0,0.0);
    [self lockFocus];
    while (looping) {
      e=[NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
      currPoint = e->location;
      [self convertPoint:&currPoint fromView:nil];
      bbox.size.width = (currPoint.x - startPoint.x);
      bbox.size.height = (currPoint.y - startPoint.y);
      /* Normalize bbox to always have positive width and height */
      if (bbox.size.width < 0) {
	bbox.size.width = -bbox.size.width;
	bbox.origin.x   = startPoint.x - bbox.size.width;
      }
      if (bbox.size.height < 0) {
	bbox.size.height = -bbox.size.height;
	bbox.origin.y    = startPoint.y - bbox.size.height;
      }

      PSnewinstance();
      if (looping = (e->type == NX_MOUSEDRAGGED)) {
	PSsetinstance(YES);
	NXSetColor([plotParam provideTextColor]);
	NXFrameRect(&bbox);
	PSsetinstance(NO);
      }
    }
    [self unlockFocus];
    [window setEventMask:oldMask];
    if ((bbox.size.width > 0) && (bbox.size.height > 0)) {
      /* At this point, bbox is in window coordinates.  Convert to curve
       * coordinates based on this view's coordinates and the bounding box.
       */

      xmin = xmin + (bbox.origin.x - XOFFSET);
      xmax = xmin + bbox.size.width;
      ymin = ymin + (bbox.origin.y - YOFFSET);
      ymax = ymin + bbox.size.height;

      /* save old min/max -- must adjust if log axis */
      t1 = xmin/ppxunit;
      t2 = xmax/ppxunit;
      t3 = ymin/ppyunit;
      t4 = ymax/ppyunit;
      if (xaxislog) {
	t1 = (float) pow((double)10.0, (double)t1);
	t2 = (float) pow((double)10.0, (double)t2);
      }
      if (yaxislog) {
	t3 = (float) pow((double)10.0, (double)t3);
	t4 = (float) pow((double)10.0, (double)t4);
      }
      [plotParam stackOldMinMax:t1 :t2 :t3 :t4];

      if (xaxislog) {
	[plotParam resetXmin:pow((double)10.0, (double)(xmin/ppxunit))];
	[plotParam resetXmax:pow((double)10.0, (double)(xmax/ppxunit))];
      }
      else {
	[plotParam resetXmin:(double)(xmin/ppxunit)];
	[plotParam resetXmax:(double)(xmax/ppxunit)];
      }
      if (yaxislog) {
	[plotParam resetYmin:pow((double)10.0, (double)(ymin/ppyunit))];
	[plotParam resetYmax:pow((double)10.0, (double)(ymax/ppyunit))];
      }
      else {
	[plotParam resetYmin:(double)(ymin/ppyunit)];
	[plotParam resetYmax:(double)(ymax/ppyunit)];
      }

      /*  Call [self display] to force current values of xmin/xmax/ymin/ymax
       *  to take effect (otherwise xmin, etc., get incremented too many times).
       */
      [plotParam drawPlotButton:1];
      [self display];
      [plotParam drawPlotButton:0];
    }
  }
  else if (zooming >= MOVELEGEND) {

    oldMask =  [window addToEventMask:NX_MOUSEDRAGGEDMASK];
    startPoint = e->location;
    [self convertPoint:&startPoint fromView:nil];
    if (zooming == MOVELEGEND) {
      NXSetRect(&bbox,startPoint.x,startPoint.y,
		legendbox.size.width,legendbox.size.height);
    }
    else if (zooming == MOVEXTITLE) {
      NXSetRect(&bbox,startPoint.x,startPoint.y,
		xtitlebox.size.width,xtitlebox.size.height);
    }
    else if (zooming == MOVEYTITLE) {
      NXSetRect(&bbox,startPoint.x,startPoint.y,
		ytitlebox.size.width,ytitlebox.size.height);
    }
    else if (zooming == MOVEMAINTITLE) {
      NXSetRect(&bbox,startPoint.x,startPoint.y,
		maintitlebox.size.width,maintitlebox.size.height);
    }
    [self lockFocus];
    while (looping) {
      e=[NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
      currPoint = e->location;
      [self convertPoint:&currPoint fromView:nil];
      bbox.origin.x = currPoint.x;
      bbox.origin.y = currPoint.y;
      PSnewinstance();
      if (looping = (e->type == NX_MOUSEDRAGGED)) {
	PSsetinstance(YES);
	NXSetColor([plotParam provideTextColor]);
	NXFrameRect(&bbox);
	PSsetinstance(NO);
      }
    }
    [self unlockFocus];
    [window setEventMask:oldMask];
    /*
     * At this point, bbox is in window coordinates. 
     */
    if (zooming == MOVELEGEND) {
      legendbox.origin.x = bbox.origin.x;
      legendbox.origin.y = bbox.origin.y;
    }
    else if (zooming == MOVEXTITLE) {
      xtitlebox.origin.x = bbox.origin.x;
      xtitlebox.origin.y = bbox.origin.y;
    }
    else if (zooming == MOVEYTITLE) {
      ytitlebox.origin.x = bbox.origin.x;
      ytitlebox.origin.y = bbox.origin.y;
    }
    else if (zooming == MOVEMAINTITLE) {
      maintitlebox.origin.x = bbox.origin.x;
      maintitlebox.origin.y = bbox.origin.y;
    }

    /*  Call [self display] to give immediate feedback   */
    [plotParam drawPlotButton:1];
    [self display];
    [plotParam drawPlotButton:0];
  }

  return self;
}

- saveEPS:sender
{
  id savePanel = [[SavePanel new] setRequiredFileType:"eps"];
  char  *eps_fileName;	// Name of the EPS file for output

  [savePanel setTitle:"Save EPS"]; /* make sure title is OK */
  if ([savePanel runModal])  {
    eps_fileName = (char *) calloc(strlen([savePanel filename])+1, sizeof(char));
    strcpy(eps_fileName, [savePanel filename]);
    [self savePSCode:eps_fileName];
  }

  return self;
}

- savePSCode:(char *)aFile
{
    NXStream	*psStream;

    psStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    if (!psStream)  {
      return self;
    }
    [self getBounds:&bounds];
    [self copyPSCodeInside:&bounds to:psStream];
	   
    NXFlush(psStream);
    NXSaveToFile(psStream, aFile);
    NXCloseMemory(psStream, NX_FREEBUFFER);

    return self;
}

/*
 * Following code taken from the Graph example in NextDeveloper/Examples.
 *
 * Copies the current view into the Pasteboard as PostScript.
 */
- copyPScode:sender
{
    NXStream *psStream;
    id        pb;
    char     *data;
    int       dataLen, maxDataLen;

  /* Open a stream on memory where we will collect the PostScript */
    psStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    if (!psStream)
      return self;

  /* Tell the Pasteboard we're going to copy PostScript */
    pb = [Pasteboard new];
    [pb declareTypes:&NXPostScriptPboardType num:1 owner:self];

  /* writes the PostScript for the whole plot as EPS into the stream */
    [self getBounds:&bounds];
    [self copyPSCodeInside:&bounds to:psStream];

  /* get the buffered up PostScript out of the stream */
    NXGetMemoryBuffer(psStream, &data, &dataLen, &maxDataLen);

  /* put the buffer in the Pasteboard, free the stream (and the buffer) */
    [pb writeType:NXPostScriptPboardType data:data length:dataLen];
    NXCloseMemory(psStream, NX_FREEBUFFER);
    return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.